feat(i18n): 改造10个组件为i18n国际化,同步更新9种语言翻译文件
- 组件改造: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 接口
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<view class="bottom-navigation">
|
||||
<view
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
:class="['nav-item', { 'nav-item--active': currentTab === tab.key }]"
|
||||
@click="switchTab(tab.key)"
|
||||
>
|
||||
<Icon
|
||||
:name="tab.icon"
|
||||
:size="'22rpx'"
|
||||
:color="currentTab === tab.key ? 'rgba(196, 30, 58, 255)' : 'rgba(113, 113, 122, 255)'"
|
||||
/>
|
||||
<text :class="['nav-label', { 'nav-label--active': currentTab === tab.key }]">
|
||||
{{ tab.label }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import Icon from '../Icon/Icon.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
interface Tab {
|
||||
key: string
|
||||
label: string
|
||||
icon: string
|
||||
path: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
currentTab: string
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
currentTab: 'almanac'
|
||||
})
|
||||
|
||||
const tabs = computed<Tab[]>(() => [
|
||||
{ key: 'almanac', label: t('nav.almanac'), icon: 'calendar', path: '/pages/almanac-search/index' },
|
||||
{ key: 'ziwei', label: t('nav.ziwei'), icon: 'star', path: '/pages/ziwei/index' },
|
||||
{ key: 'fortune', label: t('nav.fortune'), icon: 'heart', path: '/pages/fortune/index' }
|
||||
])
|
||||
|
||||
const switchTab = (key: string) => {
|
||||
const tab = tabs.find(t => t.key === key)
|
||||
if (tab) {
|
||||
uni.navigateTo({ url: tab.path })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.bottom-navigation {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
height: 56px;
|
||||
background-color: rgba(255, 255, 255, 255);
|
||||
border-top: 1px solid rgba(230, 225, 220, 255);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2px;
|
||||
padding: 6px 16px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.nav-label {
|
||||
font-size: 10px;
|
||||
line-height: 14px;
|
||||
color: rgba(113, 113, 122, 255);
|
||||
font-weight: 400;
|
||||
|
||||
&--active {
|
||||
color: rgba(196, 30, 58, 255);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 375px) {
|
||||
.bottom-navigation {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
padding: 4px 12px;
|
||||
}
|
||||
|
||||
.nav-label {
|
||||
font-size: 9px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,204 @@
|
||||
<template>
|
||||
<view
|
||||
:class="[
|
||||
'button',
|
||||
`button--${type}`,
|
||||
`button--${size}`,
|
||||
{
|
||||
'button--block': block,
|
||||
'button--disabled': disabled,
|
||||
'button--loading': loading
|
||||
}
|
||||
]"
|
||||
@click="handleClick"
|
||||
>
|
||||
<view v-if="loading" class="button-spinner"></view>
|
||||
<Icon v-if="icon && !loading" :name="icon" :size="iconSize" :color="iconColor" />
|
||||
<text class="button-text"><slot /></text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import Icon from '../Icon/Icon.vue'
|
||||
|
||||
interface Props {
|
||||
type?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger'
|
||||
size?: 'small' | 'medium' | 'large'
|
||||
block?: boolean
|
||||
disabled?: boolean
|
||||
loading?: boolean
|
||||
icon?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
type: 'primary',
|
||||
size: 'medium',
|
||||
block: false,
|
||||
disabled: false,
|
||||
loading: false
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
click: []
|
||||
}>()
|
||||
|
||||
const iconSize = computed(() => {
|
||||
switch (props.size) {
|
||||
case 'small': return '14rpx'
|
||||
case 'large': return '22rpx'
|
||||
default: return '18rpx'
|
||||
}
|
||||
})
|
||||
|
||||
const iconColor = computed(() => {
|
||||
if (props.type === 'primary') return '#ffffff'
|
||||
return 'rgba(44, 24, 16, 255)'
|
||||
})
|
||||
|
||||
const handleClick = () => {
|
||||
if (!props.disabled && !props.loading) {
|
||||
emit('click')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
white-space: nowrap;
|
||||
|
||||
&--small {
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
&--medium {
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
&--large {
|
||||
padding: 12px 24px;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
&--block {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&--loading {
|
||||
cursor: wait;
|
||||
}
|
||||
}
|
||||
|
||||
.button--primary {
|
||||
background-color: rgba(196, 30, 58, 255);
|
||||
color: #ffffff;
|
||||
|
||||
&:active:not(.button--disabled):not(.button--loading) {
|
||||
background-color: rgba(170, 20, 45, 255);
|
||||
}
|
||||
}
|
||||
|
||||
.button--secondary {
|
||||
background-color: rgba(244, 244, 245, 255);
|
||||
color: rgba(44, 24, 16, 255);
|
||||
border-color: rgba(230, 225, 220, 255);
|
||||
|
||||
&:active:not(.button--disabled):not(.button--loading) {
|
||||
background-color: rgba(230, 225, 220, 255);
|
||||
}
|
||||
}
|
||||
|
||||
.button--outline {
|
||||
background-color: transparent;
|
||||
color: rgba(196, 30, 58, 255);
|
||||
border-color: rgba(196, 30, 58, 255);
|
||||
|
||||
&:active:not(.button--disabled):not(.button--loading) {
|
||||
background-color: rgba(196, 30, 58, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.button--ghost {
|
||||
background-color: transparent;
|
||||
color: rgba(44, 24, 16, 255);
|
||||
|
||||
&:active:not(.button--disabled):not(.button--loading) {
|
||||
background-color: rgba(244, 244, 245, 255);
|
||||
}
|
||||
}
|
||||
|
||||
.button--danger {
|
||||
background-color: rgba(220, 38, 38, 255);
|
||||
color: #ffffff;
|
||||
|
||||
&:active:not(.button--disabled):not(.button--loading) {
|
||||
background-color: rgba(185, 28, 28, 255);
|
||||
}
|
||||
}
|
||||
|
||||
.button-text {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.button-spinner {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 2px solid #ffffff;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
.button--secondary .button-spinner,
|
||||
.button--ghost .button-spinner {
|
||||
border-color: rgba(44, 24, 16, 0.2);
|
||||
border-top-color: rgba(44, 24, 16, 0.8);
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@media screen and (max-width: 375px) {
|
||||
.button--small {
|
||||
padding: 5px 10px;
|
||||
font-size: 11px;
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
.button--medium {
|
||||
padding: 7px 14px;
|
||||
font-size: 13px;
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
.button--large {
|
||||
padding: 10px 20px;
|
||||
font-size: 15px;
|
||||
min-height: 44px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<view :class="['card', { 'card--clickable': clickable, 'card--bordered': bordered }]" @click="handleClick">
|
||||
<view v-if="$slots.header" class="card-header">
|
||||
<slot name="header" />
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<slot />
|
||||
</view>
|
||||
<view v-if="$slots.footer" class="card-footer">
|
||||
<slot name="footer" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
clickable?: boolean
|
||||
bordered?: boolean
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
clickable: false,
|
||||
bordered: true
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
click: []
|
||||
}>()
|
||||
|
||||
const handleClick = () => {
|
||||
emit('click')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.card {
|
||||
background-color: rgba(255, 255, 255, 255);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&--bordered {
|
||||
border: 1px solid rgba(230, 225, 220, 255);
|
||||
}
|
||||
|
||||
&--clickable {
|
||||
cursor: pointer;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
background-color: rgba(244, 244, 245, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid rgba(230, 225, 220, 255);
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
padding: 12px 16px;
|
||||
border-top: 1px solid rgba(230, 225, 220, 255);
|
||||
background-color: rgba(244, 244, 245, 0.3);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 375px) {
|
||||
.card-header {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
padding: 10px 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,20 +1,23 @@
|
||||
<template>
|
||||
<view class="empty-state">
|
||||
<Icon :name="iconName" :size="iconSize" color="rgba(200, 200, 200, 255)" />
|
||||
<Typography variant="h4" weight="semibold" class="empty-title">{{ title }}</Typography>
|
||||
<Typography variant="body" class="empty-description">{{ description }}</Typography>
|
||||
<Typography variant="h4" weight="semibold" class="empty-title">{{ displayTitle }}</Typography>
|
||||
<Typography variant="body" class="empty-description">{{ displayDescription }}</Typography>
|
||||
<Button v-if="showAction" type="primary" @click="handleAction">
|
||||
{{ actionText }}
|
||||
{{ displayActionText }}
|
||||
</Button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import Icon from '../Icon/Icon.vue'
|
||||
import Typography from '../Typography/Typography.vue'
|
||||
import Button from '../Button/Button.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
interface Props {
|
||||
icon?: string
|
||||
title?: string
|
||||
@@ -26,13 +29,17 @@ interface Props {
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
icon: 'empty',
|
||||
title: '暂无数据',
|
||||
description: '没有找到相关结果',
|
||||
title: undefined,
|
||||
description: undefined,
|
||||
showAction: true,
|
||||
actionText: '重新搜索',
|
||||
actionText: undefined,
|
||||
size: 'medium'
|
||||
})
|
||||
|
||||
const displayTitle = computed(() => props.title ?? t('common.noData'))
|
||||
const displayDescription = computed(() => props.description ?? t('common.noResult'))
|
||||
const displayActionText = computed(() => props.actionText ?? t('common.retry'))
|
||||
|
||||
const emit = defineEmits<{
|
||||
action: []
|
||||
}>()
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
@click="handleExport"
|
||||
>
|
||||
<Icon name="download" size="16rpx" color="#FFFFFF" />
|
||||
<Typography variant="body" color="#FFFFFF">导出结果</Typography>
|
||||
<Typography variant="body" color="#FFFFFF">{{ $t('export.exportResults') }}</Typography>
|
||||
</Button>
|
||||
|
||||
<view v-if="showExportModal" class="export-modal">
|
||||
<view class="modal-content">
|
||||
<view class="modal-header">
|
||||
<Typography variant="h4" weight="semibold">导出搜索结果</Typography>
|
||||
<Typography variant="h4" weight="semibold">{{ $t('export.exportSearchResults') }}</Typography>
|
||||
<Button size="small" type="text" @click="closeModal">
|
||||
<Icon name="close" size="16rpx" color="rgba(113, 113, 122, 255)" />
|
||||
</Button>
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<view class="modal-body">
|
||||
<view class="export-options">
|
||||
<Typography variant="body" class="option-title">选择导出格式</Typography>
|
||||
<Typography variant="body" class="option-title">{{ $t('export.selectFormat') }}</Typography>
|
||||
<view class="format-options">
|
||||
<view
|
||||
v-for="format in exportFormats"
|
||||
@@ -40,7 +40,7 @@
|
||||
</view>
|
||||
|
||||
<view class="export-options">
|
||||
<Typography variant="body" class="option-title">选择导出内容</Typography>
|
||||
<Typography variant="body" class="option-title">{{ $t('export.selectContent') }}</Typography>
|
||||
<view class="content-options">
|
||||
<view
|
||||
v-for="option in contentOptions"
|
||||
@@ -61,8 +61,8 @@
|
||||
</view>
|
||||
|
||||
<view class="modal-footer">
|
||||
<Button type="text" @click="closeModal">取消</Button>
|
||||
<Button type="primary" @click="confirmExport">导出</Button>
|
||||
<Button type="text" @click="closeModal">{{ $t('common.cancel') }}</Button>
|
||||
<Button type="primary" @click="confirmExport">{{ $t('export.export') }}</Button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -70,7 +70,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { SearchResult } from '../../types/search'
|
||||
import Button from '../Button/Button.vue'
|
||||
import Icon from '../Icon/Icon.vue'
|
||||
@@ -86,6 +87,8 @@ const emit = defineEmits<{
|
||||
export: [format: string, content: string[]]
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const showExportModal = ref(false)
|
||||
const selectedFormat = ref('excel')
|
||||
const selectedContent = ref(['date', 'lunarDate', 'matchedItems'])
|
||||
@@ -96,13 +99,13 @@ const exportFormats = [
|
||||
{ label: 'CSV (.csv)', value: 'csv' }
|
||||
]
|
||||
|
||||
const contentOptions = [
|
||||
{ label: '日期', value: 'date' },
|
||||
{ label: '农历日期', value: 'lunarDate' },
|
||||
{ label: '星期', value: 'weekday' },
|
||||
{ label: '匹配事项', value: 'matchedItems' },
|
||||
{ label: '匹配度', value: 'matchCount' }
|
||||
]
|
||||
const contentOptions = computed(() => [
|
||||
{ label: t('export.contentDate'), value: 'date' },
|
||||
{ label: t('export.contentLunarDate'), value: 'lunarDate' },
|
||||
{ label: t('export.contentWeekday'), value: 'weekday' },
|
||||
{ label: t('export.contentMatchedItems'), value: 'matchedItems' },
|
||||
{ label: t('export.contentMatchCount'), value: 'matchCount' }
|
||||
])
|
||||
|
||||
function handleExport() {
|
||||
showExportModal.value = true
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<view class="icon-wrapper" :style="iconStyle" @click="$emit('click')">
|
||||
<text class="icon-text" :style="textStyle">{{ iconChar }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
interface Props {
|
||||
name: string
|
||||
size?: string
|
||||
color?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
size: '20rpx',
|
||||
color: 'rgba(44, 24, 16, 255)'
|
||||
})
|
||||
|
||||
defineEmits<{
|
||||
click: []
|
||||
}>()
|
||||
|
||||
const iconMap: Record<string, string> = {
|
||||
'arrow-up': '↑',
|
||||
'arrow-down': '↓',
|
||||
'arrow-left': '←',
|
||||
'arrow-right': '→',
|
||||
'check': '✓',
|
||||
'close': '✕',
|
||||
'plus': '+',
|
||||
'minus': '−',
|
||||
'search': '🔍',
|
||||
'empty': '📭',
|
||||
'calendar': '📅',
|
||||
'star': '★',
|
||||
'heart': '♥',
|
||||
'settings': '⚙',
|
||||
'home': '🏠',
|
||||
'user': '👤',
|
||||
'info': 'ℹ',
|
||||
'warning': '⚠',
|
||||
'error': '✕',
|
||||
'success': '✓',
|
||||
'refresh': '↻',
|
||||
'download': '⬇',
|
||||
'upload': '⬆',
|
||||
'share': '↗',
|
||||
'copy': '📋',
|
||||
'delete': '🗑',
|
||||
'edit': '✎',
|
||||
'filter': '⊞',
|
||||
'sort': '⇅',
|
||||
'export': '📤',
|
||||
'import': '📥',
|
||||
'history': '🕐',
|
||||
'bookmark': '🔖',
|
||||
'chevron-up': '‹',
|
||||
'chevron-down': '›',
|
||||
'chevron-left': '‹',
|
||||
'chevron-right': '›'
|
||||
}
|
||||
|
||||
const iconChar = computed(() => {
|
||||
return iconMap[props.name] || props.name.charAt(0).toUpperCase()
|
||||
})
|
||||
|
||||
const iconStyle = computed(() => ({
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: props.size,
|
||||
height: props.size
|
||||
}))
|
||||
|
||||
const textStyle = computed(() => ({
|
||||
fontSize: props.size,
|
||||
color: props.color,
|
||||
lineHeight: '1'
|
||||
}))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.icon-wrapper {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.icon-text {
|
||||
text-align: center;
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
<view class="condition-body">
|
||||
<view class="condition-section">
|
||||
<Typography variant="caption" class="section-label">事项</Typography>
|
||||
<Typography variant="caption" class="section-label">{{ $t('search.activityItems') }}</Typography>
|
||||
<view class="items-selector">
|
||||
<view
|
||||
v-for="item in availableItems"
|
||||
@@ -28,7 +28,7 @@
|
||||
</view>
|
||||
|
||||
<view class="condition-section">
|
||||
<Typography variant="caption" class="section-label">逻辑运算符</Typography>
|
||||
<Typography variant="caption" class="section-label">{{ $t('search.logicalOperator') }}</Typography>
|
||||
<Select
|
||||
v-model="localCondition.operator"
|
||||
:options="operatorOptions"
|
||||
@@ -43,7 +43,7 @@
|
||||
size="20rpx"
|
||||
:color="localCondition.exclude ? 'rgba(196, 30, 58, 255)' : 'rgba(113, 113, 122, 255)'"
|
||||
/>
|
||||
<Typography variant="body" class="exclude-label">排除条件</Typography>
|
||||
<Typography variant="body" class="exclude-label">{{ $t('search.excludeCondition') }}</Typography>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -52,6 +52,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { SearchCondition } from '../../types/search'
|
||||
import Card from '../Card/Card.vue'
|
||||
import Button from '../Button/Button.vue'
|
||||
@@ -70,17 +71,19 @@ const emit = defineEmits<{
|
||||
delete: []
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const localCondition = ref<SearchCondition>({ ...props.condition })
|
||||
|
||||
const typeOptions = [
|
||||
{ label: '宜', value: 'suitable' },
|
||||
{ label: '忌', value: 'unsuitable' }
|
||||
]
|
||||
const typeOptions = computed(() => [
|
||||
{ label: t('search.suitable'), value: 'suitable' },
|
||||
{ label: t('search.unsuitable'), value: 'unsuitable' }
|
||||
])
|
||||
|
||||
const operatorOptions = [
|
||||
{ label: '与(AND)', value: 'and' },
|
||||
{ label: '或(OR)', value: 'or' }
|
||||
]
|
||||
const operatorOptions = computed(() => [
|
||||
{ label: t('search.operatorAnd'), value: 'and' },
|
||||
{ label: t('search.operatorOr'), value: 'or' }
|
||||
])
|
||||
|
||||
const suitableActivities = [
|
||||
'祭祀', '祈福', '求嗣', '开光', '出行', '嫁娶', '订盟', '纳采', '裁衣', '安床',
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
<template>
|
||||
<Card class="search-condition-panel">
|
||||
<view class="panel-header">
|
||||
<Typography variant="h4" weight="semibold" class="panel-title">{{ $t('search.searchConditions') }}</Typography>
|
||||
<Button size="small" type="primary" icon="plus" @click="addCondition">{{ $t('search.addCondition') }}</Button>
|
||||
</view>
|
||||
|
||||
<view v-if="conditions.length === 0" class="empty-conditions">
|
||||
<Typography variant="body" class="empty-text">{{ $t('search.addConditionHint') }}</Typography>
|
||||
</view>
|
||||
|
||||
<view v-else class="conditions-list">
|
||||
<SearchConditionItem
|
||||
v-for="(condition, index) in conditions"
|
||||
:key="condition.id"
|
||||
:condition="condition"
|
||||
@update="updateCondition(index, $event)"
|
||||
@delete="removeCondition(index)"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="days-selector">
|
||||
<Typography variant="body" class="days-label">{{ $t('search.searchDays') }}</Typography>
|
||||
<view class="days-options">
|
||||
<view
|
||||
v-for="dayOption in dayOptions"
|
||||
:key="dayOption"
|
||||
:class="['day-tag', { 'day-tag-selected': days === dayOption }]"
|
||||
@click="updateDays(dayOption)"
|
||||
>
|
||||
<Typography variant="caption" class="day-text">{{ $t('search.daysUnit', { count: dayOption }) }}</Typography>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="panel-footer">
|
||||
<Button type="primary" block @click="handleSearch">{{ $t('common.search') }}</Button>
|
||||
</view>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import type { SearchCondition } from '../../types/search'
|
||||
import Card from '../Card/Card.vue'
|
||||
import Button from '../Button/Button.vue'
|
||||
import Typography from '../Typography/Typography.vue'
|
||||
import SearchConditionItem from '../SearchConditionItem/index.vue'
|
||||
|
||||
interface Props {
|
||||
conditions: SearchCondition[]
|
||||
days: number
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:conditions': [conditions: SearchCondition[]]
|
||||
'update:days': [days: number]
|
||||
search: []
|
||||
}>()
|
||||
|
||||
const dayOptions = [7, 15, 30, 60, 90]
|
||||
|
||||
const addCondition = () => {
|
||||
const newCondition: SearchCondition = {
|
||||
id: Date.now().toString(),
|
||||
type: 'suitable',
|
||||
items: [],
|
||||
operator: 'and',
|
||||
exclude: false
|
||||
}
|
||||
const updated = [...props.conditions, newCondition]
|
||||
emit('update:conditions', updated)
|
||||
}
|
||||
|
||||
const updateCondition = (index: number, condition: SearchCondition) => {
|
||||
const updated = [...props.conditions]
|
||||
updated[index] = condition
|
||||
emit('update:conditions', updated)
|
||||
}
|
||||
|
||||
const removeCondition = (index: number) => {
|
||||
const updated = props.conditions.filter((_, i) => i !== index)
|
||||
emit('update:conditions', updated)
|
||||
}
|
||||
|
||||
const updateDays = (days: number) => {
|
||||
emit('update:days', days)
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
emit('search')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.search-condition-panel {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
color: rgba(44, 24, 16, 255);
|
||||
font-size: 18px;
|
||||
line-height: 28px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.empty-conditions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 32px 16px;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
color: rgba(113, 113, 122, 255);
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.conditions-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.days-selector {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.days-label {
|
||||
color: rgba(44, 24, 16, 255);
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.days-options {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.day-tag {
|
||||
padding: 6px 14px;
|
||||
background-color: rgba(244, 244, 245, 255);
|
||||
border: 1px solid rgba(230, 225, 220, 255);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
background-color: rgba(230, 225, 220, 255);
|
||||
}
|
||||
}
|
||||
|
||||
.day-tag-selected {
|
||||
background-color: rgba(220, 252, 231, 1);
|
||||
border-color: rgba(185, 248, 207, 1);
|
||||
}
|
||||
|
||||
.day-text {
|
||||
color: rgba(44, 24, 16, 255);
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.panel-footer {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 375px) {
|
||||
.panel-header {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.conditions-list {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.days-options {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.day-tag {
|
||||
padding: 5px 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<Card class="search-history-panel">
|
||||
<view class="panel-header">
|
||||
<Typography variant="h4" weight="semibold">搜索历史</Typography>
|
||||
<Typography variant="h4" weight="semibold">{{ $t('search.searchHistory') }}</Typography>
|
||||
<Button size="small" type="text" @click="handleClearHistory">
|
||||
<Typography variant="caption" color="rgba(196, 30, 58, 255)">清除历史</Typography>
|
||||
<Typography variant="caption" color="rgba(196, 30, 58, 255)">{{ $t('search.clearHistory') }}</Typography>
|
||||
</Button>
|
||||
</view>
|
||||
|
||||
<view v-if="history.length === 0" class="empty-history">
|
||||
<EmptyState
|
||||
icon="empty"
|
||||
title="暂无搜索历史"
|
||||
description="执行搜索后,搜索条件将显示在这里"
|
||||
:title="$t('search.noHistoryTitle')"
|
||||
:description="$t('search.noHistoryDesc')"
|
||||
size="small"
|
||||
/>
|
||||
</view>
|
||||
@@ -39,6 +39,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { SearchRequest } from '../../types/search'
|
||||
import searchService from '../../services/searchService'
|
||||
import Card from '../Card/Card.vue'
|
||||
@@ -53,6 +54,8 @@ interface HistoryItem {
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const emit = defineEmits<{
|
||||
select: [condition: SearchRequest]
|
||||
}>()
|
||||
@@ -73,8 +76,8 @@ function handleSelectHistory(item: HistoryItem) {
|
||||
|
||||
function handleClearHistory() {
|
||||
uni.showModal({
|
||||
title: '确认清除',
|
||||
content: '确定要清除所有搜索历史吗?',
|
||||
title: t('search.confirmClear'),
|
||||
content: t('search.confirmClearContent'),
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
searchService.clearSearchHistory()
|
||||
@@ -88,11 +91,11 @@ function formatHistoryTitle(condition: SearchRequest): string {
|
||||
const conditionCount = condition.conditions.length
|
||||
const days = condition.days
|
||||
|
||||
let title = `${conditionCount}个条件,${days}天范围`
|
||||
let title = t('search.historyConditionDays', { count: conditionCount, days })
|
||||
|
||||
if (conditionCount > 0) {
|
||||
const firstCondition = condition.conditions[0]
|
||||
const type = firstCondition.type === 'suitable' ? '宜' : '忌'
|
||||
const type = firstCondition.type === 'suitable' ? t('search.suitable') : t('search.unsuitable')
|
||||
const items = firstCondition.items.slice(0, 2).join('、')
|
||||
|
||||
title += `(${type}${items}${firstCondition.items.length > 2 ? '...' : ''})`
|
||||
@@ -108,13 +111,13 @@ function formatTime(timeStr: string): string {
|
||||
const diffHours = Math.floor(diffMs / (1000 * 60 * 60))
|
||||
|
||||
if (diffHours < 1) {
|
||||
return '刚刚'
|
||||
return t('search.justNow')
|
||||
} else if (diffHours < 24) {
|
||||
return `${diffHours}小时前`
|
||||
return t('search.hoursAgo', { count: diffHours })
|
||||
} else {
|
||||
const diffDays = Math.floor(diffHours / 24)
|
||||
if (diffDays < 7) {
|
||||
return `${diffDays}天前`
|
||||
return t('search.daysAgo', { count: diffDays })
|
||||
} else {
|
||||
return date.toLocaleDateString()
|
||||
}
|
||||
|
||||
@@ -9,21 +9,22 @@
|
||||
<Typography variant="caption" class="lunar-date">{{ result.lunarDate }}</Typography>
|
||||
|
||||
<view v-if="result.matchedItems.suitable.length > 0" class="matched-items">
|
||||
<Typography variant="caption" class="label suitable-label">宜</Typography>
|
||||
<Typography variant="caption" class="label suitable-label">{{ $t('search.suitable') }}</Typography>
|
||||
<Typography variant="body" class="items suitable-items">{{ result.matchedItems.suitable.join('、') }}</Typography>
|
||||
</view>
|
||||
|
||||
<view v-if="result.matchedItems.unsuitable.length > 0" class="matched-items">
|
||||
<Typography variant="caption" class="label unsuitable-label">忌</Typography>
|
||||
<Typography variant="caption" class="label unsuitable-label">{{ $t('search.unsuitable') }}</Typography>
|
||||
<Typography variant="body" class="items unsuitable-items">{{ result.matchedItems.unsuitable.join('、') }}</Typography>
|
||||
</view>
|
||||
|
||||
<Typography variant="caption" class="match-count">匹配度: {{ result.matchCount }}</Typography>
|
||||
<Typography variant="caption" class="match-count">{{ $t('search.matchCount') }}: {{ result.matchCount }}</Typography>
|
||||
</view>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { SearchResult } from '../../types/search'
|
||||
import Card from '../Card/Card.vue'
|
||||
import Typography from '../Typography/Typography.vue'
|
||||
@@ -34,12 +35,14 @@ interface Props {
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const formatDate = (dateStr: string) => {
|
||||
const date = new Date(dateStr)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}年${month}月${day}日`
|
||||
return t('search.dateFormat', { year, month, day })
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<view class="search-result-list">
|
||||
<view class="list-header">
|
||||
<Typography variant="h4" weight="semibold">搜索结果</Typography>
|
||||
<Typography variant="caption" class="result-count">共{{ results.length }}条结果</Typography>
|
||||
<Typography variant="h4" weight="semibold">{{ $t('search.searchResults') }}</Typography>
|
||||
<Typography variant="caption" class="result-count">{{ $t('search.resultCount', { count: results.length }) }}</Typography>
|
||||
<SortSwitcher
|
||||
:sortBy="sortBy"
|
||||
:sortOrder="sortOrder"
|
||||
@@ -23,7 +23,7 @@
|
||||
/>
|
||||
|
||||
<view v-if="loadingMore" class="loading-more">
|
||||
<LoadingIndicator text="加载中..." />
|
||||
<LoadingIndicator :text="$t('common.loading')" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
:variant="size === 'small' ? 'caption' : 'body'"
|
||||
class="select-placeholder"
|
||||
>
|
||||
{{ placeholder }}
|
||||
{{ displayPlaceholder }}
|
||||
</Typography>
|
||||
<Icon
|
||||
:name="isOpen ? 'arrow-up' : 'arrow-down'"
|
||||
@@ -65,6 +65,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import Typography from '@/components/Typography/Typography.vue'
|
||||
import Icon from '@/components/Icon/Icon.vue'
|
||||
|
||||
@@ -81,12 +82,16 @@ interface Props {
|
||||
size?: 'small' | 'medium' | 'large'
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
placeholder: '请选择',
|
||||
placeholder: undefined,
|
||||
disabled: false,
|
||||
size: 'medium'
|
||||
})
|
||||
|
||||
const displayPlaceholder = computed(() => props.placeholder ?? t('common.pleaseSelect'))
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: string | number): void
|
||||
(e: 'change', value: string | number): void
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view class="sort-switcher">
|
||||
<Typography variant="caption" class="sort-label">排序</Typography>
|
||||
<Typography variant="caption" class="sort-label">{{ $t('search.sort') }}</Typography>
|
||||
|
||||
<view class="sort-options">
|
||||
<view
|
||||
@@ -8,7 +8,7 @@
|
||||
:class="{ 'sort-option-active': sortBy === 'date' }"
|
||||
@click="handleSortByChange('date')"
|
||||
>
|
||||
<Typography variant="caption" class="sort-option-text">日期</Typography>
|
||||
<Typography variant="caption" class="sort-option-text">{{ $t('search.sortByDate') }}</Typography>
|
||||
<Icon
|
||||
v-if="sortBy === 'date'"
|
||||
:name="sortOrder === 'asc' ? 'arrow-up' : 'arrow-down'"
|
||||
@@ -22,7 +22,7 @@
|
||||
:class="{ 'sort-option-active': sortBy === 'matchCount' }"
|
||||
@click="handleSortByChange('matchCount')"
|
||||
>
|
||||
<Typography variant="caption" class="sort-option-text">匹配度</Typography>
|
||||
<Typography variant="caption" class="sort-option-text">{{ $t('search.sortByMatch') }}</Typography>
|
||||
<Icon
|
||||
v-if="sortBy === 'matchCount'"
|
||||
:name="sortOrder === 'asc' ? 'arrow-up' : 'arrow-down'"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<Card class="template-panel">
|
||||
<view class="panel-header">
|
||||
<Typography variant="h4" weight="semibold">搜索模板</Typography>
|
||||
<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>
|
||||
@@ -12,7 +12,7 @@
|
||||
<Icon name="search" size="16rpx" color="rgba(113, 113, 122, 255)" />
|
||||
<input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索模板"
|
||||
:placeholder="$t('search.searchTemplates')"
|
||||
class="input-field"
|
||||
/>
|
||||
</view>
|
||||
@@ -33,8 +33,8 @@
|
||||
<view v-if="filteredTemplates.length === 0" class="empty-templates">
|
||||
<EmptyState
|
||||
icon="empty"
|
||||
title="暂无模板"
|
||||
description="没有找到符合条件的模板"
|
||||
:title="$t('search.noTemplatesTitle')"
|
||||
:description="$t('search.noTemplatesDesc')"
|
||||
size="small"
|
||||
/>
|
||||
</view>
|
||||
@@ -65,6 +65,7 @@
|
||||
|
||||
<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'
|
||||
@@ -73,23 +74,25 @@ 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('全部')
|
||||
const selectedCategory = ref('__all__')
|
||||
const templates = ref<SearchTemplate[]>([])
|
||||
|
||||
const categories = computed(() => {
|
||||
const allCategories = templateService.getCategories()
|
||||
return ['全部', ...allCategories]
|
||||
return [t('search.allCategories'), ...allCategories]
|
||||
})
|
||||
|
||||
const filteredTemplates = computed(() => {
|
||||
let result = templates.value
|
||||
|
||||
if (selectedCategory.value !== '全部') {
|
||||
if (selectedCategory.value !== '__all__') {
|
||||
result = templateService.getTemplatesByCategory(selectedCategory.value)
|
||||
}
|
||||
|
||||
@@ -105,7 +108,7 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
function loadTemplates() {
|
||||
templates.value = templateService.getTemplates()
|
||||
templates.value = templateService.getSearchTemplates()
|
||||
}
|
||||
|
||||
function handleRefresh() {
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<view :class="['typography', `typography--${variant}`, `typography--${weight}`, { 'typography--ellipsis': ellipsis }]">
|
||||
<slot />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
variant?: 'h1' | 'h2' | 'h3' | 'h4' | 'body' | 'caption' | 'overline'
|
||||
weight?: 'regular' | 'medium' | 'semibold' | 'bold'
|
||||
ellipsis?: boolean
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
variant: 'body',
|
||||
weight: 'regular',
|
||||
ellipsis: false
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.typography {
|
||||
color: rgba(44, 24, 16, 255);
|
||||
|
||||
&--ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.typography--h1 {
|
||||
font-size: 28px;
|
||||
line-height: 40px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.typography--h2 {
|
||||
font-size: 24px;
|
||||
line-height: 36px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.typography--h3 {
|
||||
font-size: 20px;
|
||||
line-height: 30px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.typography--h4 {
|
||||
font-size: 18px;
|
||||
line-height: 28px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.typography--body {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.typography--caption {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.typography--overline {
|
||||
font-size: 10px;
|
||||
line-height: 14px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.typography--regular {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.typography--medium {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.typography--semibold {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.typography--bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 375px) {
|
||||
.typography--h1 {
|
||||
font-size: 24px;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.typography--h2 {
|
||||
font-size: 20px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.typography--h3 {
|
||||
font-size: 18px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.typography--h4 {
|
||||
font-size: 16px;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.typography--body {
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.typography--caption {
|
||||
font-size: 11px;
|
||||
line-height: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -9,6 +9,7 @@ export default {
|
||||
search: 'Suchen',
|
||||
male: 'Männlich',
|
||||
female: 'Weiblich',
|
||||
pleaseSelect: 'Bitte auswählen',
|
||||
},
|
||||
nav: {
|
||||
almanac: 'Almanach',
|
||||
@@ -45,63 +46,61 @@ export default {
|
||||
monthly: 'Monatlich',
|
||||
selectDate: 'Datum auswählen',
|
||||
selectMonth: 'Monat auswählen',
|
||||
overallFortune: 'Gesamtschicksal',
|
||||
career: 'Karriere',
|
||||
wealth: 'Wohlstand',
|
||||
relationship: 'Beziehung',
|
||||
health: 'Gesundheit',
|
||||
luckyColor: 'Glücksfarbe',
|
||||
luckyNumber: 'Glückszahl',
|
||||
luckyDirection: 'Glücksrichtung',
|
||||
noChart: 'Bitte erstellen Sie zuerst ein Diagramm auf der Zi Wei Dou Shu Seite',
|
||||
overallScore: 'Gesamtschicksal: {score}Pkt',
|
||||
career: 'Karriere: ',
|
||||
wealth: 'Wohlstand: ',
|
||||
relationship: 'Beziehung: ',
|
||||
health: 'Gesundheit: ',
|
||||
luckyColor: 'Glücksfarbe: ',
|
||||
luckyNumber: 'Glückszahl: ',
|
||||
luckyDirection: 'Glücksrichtung: ',
|
||||
noChartHint: 'Bitte erstellen Sie zuerst ein Diagramm auf der Zi Wei Dou Shu Seite',
|
||||
},
|
||||
search: {
|
||||
conditions: 'Suchbedingungen',
|
||||
searchConditions: 'Suchbedingungen',
|
||||
addCondition: 'Bedingung hinzufügen',
|
||||
addConditionHint: 'Bitte fügen Sie mindestens eine Suchbedingung hinzu',
|
||||
addConditionEmpty: 'Suchbedingungen hinzufügen',
|
||||
days: 'Suchtage',
|
||||
dayUnit: 'Tage',
|
||||
searchResult: 'Suchergebnisse',
|
||||
searchDays: 'Suchtage',
|
||||
daysUnit: '{count} Tage',
|
||||
searchResults: 'Suchergebnisse',
|
||||
resultCount: '{count} Ergebnisse',
|
||||
matchCount: 'Übereinstimmung: {count}',
|
||||
matchCount: 'Übereinstimmung',
|
||||
sort: 'Sortieren',
|
||||
sortByDate: 'Datum',
|
||||
sortByMatch: 'Übereinstimmung',
|
||||
typeSuitable: 'Günstig',
|
||||
typeUnsuitable: 'Ungünstig',
|
||||
suitable: 'Günstig',
|
||||
unsuitable: 'Ungünstig',
|
||||
operatorAnd: 'UND',
|
||||
operatorOr: 'ODER',
|
||||
excludeCondition: 'Bedingung ausschließen',
|
||||
items: 'Einträge',
|
||||
operator: 'Logischer Operator',
|
||||
},
|
||||
template: {
|
||||
title: 'Suchvorlagen',
|
||||
searchPlaceholder: 'Vorlagen durchsuchen',
|
||||
empty: 'Keine Vorlagen',
|
||||
emptyDesc: 'Keine Vorlagen entsprechen den Kriterien',
|
||||
all: 'Alle',
|
||||
},
|
||||
export: {
|
||||
title: 'Suchergebnisse exportieren',
|
||||
selectFormat: 'Exportformat auswählen',
|
||||
selectContent: 'Exportinhalt auswählen',
|
||||
date: 'Datum',
|
||||
lunarDate: 'Mondkalender-Datum',
|
||||
weekday: 'Wochentag',
|
||||
matchedItems: 'Übereinstimmende Einträge',
|
||||
matchCount: 'Übereinstimmung',
|
||||
},
|
||||
history: {
|
||||
title: 'Suchverlauf',
|
||||
clear: 'Verlauf löschen',
|
||||
clearConfirm: 'Löschen bestätigen',
|
||||
clearConfirmMsg: 'Möchten Sie den gesamten Suchverlauf löschen?',
|
||||
empty: 'Kein Suchverlauf',
|
||||
emptyDesc: 'Suchbedingungen werden nach der Suche hier angezeigt',
|
||||
activityItems: 'Einträge',
|
||||
logicalOperator: 'Logischer Operator',
|
||||
dateFormat: '{day}.{month}.{year}',
|
||||
searchHistory: 'Suchverlauf',
|
||||
clearHistory: 'Verlauf löschen',
|
||||
noHistoryTitle: 'Kein Suchverlauf',
|
||||
noHistoryDesc: 'Suchbedingungen werden nach der Suche hier angezeigt',
|
||||
confirmClear: 'Löschen bestätigen',
|
||||
confirmClearContent: 'Möchten Sie den gesamten Suchverlauf löschen?',
|
||||
historyConditionDays: '{count} Bedingungen, {days} Tage Bereich',
|
||||
justNow: 'Gerade eben',
|
||||
hoursAgo: 'Vor {count} Std.',
|
||||
daysAgo: 'Vor {count} Tag(en)',
|
||||
searchTemplates: 'Suchvorlagen',
|
||||
noTemplatesTitle: 'Keine Vorlagen',
|
||||
noTemplatesDesc: 'Keine Vorlagen entsprechen den Kriterien',
|
||||
allCategories: 'Alle',
|
||||
},
|
||||
export: {
|
||||
exportResults: 'Ergebnisse exportieren',
|
||||
exportSearchResults: 'Suchergebnisse exportieren',
|
||||
selectFormat: 'Exportformat auswählen',
|
||||
selectContent: 'Exportinhalt auswählen',
|
||||
export: 'Exportieren',
|
||||
contentDate: 'Datum',
|
||||
contentLunarDate: 'Mondkalender-Datum',
|
||||
contentWeekday: 'Wochentag',
|
||||
contentMatchedItems: 'Übereinstimmende Einträge',
|
||||
contentMatchCount: 'Übereinstimmung',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export default {
|
||||
search: 'Search',
|
||||
male: 'Male',
|
||||
female: 'Female',
|
||||
pleaseSelect: 'Please select',
|
||||
},
|
||||
nav: {
|
||||
almanac: 'Almanac',
|
||||
@@ -45,63 +46,61 @@ export default {
|
||||
monthly: 'Monthly',
|
||||
selectDate: 'Select Date',
|
||||
selectMonth: 'Select Month',
|
||||
overallFortune: 'Overall Fortune',
|
||||
career: 'Career',
|
||||
wealth: 'Wealth',
|
||||
relationship: 'Relationship',
|
||||
health: 'Health',
|
||||
luckyColor: 'Lucky Color',
|
||||
luckyNumber: 'Lucky Number',
|
||||
luckyDirection: 'Lucky Direction',
|
||||
noChart: 'Please generate a chart on the Zi Wei Dou Shu page first',
|
||||
overallScore: 'Overall Fortune: {score}pts',
|
||||
career: 'Career: ',
|
||||
wealth: 'Wealth: ',
|
||||
relationship: 'Relationship: ',
|
||||
health: 'Health: ',
|
||||
luckyColor: 'Lucky Color: ',
|
||||
luckyNumber: 'Lucky Number: ',
|
||||
luckyDirection: 'Lucky Direction: ',
|
||||
noChartHint: 'Please generate a chart on the Zi Wei Dou Shu page first',
|
||||
},
|
||||
search: {
|
||||
conditions: 'Search Conditions',
|
||||
searchConditions: 'Search Conditions',
|
||||
addCondition: 'Add Condition',
|
||||
addConditionHint: 'Please add at least one search condition',
|
||||
addConditionEmpty: 'Add search conditions',
|
||||
days: 'Search Days',
|
||||
dayUnit: 'days',
|
||||
searchResult: 'Search Results',
|
||||
searchDays: 'Search Days',
|
||||
daysUnit: '{count} days',
|
||||
searchResults: 'Search Results',
|
||||
resultCount: '{count} results',
|
||||
matchCount: 'Match: {count}',
|
||||
matchCount: 'Match',
|
||||
sort: 'Sort',
|
||||
sortByDate: 'Date',
|
||||
sortByMatch: 'Match',
|
||||
typeSuitable: 'Suitable',
|
||||
typeUnsuitable: 'Unsuitable',
|
||||
suitable: 'Suitable',
|
||||
unsuitable: 'Unsuitable',
|
||||
operatorAnd: 'AND',
|
||||
operatorOr: 'OR',
|
||||
excludeCondition: 'Exclude Condition',
|
||||
items: 'Items',
|
||||
operator: 'Logical Operator',
|
||||
},
|
||||
template: {
|
||||
title: 'Search Templates',
|
||||
searchPlaceholder: 'Search templates',
|
||||
empty: 'No Templates',
|
||||
emptyDesc: 'No templates match the criteria',
|
||||
all: 'All',
|
||||
},
|
||||
export: {
|
||||
title: 'Export Search Results',
|
||||
selectFormat: 'Select Export Format',
|
||||
selectContent: 'Select Export Content',
|
||||
date: 'Date',
|
||||
lunarDate: 'Lunar Date',
|
||||
weekday: 'Weekday',
|
||||
matchedItems: 'Matched Items',
|
||||
matchCount: 'Match Score',
|
||||
},
|
||||
history: {
|
||||
title: 'Search History',
|
||||
clear: 'Clear History',
|
||||
clearConfirm: 'Confirm Clear',
|
||||
clearConfirmMsg: 'Are you sure you want to clear all search history?',
|
||||
empty: 'No Search History',
|
||||
emptyDesc: 'Search conditions will appear here after searching',
|
||||
activityItems: 'Items',
|
||||
logicalOperator: 'Logical Operator',
|
||||
dateFormat: '{month}/{day}/{year}',
|
||||
searchHistory: 'Search History',
|
||||
clearHistory: 'Clear History',
|
||||
noHistoryTitle: 'No Search History',
|
||||
noHistoryDesc: 'Search conditions will appear here after searching',
|
||||
confirmClear: 'Confirm Clear',
|
||||
confirmClearContent: 'Are you sure you want to clear all search history?',
|
||||
historyConditionDays: '{count} conditions, {days} days range',
|
||||
justNow: 'Just now',
|
||||
hoursAgo: '{count}h ago',
|
||||
daysAgo: '{count}d ago',
|
||||
searchTemplates: 'Search Templates',
|
||||
noTemplatesTitle: 'No Templates',
|
||||
noTemplatesDesc: 'No templates match the criteria',
|
||||
allCategories: 'All',
|
||||
},
|
||||
export: {
|
||||
exportResults: 'Export Results',
|
||||
exportSearchResults: 'Export Search Results',
|
||||
selectFormat: 'Select Export Format',
|
||||
selectContent: 'Select Export Content',
|
||||
export: 'Export',
|
||||
contentDate: 'Date',
|
||||
contentLunarDate: 'Lunar Date',
|
||||
contentWeekday: 'Weekday',
|
||||
contentMatchedItems: 'Matched Items',
|
||||
contentMatchCount: 'Match Score',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export default {
|
||||
search: 'Buscar',
|
||||
male: 'Masculino',
|
||||
female: 'Femenino',
|
||||
pleaseSelect: 'Seleccione',
|
||||
},
|
||||
nav: {
|
||||
almanac: 'Almanaque',
|
||||
@@ -45,63 +46,61 @@ export default {
|
||||
monthly: 'Mensual',
|
||||
selectDate: 'Seleccionar fecha',
|
||||
selectMonth: 'Seleccionar mes',
|
||||
overallFortune: 'Fortuna general',
|
||||
career: 'Carrera',
|
||||
wealth: 'Riqueza',
|
||||
relationship: 'Relación',
|
||||
health: 'Salud',
|
||||
luckyColor: 'Color de la suerte',
|
||||
luckyNumber: 'Número de la suerte',
|
||||
luckyDirection: 'Dirección de la suerte',
|
||||
noChart: 'Primero genere una carta en la página de Zi Wei Dou Shu',
|
||||
overallScore: 'Fortuna general: {score}pts',
|
||||
career: 'Carrera: ',
|
||||
wealth: 'Riqueza: ',
|
||||
relationship: 'Relación: ',
|
||||
health: 'Salud: ',
|
||||
luckyColor: 'Color de la suerte: ',
|
||||
luckyNumber: 'Número de la suerte: ',
|
||||
luckyDirection: 'Dirección de la suerte: ',
|
||||
noChartHint: 'Primero genere una carta en la página de Zi Wei Dou Shu',
|
||||
},
|
||||
search: {
|
||||
conditions: 'Condiciones de búsqueda',
|
||||
searchConditions: 'Condiciones de búsqueda',
|
||||
addCondition: 'Añadir condición',
|
||||
addConditionHint: 'Añada al menos una condición de búsqueda',
|
||||
addConditionEmpty: 'Añadir condiciones de búsqueda',
|
||||
days: 'Días de búsqueda',
|
||||
dayUnit: 'días',
|
||||
searchResult: 'Resultados de búsqueda',
|
||||
searchDays: 'Días de búsqueda',
|
||||
daysUnit: '{count} días',
|
||||
searchResults: 'Resultados de búsqueda',
|
||||
resultCount: '{count} resultados',
|
||||
matchCount: 'Coincidencia: {count}',
|
||||
matchCount: 'Coincidencia',
|
||||
sort: 'Ordenar',
|
||||
sortByDate: 'Fecha',
|
||||
sortByMatch: 'Coincidencia',
|
||||
typeSuitable: 'Favorable',
|
||||
typeUnsuitable: 'Desfavorable',
|
||||
suitable: 'Favorable',
|
||||
unsuitable: 'Desfavorable',
|
||||
operatorAnd: 'Y',
|
||||
operatorOr: 'O',
|
||||
excludeCondition: 'Excluir condición',
|
||||
items: 'Elementos',
|
||||
operator: 'Operador lógico',
|
||||
},
|
||||
template: {
|
||||
title: 'Plantillas de búsqueda',
|
||||
searchPlaceholder: 'Buscar plantillas',
|
||||
empty: 'Sin plantillas',
|
||||
emptyDesc: 'No hay plantillas que coincidan con los criterios',
|
||||
all: 'Todo',
|
||||
},
|
||||
export: {
|
||||
title: 'Exportar resultados',
|
||||
selectFormat: 'Seleccionar formato de exportación',
|
||||
selectContent: 'Seleccionar contenido de exportación',
|
||||
date: 'Fecha',
|
||||
lunarDate: 'Fecha lunar',
|
||||
weekday: 'Día de la semana',
|
||||
matchedItems: 'Elementos coincidentes',
|
||||
matchCount: 'Coincidencia',
|
||||
},
|
||||
history: {
|
||||
title: 'Historial de búsqueda',
|
||||
clear: 'Borrar historial',
|
||||
clearConfirm: 'Confirmar borrado',
|
||||
clearConfirmMsg: '¿Desea borrar todo el historial de búsqueda?',
|
||||
empty: 'Sin historial',
|
||||
emptyDesc: 'Las condiciones de búsqueda aparecerán aquí después de buscar',
|
||||
activityItems: 'Elementos',
|
||||
logicalOperator: 'Operador lógico',
|
||||
dateFormat: '{day}/{month}/{year}',
|
||||
searchHistory: 'Historial de búsqueda',
|
||||
clearHistory: 'Borrar historial',
|
||||
noHistoryTitle: 'Sin historial',
|
||||
noHistoryDesc: 'Las condiciones de búsqueda aparecerán aquí después de buscar',
|
||||
confirmClear: 'Confirmar borrado',
|
||||
confirmClearContent: '¿Desea borrar todo el historial de búsqueda?',
|
||||
historyConditionDays: '{count} condiciones, {days} días de rango',
|
||||
justNow: 'Justo ahora',
|
||||
hoursAgo: 'Hace {count}h',
|
||||
daysAgo: 'Hace {count}d',
|
||||
searchTemplates: 'Plantillas de búsqueda',
|
||||
noTemplatesTitle: 'Sin plantillas',
|
||||
noTemplatesDesc: 'No hay plantillas que coincidan con los criterios',
|
||||
allCategories: 'Todo',
|
||||
},
|
||||
export: {
|
||||
exportResults: 'Exportar resultados',
|
||||
exportSearchResults: 'Exportar resultados de búsqueda',
|
||||
selectFormat: 'Seleccionar formato de exportación',
|
||||
selectContent: 'Seleccionar contenido de exportación',
|
||||
export: 'Exportar',
|
||||
contentDate: 'Fecha',
|
||||
contentLunarDate: 'Fecha lunar',
|
||||
contentWeekday: 'Día de la semana',
|
||||
contentMatchedItems: 'Elementos coincidentes',
|
||||
contentMatchCount: 'Coincidencia',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export default {
|
||||
search: 'Rechercher',
|
||||
male: 'Homme',
|
||||
female: 'Femme',
|
||||
pleaseSelect: 'Veuillez sélectionner',
|
||||
},
|
||||
nav: {
|
||||
almanac: 'Almanach',
|
||||
@@ -45,63 +46,61 @@ export default {
|
||||
monthly: 'Mensuel',
|
||||
selectDate: 'Sélectionner la date',
|
||||
selectMonth: 'Sélectionner le mois',
|
||||
overallFortune: 'Destin global',
|
||||
career: 'Carrière',
|
||||
wealth: 'Richesse',
|
||||
relationship: 'Relation',
|
||||
health: 'Santé',
|
||||
luckyColor: 'Couleur porte-bonheur',
|
||||
luckyNumber: 'Numéro chance',
|
||||
luckyDirection: 'Direction porte-bonheur',
|
||||
noChart: 'Veuillez d\'abord générer un diagramme sur la page Zi Wei Dou Shu',
|
||||
overallScore: 'Destin global : {score}pts',
|
||||
career: 'Carrière : ',
|
||||
wealth: 'Richesse : ',
|
||||
relationship: 'Relation : ',
|
||||
health: 'Santé : ',
|
||||
luckyColor: 'Couleur porte-bonheur : ',
|
||||
luckyNumber: 'Numéro chance : ',
|
||||
luckyDirection: 'Direction porte-bonheur : ',
|
||||
noChartHint: 'Veuillez d\'abord générer un diagramme sur la page Zi Wei Dou Shu',
|
||||
},
|
||||
search: {
|
||||
conditions: 'Conditions de recherche',
|
||||
searchConditions: 'Conditions de recherche',
|
||||
addCondition: 'Ajouter une condition',
|
||||
addConditionHint: 'Veuillez ajouter au moins une condition de recherche',
|
||||
addConditionEmpty: 'Ajouter des conditions de recherche',
|
||||
days: 'Jours de recherche',
|
||||
dayUnit: 'jours',
|
||||
searchResult: 'Résultats de recherche',
|
||||
searchDays: 'Jours de recherche',
|
||||
daysUnit: '{count} jours',
|
||||
searchResults: 'Résultats de recherche',
|
||||
resultCount: '{count} résultats',
|
||||
matchCount: 'Correspondance : {count}',
|
||||
matchCount: 'Correspondance',
|
||||
sort: 'Trier',
|
||||
sortByDate: 'Date',
|
||||
sortByMatch: 'Correspondance',
|
||||
typeSuitable: 'Favorable',
|
||||
typeUnsuitable: 'Défavorable',
|
||||
suitable: 'Favorable',
|
||||
unsuitable: 'Défavorable',
|
||||
operatorAnd: 'ET',
|
||||
operatorOr: 'OU',
|
||||
excludeCondition: 'Exclure la condition',
|
||||
items: 'Éléments',
|
||||
operator: 'Opérateur logique',
|
||||
},
|
||||
template: {
|
||||
title: 'Modèles de recherche',
|
||||
searchPlaceholder: 'Rechercher des modèles',
|
||||
empty: 'Aucun modèle',
|
||||
emptyDesc: 'Aucun modèle ne correspond aux critères',
|
||||
all: 'Tous',
|
||||
},
|
||||
export: {
|
||||
title: 'Exporter les résultats',
|
||||
selectFormat: 'Sélectionner le format d\'export',
|
||||
selectContent: 'Sélectionner le contenu d\'export',
|
||||
date: 'Date',
|
||||
lunarDate: 'Date lunaire',
|
||||
weekday: 'Jour de la semaine',
|
||||
matchedItems: 'Éléments correspondants',
|
||||
matchCount: 'Correspondance',
|
||||
},
|
||||
history: {
|
||||
title: 'Historique de recherche',
|
||||
clear: 'Effacer l\'historique',
|
||||
clearConfirm: 'Confirmer la suppression',
|
||||
clearConfirmMsg: 'Voulez-vous effacer tout l\'historique de recherche ?',
|
||||
empty: 'Aucun historique',
|
||||
emptyDesc: 'Les conditions de recherche apparaîtront ici après la recherche',
|
||||
activityItems: 'Éléments',
|
||||
logicalOperator: 'Opérateur logique',
|
||||
dateFormat: '{day}/{month}/{year}',
|
||||
searchHistory: 'Historique de recherche',
|
||||
clearHistory: 'Effacer l\'historique',
|
||||
noHistoryTitle: 'Aucun historique',
|
||||
noHistoryDesc: 'Les conditions de recherche apparaîtront ici après la recherche',
|
||||
confirmClear: 'Confirmer la suppression',
|
||||
confirmClearContent: 'Voulez-vous effacer tout l\'historique de recherche ?',
|
||||
historyConditionDays: '{count} conditions, {days} jours de plage',
|
||||
justNow: 'À l\'instant',
|
||||
hoursAgo: 'Il y a {count}h',
|
||||
daysAgo: 'Il y a {count}j',
|
||||
searchTemplates: 'Modèles de recherche',
|
||||
noTemplatesTitle: 'Aucun modèle',
|
||||
noTemplatesDesc: 'Aucun modèle ne correspond aux critères',
|
||||
allCategories: 'Tous',
|
||||
},
|
||||
export: {
|
||||
exportResults: 'Exporter les résultats',
|
||||
exportSearchResults: 'Exporter les résultats de recherche',
|
||||
selectFormat: 'Sélectionner le format d\'export',
|
||||
selectContent: 'Sélectionner le contenu d\'export',
|
||||
export: 'Exporter',
|
||||
contentDate: 'Date',
|
||||
contentLunarDate: 'Date lunaire',
|
||||
contentWeekday: 'Jour de la semaine',
|
||||
contentMatchedItems: 'Éléments correspondants',
|
||||
contentMatchCount: 'Correspondance',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export default {
|
||||
search: '検索',
|
||||
male: '男性',
|
||||
female: '女性',
|
||||
pleaseSelect: '選択してください',
|
||||
},
|
||||
nav: {
|
||||
almanac: '黄暦',
|
||||
@@ -45,63 +46,61 @@ export default {
|
||||
monthly: '月運',
|
||||
selectDate: '日付を選択',
|
||||
selectMonth: '月を選択',
|
||||
overallFortune: '総合運勢',
|
||||
career: '事業',
|
||||
wealth: '財運',
|
||||
relationship: '恋愛',
|
||||
health: '健康',
|
||||
luckyColor: 'ラッキーカラー',
|
||||
luckyNumber: 'ラッキーナンバー',
|
||||
luckyDirection: '吉方位',
|
||||
noChart: '先に紫微斗数ページで排盤を完了してください',
|
||||
overallScore: '総合運勢:{score}点',
|
||||
career: '事業:',
|
||||
wealth: '財運:',
|
||||
relationship: '恋愛:',
|
||||
health: '健康:',
|
||||
luckyColor: 'ラッキーカラー:',
|
||||
luckyNumber: 'ラッキーナンバー:',
|
||||
luckyDirection: '吉方位:',
|
||||
noChartHint: '先に紫微斗数ページで排盤を完了してください',
|
||||
},
|
||||
search: {
|
||||
conditions: '検索条件',
|
||||
searchConditions: '検索条件',
|
||||
addCondition: '条件を追加',
|
||||
addConditionHint: '検索条件を少なくとも1つ追加してください',
|
||||
addConditionEmpty: '検索条件を追加',
|
||||
days: '検索日数',
|
||||
dayUnit: '日',
|
||||
searchResult: '検索結果',
|
||||
searchDays: '検索日数',
|
||||
daysUnit: '{count}日',
|
||||
searchResults: '検索結果',
|
||||
resultCount: '{count}件の結果',
|
||||
matchCount: '一致度: {count}',
|
||||
matchCount: '一致度',
|
||||
sort: '並び替え',
|
||||
sortByDate: '日付',
|
||||
sortByMatch: '一致度',
|
||||
typeSuitable: '宜',
|
||||
typeUnsuitable: '忌',
|
||||
suitable: '宜',
|
||||
unsuitable: '忌',
|
||||
operatorAnd: 'AND',
|
||||
operatorOr: 'OR',
|
||||
excludeCondition: '除外条件',
|
||||
items: '事項',
|
||||
operator: '論理演算子',
|
||||
},
|
||||
template: {
|
||||
title: '検索テンプレート',
|
||||
searchPlaceholder: 'テンプレートを検索',
|
||||
empty: 'テンプレートなし',
|
||||
emptyDesc: '条件に一致するテンプレートがありません',
|
||||
all: 'すべて',
|
||||
},
|
||||
export: {
|
||||
title: '検索結果をエクスポート',
|
||||
selectFormat: 'エクスポート形式を選択',
|
||||
selectContent: 'エクスポート内容を選択',
|
||||
date: '日付',
|
||||
lunarDate: '旧暦日付',
|
||||
weekday: '曜日',
|
||||
matchedItems: '一致事項',
|
||||
matchCount: '一致度',
|
||||
},
|
||||
history: {
|
||||
title: '検索履歴',
|
||||
clear: '履歴をクリア',
|
||||
clearConfirm: 'クリアの確認',
|
||||
clearConfirmMsg: 'すべての検索履歴をクリアしますか?',
|
||||
empty: '検索履歴なし',
|
||||
emptyDesc: '検索実行後、条件がここに表示されます',
|
||||
activityItems: '事項',
|
||||
logicalOperator: '論理演算子',
|
||||
dateFormat: '{year}年{month}月{day}日',
|
||||
searchHistory: '検索履歴',
|
||||
clearHistory: '履歴をクリア',
|
||||
noHistoryTitle: '検索履歴なし',
|
||||
noHistoryDesc: '検索実行後、条件がここに表示されます',
|
||||
confirmClear: 'クリアの確認',
|
||||
confirmClearContent: 'すべての検索履歴をクリアしますか?',
|
||||
historyConditionDays: '{count}つの条件、{days}日範囲',
|
||||
justNow: 'たった今',
|
||||
hoursAgo: '{count}時間前',
|
||||
daysAgo: '{count}日前',
|
||||
searchTemplates: '検索テンプレート',
|
||||
noTemplatesTitle: 'テンプレートなし',
|
||||
noTemplatesDesc: '条件に一致するテンプレートがありません',
|
||||
allCategories: 'すべて',
|
||||
},
|
||||
export: {
|
||||
exportResults: '結果をエクスポート',
|
||||
exportSearchResults: '検索結果をエクスポート',
|
||||
selectFormat: 'エクスポート形式を選択',
|
||||
selectContent: 'エクスポート内容を選択',
|
||||
export: 'エクスポート',
|
||||
contentDate: '日付',
|
||||
contentLunarDate: '旧暦日付',
|
||||
contentWeekday: '曜日',
|
||||
contentMatchedItems: '一致事項',
|
||||
contentMatchCount: '一致度',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export default {
|
||||
search: '검색',
|
||||
male: '남성',
|
||||
female: '여성',
|
||||
pleaseSelect: '선택하세요',
|
||||
},
|
||||
nav: {
|
||||
almanac: '황력',
|
||||
@@ -45,63 +46,61 @@ export default {
|
||||
monthly: '월운',
|
||||
selectDate: '날짜 선택',
|
||||
selectMonth: '월 선택',
|
||||
overallFortune: '종합 운세',
|
||||
career: '사업',
|
||||
wealth: '재운',
|
||||
relationship: '연애',
|
||||
health: '건강',
|
||||
luckyColor: '행운의 색',
|
||||
luckyNumber: '행운의 숫자',
|
||||
luckyDirection: '행운의 방위',
|
||||
noChart: '먼저 자미두수 페이지에서 명반을 생성해 주세요',
|
||||
overallScore: '종합 운세: {score}점',
|
||||
career: '사업: ',
|
||||
wealth: '재운: ',
|
||||
relationship: '연애: ',
|
||||
health: '건강: ',
|
||||
luckyColor: '행운의 색: ',
|
||||
luckyNumber: '행운의 숫자: ',
|
||||
luckyDirection: '행운의 방위: ',
|
||||
noChartHint: '먼저 자미두수 페이지에서 명반을 생성해 주세요',
|
||||
},
|
||||
search: {
|
||||
conditions: '검색 조건',
|
||||
searchConditions: '검색 조건',
|
||||
addCondition: '조건 추가',
|
||||
addConditionHint: '검색 조건을 최소 하나 추가해 주세요',
|
||||
addConditionEmpty: '검색 조건 추가',
|
||||
days: '검색 일수',
|
||||
dayUnit: '일',
|
||||
searchResult: '검색 결과',
|
||||
searchDays: '검색 일수',
|
||||
daysUnit: '{count}일',
|
||||
searchResults: '검색 결과',
|
||||
resultCount: '{count}개 결과',
|
||||
matchCount: '일치도: {count}',
|
||||
matchCount: '일치도',
|
||||
sort: '정렬',
|
||||
sortByDate: '날짜',
|
||||
sortByMatch: '일치도',
|
||||
typeSuitable: '宜',
|
||||
typeUnsuitable: '忌',
|
||||
suitable: '宜',
|
||||
unsuitable: '忌',
|
||||
operatorAnd: 'AND',
|
||||
operatorOr: 'OR',
|
||||
excludeCondition: '제외 조건',
|
||||
items: '항목',
|
||||
operator: '논리 연산자',
|
||||
},
|
||||
template: {
|
||||
title: '검색 템플릿',
|
||||
searchPlaceholder: '템플릿 검색',
|
||||
empty: '템플릿 없음',
|
||||
emptyDesc: '조건에 맞는 템플릿이 없습니다',
|
||||
all: '전체',
|
||||
},
|
||||
export: {
|
||||
title: '검색 결과 내보내기',
|
||||
selectFormat: '내보내기 형식 선택',
|
||||
selectContent: '내보내기 내용 선택',
|
||||
date: '날짜',
|
||||
lunarDate: '음력 날짜',
|
||||
weekday: '요일',
|
||||
matchedItems: '일치 항목',
|
||||
matchCount: '일치도',
|
||||
},
|
||||
history: {
|
||||
title: '검색 기록',
|
||||
clear: '기록 삭제',
|
||||
clearConfirm: '삭제 확인',
|
||||
clearConfirmMsg: '모든 검색 기록을 삭제하시겠습니까?',
|
||||
empty: '검색 기록 없음',
|
||||
emptyDesc: '검색 실행 후 조건이 여기에 표시됩니다',
|
||||
activityItems: '항목',
|
||||
logicalOperator: '논리 연산자',
|
||||
dateFormat: '{year}년 {month}월 {day}일',
|
||||
searchHistory: '검색 기록',
|
||||
clearHistory: '기록 삭제',
|
||||
noHistoryTitle: '검색 기록 없음',
|
||||
noHistoryDesc: '검색 실행 후 조건이 여기에 표시됩니다',
|
||||
confirmClear: '삭제 확인',
|
||||
confirmClearContent: '모든 검색 기록을 삭제하시겠습니까?',
|
||||
historyConditionDays: '{count}개 조건, {days}일 범위',
|
||||
justNow: '방금',
|
||||
hoursAgo: '{count}시간 전',
|
||||
daysAgo: '{count}일 전',
|
||||
searchTemplates: '검색 템플릿',
|
||||
noTemplatesTitle: '템플릿 없음',
|
||||
noTemplatesDesc: '조건에 맞는 템플릿이 없습니다',
|
||||
allCategories: '전체',
|
||||
},
|
||||
export: {
|
||||
exportResults: '결과 내보내기',
|
||||
exportSearchResults: '검색 결과 내보내기',
|
||||
selectFormat: '내보내기 형식 선택',
|
||||
selectContent: '내보내기 내용 선택',
|
||||
export: '내보내기',
|
||||
contentDate: '날짜',
|
||||
contentLunarDate: '음력 날짜',
|
||||
contentWeekday: '요일',
|
||||
contentMatchedItems: '일치 항목',
|
||||
contentMatchCount: '일치도',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export default {
|
||||
search: 'Pesquisar',
|
||||
male: 'Masculino',
|
||||
female: 'Feminino',
|
||||
pleaseSelect: 'Selecione',
|
||||
},
|
||||
nav: {
|
||||
almanac: 'Almanaque',
|
||||
@@ -45,63 +46,61 @@ export default {
|
||||
monthly: 'Mensal',
|
||||
selectDate: 'Selecionar data',
|
||||
selectMonth: 'Selecionar mês',
|
||||
overallFortune: 'Fortuna geral',
|
||||
career: 'Carreira',
|
||||
wealth: 'Riqueza',
|
||||
relationship: 'Relacionamento',
|
||||
health: 'Saúde',
|
||||
luckyColor: 'Cor da sorte',
|
||||
luckyNumber: 'Número da sorte',
|
||||
luckyDirection: 'Direção da sorte',
|
||||
noChart: 'Primeiro gere uma carta na página Zi Wei Dou Shu',
|
||||
overallScore: 'Fortuna geral: {score}pts',
|
||||
career: 'Carreira: ',
|
||||
wealth: 'Riqueza: ',
|
||||
relationship: 'Relacionamento: ',
|
||||
health: 'Saúde: ',
|
||||
luckyColor: 'Cor da sorte: ',
|
||||
luckyNumber: 'Número da sorte: ',
|
||||
luckyDirection: 'Direção da sorte: ',
|
||||
noChartHint: 'Primeiro gere uma carta na página Zi Wei Dou Shu',
|
||||
},
|
||||
search: {
|
||||
conditions: 'Condições de pesquisa',
|
||||
searchConditions: 'Condições de pesquisa',
|
||||
addCondition: 'Adicionar condição',
|
||||
addConditionHint: 'Adicione pelo menos uma condição de pesquisa',
|
||||
addConditionEmpty: 'Adicionar condições de pesquisa',
|
||||
days: 'Dias de pesquisa',
|
||||
dayUnit: 'dias',
|
||||
searchResult: 'Resultados da pesquisa',
|
||||
searchDays: 'Dias de pesquisa',
|
||||
daysUnit: '{count} dias',
|
||||
searchResults: 'Resultados da pesquisa',
|
||||
resultCount: '{count} resultados',
|
||||
matchCount: 'Correspondência: {count}',
|
||||
matchCount: 'Correspondência',
|
||||
sort: 'Ordenar',
|
||||
sortByDate: 'Data',
|
||||
sortByMatch: 'Correspondência',
|
||||
typeSuitable: 'Favorável',
|
||||
typeUnsuitable: 'Desfavorável',
|
||||
suitable: 'Favorável',
|
||||
unsuitable: 'Desfavorável',
|
||||
operatorAnd: 'E',
|
||||
operatorOr: 'OU',
|
||||
excludeCondition: 'Excluir condição',
|
||||
items: 'Itens',
|
||||
operator: 'Operador lógico',
|
||||
},
|
||||
template: {
|
||||
title: 'Modelos de pesquisa',
|
||||
searchPlaceholder: 'Pesquisar modelos',
|
||||
empty: 'Sem modelos',
|
||||
emptyDesc: 'Nenhum modelo corresponde aos critérios',
|
||||
all: 'Todos',
|
||||
},
|
||||
export: {
|
||||
title: 'Exportar resultados',
|
||||
selectFormat: 'Selecionar formato de exportação',
|
||||
selectContent: 'Selecionar conteúdo de exportação',
|
||||
date: 'Data',
|
||||
lunarDate: 'Data lunar',
|
||||
weekday: 'Dia da semana',
|
||||
matchedItems: 'Itens correspondentes',
|
||||
matchCount: 'Correspondência',
|
||||
},
|
||||
history: {
|
||||
title: 'Histórico de pesquisa',
|
||||
clear: 'Limpar histórico',
|
||||
clearConfirm: 'Confirmar limpeza',
|
||||
clearConfirmMsg: 'Deseja limpar todo o histórico de pesquisa?',
|
||||
empty: 'Sem histórico',
|
||||
emptyDesc: 'As condições de pesquisa aparecerão aqui após a pesquisa',
|
||||
activityItems: 'Itens',
|
||||
logicalOperator: 'Operador lógico',
|
||||
dateFormat: '{day}/{month}/{year}',
|
||||
searchHistory: 'Histórico de pesquisa',
|
||||
clearHistory: 'Limpar histórico',
|
||||
noHistoryTitle: 'Sem histórico',
|
||||
noHistoryDesc: 'As condições de pesquisa aparecerão aqui após a pesquisa',
|
||||
confirmClear: 'Confirmar limpeza',
|
||||
confirmClearContent: 'Deseja limpar todo o histórico de pesquisa?',
|
||||
historyConditionDays: '{count} condições, {days} dias de intervalo',
|
||||
justNow: 'Agora mesmo',
|
||||
hoursAgo: 'Há {count}h',
|
||||
daysAgo: 'Há {count}d',
|
||||
searchTemplates: 'Modelos de pesquisa',
|
||||
noTemplatesTitle: 'Sem modelos',
|
||||
noTemplatesDesc: 'Nenhum modelo corresponde aos critérios',
|
||||
allCategories: 'Todos',
|
||||
},
|
||||
export: {
|
||||
exportResults: 'Exportar resultados',
|
||||
exportSearchResults: 'Exportar resultados de pesquisa',
|
||||
selectFormat: 'Selecionar formato de exportação',
|
||||
selectContent: 'Selecionar conteúdo de exportação',
|
||||
export: 'Exportar',
|
||||
contentDate: 'Data',
|
||||
contentLunarDate: 'Data lunar',
|
||||
contentWeekday: 'Dia da semana',
|
||||
contentMatchedItems: 'Itens correspondentes',
|
||||
contentMatchCount: 'Correspondência',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export default {
|
||||
search: '搜索',
|
||||
male: '男',
|
||||
female: '女',
|
||||
pleaseSelect: '请选择',
|
||||
},
|
||||
nav: {
|
||||
almanac: '黄历',
|
||||
@@ -45,63 +46,61 @@ export default {
|
||||
monthly: '月运',
|
||||
selectDate: '选择日期',
|
||||
selectMonth: '选择月份',
|
||||
overallFortune: '综合运势',
|
||||
career: '事业',
|
||||
wealth: '财运',
|
||||
relationship: '感情',
|
||||
health: '健康',
|
||||
luckyColor: '幸运色',
|
||||
luckyNumber: '幸运数字',
|
||||
luckyDirection: '幸运方位',
|
||||
noChart: '请先在紫微斗数页面完成排盘',
|
||||
overallScore: '综合运势:{score}分',
|
||||
career: '事业:',
|
||||
wealth: '财运:',
|
||||
relationship: '感情:',
|
||||
health: '健康:',
|
||||
luckyColor: '幸运色:',
|
||||
luckyNumber: '幸运数字:',
|
||||
luckyDirection: '幸运方位:',
|
||||
noChartHint: '请先在紫微斗数页面完成排盘',
|
||||
},
|
||||
search: {
|
||||
conditions: '搜索条件',
|
||||
searchConditions: '搜索条件',
|
||||
addCondition: '添加条件',
|
||||
addConditionHint: '请至少添加一个搜索条件',
|
||||
addConditionEmpty: '请添加搜索条件',
|
||||
days: '搜索天数',
|
||||
dayUnit: '天',
|
||||
searchResult: '搜索结果',
|
||||
searchDays: '搜索天数',
|
||||
daysUnit: '{count}天',
|
||||
searchResults: '搜索结果',
|
||||
resultCount: '共{count}条结果',
|
||||
matchCount: '匹配度: {count}',
|
||||
matchCount: '匹配度',
|
||||
sort: '排序',
|
||||
sortByDate: '日期',
|
||||
sortByMatch: '匹配度',
|
||||
typeSuitable: '宜',
|
||||
typeUnsuitable: '忌',
|
||||
suitable: '宜',
|
||||
unsuitable: '忌',
|
||||
operatorAnd: '与(AND)',
|
||||
operatorOr: '或(OR)',
|
||||
excludeCondition: '排除条件',
|
||||
items: '事项',
|
||||
operator: '逻辑运算符',
|
||||
},
|
||||
template: {
|
||||
title: '搜索模板',
|
||||
searchPlaceholder: '搜索模板',
|
||||
empty: '暂无模板',
|
||||
emptyDesc: '没有找到符合条件的模板',
|
||||
all: '全部',
|
||||
},
|
||||
export: {
|
||||
title: '导出搜索结果',
|
||||
selectFormat: '选择导出格式',
|
||||
selectContent: '选择导出内容',
|
||||
date: '日期',
|
||||
lunarDate: '农历日期',
|
||||
weekday: '星期',
|
||||
matchedItems: '匹配事项',
|
||||
matchCount: '匹配度',
|
||||
},
|
||||
history: {
|
||||
title: '搜索历史',
|
||||
clear: '清除历史',
|
||||
clearConfirm: '确认清除',
|
||||
clearConfirmMsg: '确定要清除所有搜索历史吗?',
|
||||
empty: '暂无搜索历史',
|
||||
emptyDesc: '执行搜索后,搜索条件将显示在这里',
|
||||
activityItems: '事项',
|
||||
logicalOperator: '逻辑运算符',
|
||||
dateFormat: '{year}年{month}月{day}日',
|
||||
searchHistory: '搜索历史',
|
||||
clearHistory: '清除历史',
|
||||
noHistoryTitle: '暂无搜索历史',
|
||||
noHistoryDesc: '执行搜索后,搜索条件将显示在这里',
|
||||
confirmClear: '确认清除',
|
||||
confirmClearContent: '确定要清除所有搜索历史吗?',
|
||||
historyConditionDays: '{count}个条件,{days}天范围',
|
||||
justNow: '刚刚',
|
||||
hoursAgo: '{count}小时前',
|
||||
daysAgo: '{count}天前',
|
||||
searchTemplates: '搜索模板',
|
||||
noTemplatesTitle: '暂无模板',
|
||||
noTemplatesDesc: '没有找到符合条件的模板',
|
||||
allCategories: '全部',
|
||||
},
|
||||
export: {
|
||||
exportResults: '导出结果',
|
||||
exportSearchResults: '导出搜索结果',
|
||||
selectFormat: '选择导出格式',
|
||||
selectContent: '选择导出内容',
|
||||
export: '导出',
|
||||
contentDate: '日期',
|
||||
contentLunarDate: '农历日期',
|
||||
contentWeekday: '星期',
|
||||
contentMatchedItems: '匹配事项',
|
||||
contentMatchCount: '匹配度',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export default {
|
||||
search: '搜尋',
|
||||
male: '男',
|
||||
female: '女',
|
||||
pleaseSelect: '請選擇',
|
||||
},
|
||||
nav: {
|
||||
almanac: '黃曆',
|
||||
@@ -45,63 +46,61 @@ export default {
|
||||
monthly: '月運',
|
||||
selectDate: '選擇日期',
|
||||
selectMonth: '選擇月份',
|
||||
overallFortune: '綜合運勢',
|
||||
career: '事業',
|
||||
wealth: '財運',
|
||||
relationship: '感情',
|
||||
health: '健康',
|
||||
luckyColor: '幸運色',
|
||||
luckyNumber: '幸運數字',
|
||||
luckyDirection: '幸運方位',
|
||||
noChart: '請先在紫微斗數頁面完成排盤',
|
||||
overallScore: '綜合運勢:{score}分',
|
||||
career: '事業:',
|
||||
wealth: '財運:',
|
||||
relationship: '感情:',
|
||||
health: '健康:',
|
||||
luckyColor: '幸運色:',
|
||||
luckyNumber: '幸運數字:',
|
||||
luckyDirection: '幸運方位:',
|
||||
noChartHint: '請先在紫微斗數頁面完成排盤',
|
||||
},
|
||||
search: {
|
||||
conditions: '搜尋條件',
|
||||
searchConditions: '搜尋條件',
|
||||
addCondition: '新增條件',
|
||||
addConditionHint: '請至少新增一個搜尋條件',
|
||||
addConditionEmpty: '請新增搜尋條件',
|
||||
days: '搜尋天數',
|
||||
dayUnit: '天',
|
||||
searchResult: '搜尋結果',
|
||||
searchDays: '搜尋天數',
|
||||
daysUnit: '{count}天',
|
||||
searchResults: '搜尋結果',
|
||||
resultCount: '共{count}條結果',
|
||||
matchCount: '匹配度: {count}',
|
||||
matchCount: '匹配度',
|
||||
sort: '排序',
|
||||
sortByDate: '日期',
|
||||
sortByMatch: '匹配度',
|
||||
typeSuitable: '宜',
|
||||
typeUnsuitable: '忌',
|
||||
suitable: '宜',
|
||||
unsuitable: '忌',
|
||||
operatorAnd: '與(AND)',
|
||||
operatorOr: '或(OR)',
|
||||
excludeCondition: '排除條件',
|
||||
items: '事項',
|
||||
operator: '邏輯運算子',
|
||||
},
|
||||
template: {
|
||||
title: '搜尋模板',
|
||||
searchPlaceholder: '搜尋模板',
|
||||
empty: '暫無模板',
|
||||
emptyDesc: '沒有找到符合條件的模板',
|
||||
all: '全部',
|
||||
},
|
||||
export: {
|
||||
title: '匯出搜尋結果',
|
||||
selectFormat: '選擇匯出格式',
|
||||
selectContent: '選擇匯出內容',
|
||||
date: '日期',
|
||||
lunarDate: '農曆日期',
|
||||
weekday: '星期',
|
||||
matchedItems: '匹配事項',
|
||||
matchCount: '匹配度',
|
||||
},
|
||||
history: {
|
||||
title: '搜尋歷史',
|
||||
clear: '清除歷史',
|
||||
clearConfirm: '確認清除',
|
||||
clearConfirmMsg: '確定要清除所有搜尋歷史嗎?',
|
||||
empty: '暫無搜尋歷史',
|
||||
emptyDesc: '執行搜尋後,搜尋條件將顯示在這裡',
|
||||
activityItems: '事項',
|
||||
logicalOperator: '邏輯運算子',
|
||||
dateFormat: '{year}年{month}月{day}日',
|
||||
searchHistory: '搜尋歷史',
|
||||
clearHistory: '清除歷史',
|
||||
noHistoryTitle: '暫無搜尋歷史',
|
||||
noHistoryDesc: '執行搜尋後,搜尋條件將顯示在這裡',
|
||||
confirmClear: '確認清除',
|
||||
confirmClearContent: '確定要清除所有搜尋歷史嗎?',
|
||||
historyConditionDays: '{count}個條件,{days}天範圍',
|
||||
justNow: '剛剛',
|
||||
hoursAgo: '{count}小時前',
|
||||
daysAgo: '{count}天前',
|
||||
searchTemplates: '搜尋模板',
|
||||
noTemplatesTitle: '暫無模板',
|
||||
noTemplatesDesc: '沒有找到符合條件的模板',
|
||||
allCategories: '全部',
|
||||
},
|
||||
export: {
|
||||
exportResults: '匯出結果',
|
||||
exportSearchResults: '匯出搜尋結果',
|
||||
selectFormat: '選擇匯出格式',
|
||||
selectContent: '選擇匯出內容',
|
||||
export: '匯出',
|
||||
contentDate: '日期',
|
||||
contentLunarDate: '農曆日期',
|
||||
contentWeekday: '星期',
|
||||
contentMatchedItems: '匹配事項',
|
||||
contentMatchCount: '匹配度',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -32,17 +32,6 @@ export interface SavedSearchCondition {
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export interface SearchService {
|
||||
search(request: SearchRequest): Promise<SearchResult[]>
|
||||
saveSearchCondition(name: string, condition: SearchRequest): void
|
||||
getSavedSearchConditions(): SavedSearchCondition[]
|
||||
deleteSearchCondition(id: string): void
|
||||
loadSearchCondition(condition: SearchRequest): void
|
||||
saveSearchHistory(request: SearchRequest): void
|
||||
getSearchHistory(): any[]
|
||||
clearSearchHistory(): void
|
||||
}
|
||||
|
||||
export interface SearchHistoryItem {
|
||||
id: string
|
||||
condition: SearchRequest
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
import type { ApiError, ErrorType } from './httpClient'
|
||||
export enum AppErrorType {
|
||||
CALCULATION_ERROR = 'CALCULATION_ERROR',
|
||||
DATA_ERROR = 'DATA_ERROR',
|
||||
STORAGE_ERROR = 'STORAGE_ERROR',
|
||||
VALIDATION_ERROR = 'VALIDATION_ERROR',
|
||||
UNKNOWN_ERROR = 'UNKNOWN_ERROR'
|
||||
}
|
||||
|
||||
export interface AppError {
|
||||
type: AppErrorType
|
||||
code: number
|
||||
message: string
|
||||
detail?: any
|
||||
}
|
||||
|
||||
class ErrorHandler {
|
||||
private errorMessages: Record<ErrorType, string> = {
|
||||
NETWORK_ERROR: '网络连接失败,请检查网络设置',
|
||||
TIMEOUT_ERROR: '请求超时,请稍后重试',
|
||||
BUSINESS_ERROR: '操作失败,请稍后重试',
|
||||
AUTH_ERROR: '认证失败,请重新登录',
|
||||
TOKEN_EXPIRED: '登录已过期,请重新登录',
|
||||
private errorMessages: Record<AppErrorType, string> = {
|
||||
CALCULATION_ERROR: '计算出错,请稍后重试',
|
||||
DATA_ERROR: '数据异常,请稍后重试',
|
||||
STORAGE_ERROR: '存储操作失败,请检查设备空间',
|
||||
VALIDATION_ERROR: '输入数据有误,请检查后重试',
|
||||
UNKNOWN_ERROR: '未知错误,请稍后重试'
|
||||
}
|
||||
|
||||
handleError(error: ApiError): void {
|
||||
this.logError(error)
|
||||
this.showErrorMessage(error.message)
|
||||
handleError(error: AppError | Error): void {
|
||||
const appError = this.toAppError(error)
|
||||
this.logError(appError)
|
||||
this.showErrorMessage(appError.message)
|
||||
}
|
||||
|
||||
showErrorMessage(message: string): void {
|
||||
@@ -31,8 +44,8 @@ class ErrorHandler {
|
||||
})
|
||||
}
|
||||
|
||||
logError(error: ApiError): void {
|
||||
console.error('[API Error]', {
|
||||
logError(error: AppError): void {
|
||||
console.error('[App Error]', {
|
||||
type: error.type,
|
||||
code: error.code,
|
||||
message: error.message,
|
||||
@@ -40,20 +53,23 @@ class ErrorHandler {
|
||||
})
|
||||
}
|
||||
|
||||
getErrorMessage(error: ApiError): string {
|
||||
getErrorMessage(error: AppError): string {
|
||||
return this.errorMessages[error.type] || error.message
|
||||
}
|
||||
|
||||
isAuthError(error: ApiError): boolean {
|
||||
return error.type === 'AUTH_ERROR' || error.type === 'TOKEN_EXPIRED'
|
||||
createError(type: AppErrorType, message: string, code: number = 0, detail?: any): AppError {
|
||||
return { type, code, message, detail }
|
||||
}
|
||||
|
||||
isNetworkError(error: ApiError): boolean {
|
||||
return error.type === 'NETWORK_ERROR' || error.type === 'TIMEOUT_ERROR'
|
||||
}
|
||||
|
||||
isBusinessError(error: ApiError): boolean {
|
||||
return error.type === 'BUSINESS_ERROR'
|
||||
private toAppError(error: AppError | Error): AppError {
|
||||
if ('type' in error && 'code' in error) {
|
||||
return error as AppError
|
||||
}
|
||||
return {
|
||||
type: AppErrorType.UNKNOWN_ERROR,
|
||||
code: 0,
|
||||
message: error.message || '未知错误'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user