From 45dbac5012ef9823083ee094c462dcd97999be75 Mon Sep 17 00:00:00 2001 From: Zyronon Date: Tue, 28 Apr 2026 01:14:45 +0800 Subject: [PATCH 1/4] feat: normalize user input to match target text --- .../components/word/PracticeSettingDialog.vue | 2 +- .../core/src/components/word/TypeWord.vue | 20 +++++++++----- packages/core/src/hooks/dict.ts | 2 +- packages/core/src/utils/index.ts | 26 +++++++++++++++++++ 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/packages/core/src/components/word/PracticeSettingDialog.vue b/packages/core/src/components/word/PracticeSettingDialog.vue index ebaca97e..f33cddc0 100644 --- a/packages/core/src/components/word/PracticeSettingDialog.vue +++ b/packages/core/src/components/word/PracticeSettingDialog.vue @@ -77,7 +77,7 @@ watch( {{ $t('new_words_count2') }} - ,{{ $t('review') }} + ,最多复习
{{ (tempPerDayStudyNumber * tempWordReviewRatio) || '-'}}
diff --git a/packages/core/src/components/word/TypeWord.vue b/packages/core/src/components/word/TypeWord.vue index 5f2a1b3a..ee3c5504 100644 --- a/packages/core/src/components/word/TypeWord.vue +++ b/packages/core/src/components/word/TypeWord.vue @@ -6,7 +6,7 @@ import { getBrowserKey, usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePl import { emitter, EventKey, useEventsByWatch } from '../../utils/eventBus' import { onMounted, onUnmounted, watch } from 'vue' import SentenceHightLightWord from './SentenceHightLightWord.vue' -import { _nextTick, last } from '../../utils' +import { _nextTick, last, normalizeWord } from '../../utils' import { BaseButton, BaseIcon, Toast, ToastComponent, Tooltip, VolumeIcon } from '@typewords/base' import Space from '../article/Space.vue' import { useI18n } from 'vue-i18n' @@ -173,16 +173,22 @@ let showWordResult = ref(false) let pressNumber = 0 const right = $computed(() => { - let target + let a = input + let b if (isTypingSentence()) { - target = props.word.sentences[currentPracticeSentenceIndex].c + b = props.word.sentences[currentPracticeSentenceIndex].c } else { - target = props.word.word + b = props.word.word + } + + if (settingStore.wordPracticeType === WordPracticeType.Dictation) { + a = normalizeWord(a) + b = normalizeWord(b) } if (settingStore.ignoreCase) { - return input.toLowerCase() === target.toLowerCase() + return a.toLowerCase() === b.toLowerCase() } else { - return input === target + return a === b } }) @@ -325,7 +331,7 @@ async function onTyping(e: KeyboardEvent) { // 这里inputLock 不设为 false,不能再输入了,只能删除(删除会重置 inputLock)或按空格切下一格 if (input.length && (input.length >= target.length || !target.includes(' '))) { //比对是否一致 - if (input.toLowerCase() === target.toLowerCase()) { + if (right) { //如果已显示单词,则发射完成事件,并 return if (showWordResult.value) { return emit('complete') diff --git a/packages/core/src/hooks/dict.ts b/packages/core/src/hooks/dict.ts index 43e97531..b15d5ef7 100644 --- a/packages/core/src/hooks/dict.ts +++ b/packages/core/src/hooks/dict.ts @@ -111,7 +111,7 @@ export function getCurrentStudyWord(): TaskWords { const perDay = dict.perDayStudyNumber const start = isTest ? 1 : dict.lastLearnIndex const complete = isTest ? true : dict.complete - const isEnd = start >= dict.length - 1 + const isEnd = start >= dict.length - 1 && dict.length !== 1 const reviewRatio = settingStore.wordReviewRatio let end = start diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index af9db362..da340f85 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -644,3 +644,29 @@ export function isEmpty(obj: any): boolean { } return obj === null || obj === undefined || obj === '' } + +const charMap = { + '’': "'", + '‘': "'", + '“': '"', + '”': '"', + ' ': ' ', + '。': '.', + ',': ',', + '?': '?', + '【': '[', + '】': ']', + '¥': '$', + '!': '!', + '(': '(', + ')': ')', + '《': '<', + '》': '>', +} + +export function normalizeWord(word: string) { + return word + .split('') + .map(ch => charMap[ch] || ch) + .join('') +} From caa10df1c79c3298ca7dab9b31319cb2e04d016a Mon Sep 17 00:00:00 2001 From: Zyronon Date: Fri, 8 May 2026 00:40:01 +0800 Subject: [PATCH 2/4] fix: practice article errors & CSS layout issue on 1536px screens --- apps/nuxt/app/assets/css/main.scss | 8 +++----- apps/nuxt/app/pages/(articles)/book/[id].vue | 12 ++++++------ packages/core/src/components/PracticeLayout.vue | 2 +- packages/core/src/utils/cache.ts | 15 ++++++++++++--- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/apps/nuxt/app/assets/css/main.scss b/apps/nuxt/app/assets/css/main.scss index 3a0a1abd..5880d42a 100644 --- a/apps/nuxt/app/assets/css/main.scss +++ b/apps/nuxt/app/assets/css/main.scss @@ -121,7 +121,7 @@ html.dark { --color-line: rgb(66, 66, 66); } -@media (max-width: 1720px) { +@media (max-width: 1706px) { :root { --toolbar-width: 50vw; --panel-width: 20rem; @@ -133,9 +133,9 @@ html.dark { } } -@media (max-width: 1560px) { +@media (max-width: 1439px) { :root { - --panel-width: 24rem; + --panel-width: 25rem; --toolbar-width: 60vw; --article-width: 60vw; --article-toolbar-width: 60vw; @@ -148,8 +148,6 @@ html.dark { --toolbar-width: 70vw; --stat-gap: 0.5rem; --space: 0.3rem; - - --article-width: 70vw; --article-toolbar-width: 70vw; } diff --git a/apps/nuxt/app/pages/(articles)/book/[id].vue b/apps/nuxt/app/pages/(articles)/book/[id].vue index 87350823..d9774006 100644 --- a/apps/nuxt/app/pages/(articles)/book/[id].vue +++ b/apps/nuxt/app/pages/(articles)/book/[id].vue @@ -64,12 +64,12 @@ async function startPractice() { if (cache) { let currentArticle = store.sbook.articles[store.sbook.lastLearnIndex] let data: Partial & { title: string; articleId: number } = { - articleId: Number(currentArticle.id), - title: currentArticle.title, - spend: cache.statStoreData.spend, - startDate: cache.statStoreData.startDate, - total: cache.statStoreData.total, - wrong: cache.statStoreData.wrong, + articleId: Number(currentArticle?.id), + title: currentArticle?.title, + spend: cache?.statStoreData?.spend, + startDate: cache?.statStoreData?.startDate, + total: cache?.statStoreData?.total, + wrong: cache?.statStoreData?.wrong, } store.sbook.statistics.push(data as any) await practice.clear() diff --git a/packages/core/src/components/PracticeLayout.vue b/packages/core/src/components/PracticeLayout.vue index 808cbc48..4bd746f3 100644 --- a/packages/core/src/components/PracticeLayout.vue +++ b/packages/core/src/components/PracticeLayout.vue @@ -51,7 +51,7 @@ defineProps<{ height: calc(100vh - 1.8rem); } -@media (max-width: 1560px) { +@media (max-width: 1439px) { .panel-wrap { position: fixed; top: 0; diff --git a/packages/core/src/utils/cache.ts b/packages/core/src/utils/cache.ts index 9fd10efb..69ba2233 100644 --- a/packages/core/src/utils/cache.ts +++ b/packages/core/src/utils/cache.ts @@ -89,7 +89,10 @@ async function getLocalWithMeta(config: CacheConfig): Promise(config: CacheConfig): Promise { const result = await getLocalWithMeta(config) - return result?.val ?? null + if (result?.val) { + if (Object.keys(result.val).length > 0) return result.val + } + return null } async function setLocal(config: CacheConfig, val: T | null, updated_at: string): Promise { @@ -110,7 +113,10 @@ export async function getPracticeWordCacheLocalWithMeta(): Promise(PRACTICE_WORD_CACHE) } -export async function setPracticeWordCacheLocal(cache: PracticeWordCacheStored | null, updated_at?: string): Promise { +export async function setPracticeWordCacheLocal( + cache: PracticeWordCacheStored | null, + updated_at?: string +): Promise { await setLocal(PRACTICE_WORD_CACHE, cache, updated_at) } @@ -122,6 +128,9 @@ export async function getPracticeArticleCacheLocalWithMeta(): Promise(PRACTICE_ARTICLE_CACHE) } -export async function setPracticeArticleCacheLocal(cache: PracticeArticleCache | null, updated_at?: string): Promise { +export async function setPracticeArticleCacheLocal( + cache: PracticeArticleCache | null, + updated_at?: string +): Promise { await setLocal(PRACTICE_ARTICLE_CACHE, cache, updated_at) } From 3d6d5efd611ba2fef607a32edea85e42844fb5c6 Mon Sep 17 00:00:00 2001 From: Zyronon Date: Fri, 8 May 2026 14:16:43 +0800 Subject: [PATCH 3/4] feat: mini program --- apps/nuxt/app/layouts/default.vue | 3 ++- apps/nuxt/app/pages/index.vue | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/nuxt/app/layouts/default.vue b/apps/nuxt/app/layouts/default.vue index a42ead6e..91881bf4 100644 --- a/apps/nuxt/app/layouts/default.vue +++ b/apps/nuxt/app/layouts/default.vue @@ -14,6 +14,7 @@ import 'vue-virtual-scroller/dist/vue-virtual-scroller.css' import { useInit } from '@typewords/core/composables/useInit.ts' import { useI18n } from 'vue-i18n' import { Supabase } from '@typewords/core/utils/supabase.ts' +import MiniProgram from '@/components/MiniProgram.vue' const router = useRouter() const { toggleTheme, getTheme, setTheme } = useTheme() @@ -161,7 +162,7 @@ onMounted(() => {
- +
diff --git a/apps/nuxt/app/pages/index.vue b/apps/nuxt/app/pages/index.vue index a83627ab..2c4c2a9b 100644 --- a/apps/nuxt/app/pages/index.vue +++ b/apps/nuxt/app/pages/index.vue @@ -461,7 +461,7 @@ let mobileMenuOpen = $ref(false) target="_blank" >查看源码 - +
From 986a579ac49e148cc5936fc56e7560983dfa2691 Mon Sep 17 00:00:00 2001 From: Zyronon Date: Fri, 8 May 2026 16:40:59 +0800 Subject: [PATCH 4/4] fix: sync failed on first use --- apps/nuxt/app/pages/setting.vue | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/nuxt/app/pages/setting.vue b/apps/nuxt/app/pages/setting.vue index c37746c1..7e2fb0c7 100644 --- a/apps/nuxt/app/pages/setting.vue +++ b/apps/nuxt/app/pages/setting.vue @@ -49,7 +49,6 @@ import SettingItem from '@typewords/core/components/setting/SettingItem.vue' import { Supabase } from '@typewords/core/utils/supabase.ts' import BackupGateDialog from '@typewords/core/components/dialog/BackupGateDialog.vue' - import { createClient } from '@supabase/supabase-js' import { useRoute } from 'vue-router' import type { BackupData, Snapshot } from '@typewords/core' @@ -443,6 +442,9 @@ function transferOk() { async function clearAllData() { await dataSyncPersistence.clear() Supabase.removeConfig() + sbForm.url = '' + sbForm.key = '' + sbStatus = { status: 'idle', statusMessage: undefined } Toast.success('清除成功') } @@ -524,6 +526,7 @@ async function doSaveSbConfig() { } else { Supabase.setStatus('success') sbStatus = Supabase.getStatus() + await onSbFirstSyncChoice('push_local') Toast.success('保存成功') Supabase.saveConfig(sbForm?.url, sbForm?.key) transferOk() @@ -677,7 +680,9 @@ function removeSbConfig() {
- 状态:同步正常运行中,数据已同步到云端 + 状态:同步正常运行中,数据已同步到云端 同步状态:失败{{ sbStatus.statusMessage ? `(${sbStatus.statusMessage})` : '' }} @@ -767,7 +772,7 @@ function removeSbConfig() {
-
Build {{ gitLastCommitHash }} {{gitLastCommitTime}}
+
Build {{ gitLastCommitHash }} {{ gitLastCommitTime }}
@@ -808,9 +813,9 @@ function removeSbConfig() {
暂无历史数据
这里是每次 {{ APP_NAME }} 更新后/报错后自动保存的用户数据,如果您的数据被损坏,您可在此尝试恢复
-
+
-
{{ i+1 }}. 版本号:{{ item.hash }}
+
{{ i + 1 }}. 版本号:{{ item.hash }}
自动备份时间:{{ formatHistoryTime(item.createdAt) }}