feat: search translate history (#9342)

* feat(翻译历史): 添加搜索翻译历史UI

在翻译历史页面添加搜索框

* feat(翻译历史): 优化搜索功能并添加延迟渲染

- 将搜索逻辑提取为独立函数并使用useDeferredValue优化性能
- 重构类型命名和状态管理
- 格式化日期显示并移入memo计算

* feat(i18n): 为翻译历史添加搜索框占位文本

* refactor(translate): 移除未使用的InputRef引用和inputRef变量
This commit is contained in:
Phantom 2025-08-21 12:48:27 +08:00 committed by kangfenmao
parent 1ac32bad14
commit fe097a937c
10 changed files with 91 additions and 9 deletions

View File

@ -3723,6 +3723,9 @@
"error": {
"save": "Failed to save translation history"
},
"search": {
"placeholder": "Search translation history"
},
"title": "Translation History"
},
"input": {

View File

@ -3723,6 +3723,9 @@
"error": {
"save": "保存翻訳履歴に失敗しました"
},
"search": {
"placeholder": "翻訳履歴を検索する"
},
"title": "翻訳履歴"
},
"input": {

View File

@ -3723,6 +3723,9 @@
"error": {
"save": "Не удалось сохранить историю переводов"
},
"search": {
"placeholder": "Поиск истории переводов"
},
"title": "История переводов"
},
"input": {

View File

@ -3723,6 +3723,9 @@
"error": {
"save": "保存翻译历史失败"
},
"search": {
"placeholder": "搜索翻译历史"
},
"title": "翻译历史"
},
"input": {

View File

@ -3723,6 +3723,9 @@
"error": {
"save": "保存翻譯歷史失敗"
},
"search": {
"placeholder": "搜索翻譯歷史"
},
"title": "翻譯歷史"
},
"input": {

View File

@ -3723,6 +3723,9 @@
"error": {
"save": "Αποτυχία αποθήκευσης του ιστορικού μεταφράσεων"
},
"search": {
"placeholder": "Αναζήτηση ιστορικού μεταφράσεων"
},
"title": "Ιστορικό μετάφρασης"
},
"input": {

View File

@ -3723,6 +3723,9 @@
"error": {
"save": "Error al guardar el historial de traducciones"
},
"search": {
"placeholder": "Historial de búsqueda de traducción"
},
"title": "Historial de traducciones"
},
"input": {

View File

@ -3723,6 +3723,9 @@
"error": {
"save": "Échec de la sauvegarde de l'historique des traductions"
},
"search": {
"placeholder": "Rechercher l'historique des traductions"
},
"title": "Historique des traductions"
},
"input": {

View File

@ -3723,6 +3723,9 @@
"error": {
"save": "Falha ao guardar o histórico de traduções"
},
"search": {
"placeholder": "Pesquisar histórico de tradução"
},
"title": "Histórico de Tradução"
},
"input": {

View File

@ -1,28 +1,32 @@
import { DeleteOutlined } from '@ant-design/icons'
import { HStack } from '@renderer/components/Layout'
import { DynamicVirtualList } from '@renderer/components/VirtualList'
import db from '@renderer/databases'
import useTranslate from '@renderer/hooks/useTranslate'
import { clearHistory, deleteHistory } from '@renderer/services/TranslateService'
import { TranslateHistory, TranslateLanguage } from '@renderer/types'
import { Button, Drawer, Dropdown, Empty, Flex, Popconfirm } from 'antd'
import { Button, Drawer, Dropdown, Empty, Flex, Input, Popconfirm } from 'antd'
import dayjs from 'dayjs'
import { useLiveQuery } from 'dexie-react-hooks'
import { isEmpty } from 'lodash'
import { FC, useMemo } from 'react'
import { SearchIcon } from 'lucide-react'
import { FC, useCallback, useDeferredValue, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
type DisplayedTranslateHistory = TranslateHistory & {
type DisplayedTranslateHistoryItem = TranslateHistory & {
_sourceLanguage: TranslateLanguage
_targetLanguage: TranslateLanguage
}
type TranslateHistoryProps = {
isOpen: boolean
onHistoryItemClick: (history: DisplayedTranslateHistory) => void
onHistoryItemClick: (history: DisplayedTranslateHistoryItem) => void
onClose: () => void
}
// const logger = loggerService.withContext('TranslateHistory')
// px
const ITEM_HEIGHT = 140
@ -30,17 +34,35 @@ const TranslateHistoryList: FC<TranslateHistoryProps> = ({ isOpen, onHistoryItem
const { t } = useTranslation()
const { getLanguageByLangcode } = useTranslate()
const _translateHistory = useLiveQuery(() => db.translate_history.orderBy('createdAt').reverse().toArray(), [])
const [search, setSearch] = useState('')
const [displayedHistory, setDisplayedHistory] = useState<DisplayedTranslateHistoryItem[]>([])
const translateHistory: DisplayedTranslateHistory[] = useMemo(() => {
const translateHistory: DisplayedTranslateHistoryItem[] = useMemo(() => {
if (!_translateHistory) return []
return _translateHistory.map((item) => ({
...item,
_sourceLanguage: getLanguageByLangcode(item.sourceLanguage),
_targetLanguage: getLanguageByLangcode(item.targetLanguage)
_targetLanguage: getLanguageByLangcode(item.targetLanguage),
createdAt: dayjs(item.createdAt).format('MM/DD HH:mm')
}))
}, [_translateHistory, getLanguageByLangcode])
const searchFilter = useCallback(
(item: DisplayedTranslateHistoryItem) => {
if (isEmpty(search)) return true
const content = `${item._sourceLanguage.label()} ${item._targetLanguage.label()} ${item.sourceText} ${item.targetText} ${item.createdAt}`
return content.includes(search)
},
[search]
)
useEffect(() => {
setDisplayedHistory(translateHistory.filter(searchFilter))
}, [searchFilter, translateHistory])
const deferredHistory = useDeferredValue(displayedHistory)
return (
<Drawer
title={t('translate.history.title')}
@ -71,9 +93,32 @@ const TranslateHistoryList: FC<TranslateHistoryProps> = ({ isOpen, onHistoryItem
}
}}>
<HistoryContainer>
{translateHistory && translateHistory.length ? (
{/* Search Bar */}
<HStack style={{ padding: '0 12px', borderBottom: '1px solid var(--ant-color-split)' }}>
<Input
prefix={
<IconWrapper>
<SearchIcon size={18} />
</IconWrapper>
}
placeholder={t('translate.history.search.placeholder')}
value={search}
onChange={(e) => {
setSearch(e.target.value)
}}
allowClear
autoFocus
spellCheck={false}
style={{ paddingLeft: 0, height: '3em' }}
variant="borderless"
size="middle"
/>
</HStack>
{/* Virtual List */}
{deferredHistory.length > 0 ? (
<HistoryList>
<DynamicVirtualList list={translateHistory} estimateSize={() => ITEM_HEIGHT}>
<DynamicVirtualList list={deferredHistory} estimateSize={() => ITEM_HEIGHT}>
{(item) => {
return (
<Dropdown
@ -98,7 +143,7 @@ const TranslateHistoryList: FC<TranslateHistoryProps> = ({ isOpen, onHistoryItem
<HistoryListItemLanguage>{item._sourceLanguage.label()} </HistoryListItemLanguage>
<HistoryListItemLanguage>{item._targetLanguage.label()}</HistoryListItemLanguage>
</Flex>
<HistoryListItemDate>{dayjs(item.createdAt).format('MM/DD HH:mm')}</HistoryListItemDate>
<HistoryListItemDate>{item.createdAt}</HistoryListItemDate>
</Flex>
<HistoryListItemTitle>{item.sourceText}</HistoryListItemTitle>
<HistoryListItemTitle style={{ color: 'var(--color-text-2)' }}>
@ -192,4 +237,14 @@ const HistoryListItemLanguage = styled.div`
color: var(--color-text-3);
`
const IconWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
height: 30px;
width: 30px;
border-radius: 15px;
background-color: var(--color-background-soft);
`
export default TranslateHistoryList