feat: save

This commit is contained in:
zyronon
2025-10-26 22:25:07 +08:00
parent f7aebf7c75
commit 70cd3e0d60
9 changed files with 74 additions and 70 deletions

View File

@@ -4,7 +4,7 @@ import { useSettingStore } from "@/stores/setting.ts";
import { getAudioFileUrl, usePlayAudio } from "@/hooks/sound.ts";
import { getShortcutKey, useEventListener } from "@/hooks/event.ts";
import { checkAndUpgradeSaveDict, checkAndUpgradeSaveSetting, cloneDeep, loadJsLib, shakeCommonDict } from "@/utils";
import { DefaultShortcutKeyMap, ShortcutKey } from "@/types/types.ts";
import {DefaultShortcutKeyMap, ShortcutKey, WordPracticeMode} from "@/types/types.ts";
import BaseButton from "@/components/BaseButton.vue";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import { useBaseStore } from "@/stores/base.ts";
@@ -489,8 +489,8 @@ function importOldData() {
<div v-if="tabIndex === 1">
<SettingItem title="练习模式">
<RadioGroup v-model="settingStore.wordPracticeMode" class="flex-col gap-0!">
<Radio :value="0" label="智能模式,系统自动计算复习单词与默写单词"/>
<Radio :value="1" label="自由模式,系统不强制复习与默写"/>
<Radio :value="WordPracticeMode.System" label="智能模式,系统自动计算复习单词与默写单词"/>
<Radio :value="WordPracticeMode.Free" label="自由模式,系统不强制复习与默写"/>
</RadioGroup>
</SettingItem>

View File

@@ -6,11 +6,11 @@ import Statistics from "@/pages/word/Statistics.vue";
import {emitter, EventKey, useEvents} from "@/utils/eventBus.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {Dict, PracticeData, PracticeMode, ShortcutKey, TaskWords, Word} from "@/types/types.ts";
import {Dict, PracticeData, WordPracticeType, ShortcutKey, TaskWords, Word, WordPracticeMode} from "@/types/types.ts";
import {useDisableEventListener, useOnKeyboardEventListener, useStartKeyboardEventListener} from "@/hooks/event.ts";
import useTheme from "@/hooks/theme.ts";
import {getCurrentStudyWord, useWordOptions} from "@/hooks/dict.ts";
import {_getDictDataByUrl, cloneDeep, resourceWrap, shuffle, sleep} from "@/utils";
import {_getDictDataByUrl, cloneDeep, resourceWrap, shuffle} from "@/utils";
import {useRoute, useRouter} from "vue-router";
import Footer from "@/pages/word/components/Footer.vue";
import Panel from "@/components/Panel.vue";
@@ -27,7 +27,6 @@ import ConflictNotice from "@/components/ConflictNotice.vue";
import PracticeLayout from "@/components/PracticeLayout.vue";
import {DICT_LIST, PracticeSaveWordKey} from "@/config/env.ts";
import {set} from "idb-keyval";
import {ToastInstance} from "@/components/base/toast/type.ts";
const {
@@ -61,7 +60,7 @@ let data = $ref<PracticeData>({
})
let isTypingWrongWord = ref(false)
let practiceMode = ref(PracticeMode.FollowWrite)
let practiceMode = ref(WordPracticeType.FollowWrite)
provide('isTypingWrongWord', isTypingWrongWord)
provide('practiceData', data)
provide('practiceMode', practiceMode)
@@ -128,21 +127,21 @@ function initData(initVal: TaskWords, init: boolean = false) {
taskWords = initVal
if (taskWords.new.length === 0) {
if (taskWords.review.length) {
practiceMode.value = PracticeMode.Identify
statStore.step = 2
settingStore.wordPracticeType = WordPracticeType.Identify
statStore.step = 3
data.words = taskWords.review
} else {
if (taskWords.write.length) {
practiceMode.value = PracticeMode.Identify
settingStore.wordPracticeType = WordPracticeType.Identify
data.words = taskWords.write
statStore.step = 4
statStore.step = 6
} else {
Toast.warning('没有可学习的单词!')
router.push('/word')
}
}
} else {
practiceMode.value = PracticeMode.FollowWrite
settingStore.wordPracticeType = WordPracticeType.FollowWrite
data.words = taskWords.new
statStore.step = 0
}
@@ -172,20 +171,20 @@ const nextWord: Word = $computed(() => {
return data.words?.[data.index + 1] ?? undefined
})
watch(practiceMode, (n) => {
if (settingStore.wordPracticeMode === 1) return
watch(() => settingStore.wordPracticeType, (n) => {
if (settingStore.wordPracticeMode === WordPracticeMode.Free) return
switch (n) {
case PracticeMode.Spell:
case PracticeMode.Dictation:
case PracticeMode.Listen:
case WordPracticeType.Spell:
case WordPracticeType.Dictation:
case WordPracticeType.Listen:
settingStore.dictation = true;
settingStore.translate = false;
break
case PracticeMode.FollowWrite:
case WordPracticeType.FollowWrite:
settingStore.dictation = false;
settingStore.translate = true;
break
case PracticeMode.Identify:
case WordPracticeType.Identify:
settingStore.dictation = false;
settingStore.translate = false;
break
@@ -196,11 +195,11 @@ function wordLoop() {
// return data.index++
let d = Math.floor(data.index / 6) - 1
if (data.index > 0 && data.index % 6 === (d < 0 ? 0 : d)) {
if (practiceMode.value === PracticeMode.FollowWrite) {
practiceMode.value = PracticeMode.Spell
if (settingStore.wordPracticeType === WordPracticeType.FollowWrite) {
settingStore.wordPracticeType = WordPracticeType.Spell
data.index -= 6
} else {
practiceMode.value = PracticeMode.FollowWrite
settingStore.wordPracticeType = WordPracticeType.FollowWrite
data.index++
}
} else {
@@ -216,13 +215,12 @@ function goNextStep(originList, mode, msg) {
if (toastInstance) toastInstance.close()
toastInstance = Toast.info('输入完成后按空格键切换下一个', {duration: 5000})
data.words = list
practiceMode.value = mode
settingStore.wordPracticeType = mode
data.index = 0
statStore.step++
} else {
console.log(msg + ':无单词略过')
statStore.step++
statStore.step++
statStore.step += 3
next()
}
}
@@ -233,7 +231,7 @@ async function next(isTyping: boolean = true) {
if (isTyping) {
statStore.inputWordNumber++
}
if (settingStore.wordPracticeMode === 1) {
if (settingStore.wordPracticeMode === WordPracticeMode.Free) {
if (data.index === data.words.length - 1) {
console.log('自由模式,全完学完了')
showStatDialog = true
@@ -244,7 +242,7 @@ async function next(isTyping: boolean = true) {
} else {
if (data.index === data.words.length - 1) {
if (statStore.step === 0 || isTypingWrongWord.value) {
if (practiceMode.value !== PracticeMode.Spell) {
if (settingStore.wordPracticeType !== WordPracticeType.Spell) {
let i = data.index
i--
let d = Math.floor(i / 6) - 1
@@ -259,14 +257,14 @@ async function next(isTyping: boolean = true) {
}
data.index = i + 1
emitter.emit(EventKey.resetWord)
practiceMode.value = PracticeMode.Spell
settingStore.wordPracticeType = WordPracticeType.Spell
return
}
}
data.wrongWords = data.wrongWords.filter(v => (!data.excludeWords.includes(v.word)))
if (data.wrongWords.length) {
isTypingWrongWord.value = true
practiceMode.value = PracticeMode.FollowWrite
settingStore.wordPracticeType = WordPracticeType.FollowWrite
console.log('当前学完了,但还有错词')
data.words = shuffle(cloneDeep(data.wrongWords))
data.index = 0
@@ -285,42 +283,42 @@ async function next(isTyping: boolean = true) {
//开始默写之前
if (statStore.step === 7) {
return goNextStep(shuffle(taskWords.write), PracticeMode.Dictation, '开始默写之前')
return goNextStep(shuffle(taskWords.write), WordPracticeType.Dictation, '开始默写之前')
}
//开始听写之前
if (statStore.step === 6) {
return goNextStep(shuffle(taskWords.write), PracticeMode.Listen, '开始听写之前')
return goNextStep(shuffle(taskWords.write), WordPracticeType.Listen, '开始听写之前')
}
//开始复写之前
if (statStore.step === 5) {
return goNextStep(taskWords.write, PracticeMode.Identify, '开始复写之前')
return goNextStep(taskWords.write, WordPracticeType.Identify, '开始复写之前')
}
//开始默写上次
if (statStore.step === 4) {
return goNextStep(shuffle(taskWords.review), PracticeMode.Dictation, '开始默写上次')
return goNextStep(shuffle(taskWords.review), WordPracticeType.Dictation, '开始默写上次')
}
//开始听写上次
if (statStore.step === 3) {
return goNextStep(shuffle(taskWords.review), PracticeMode.Listen, '开始听写上次')
return goNextStep(shuffle(taskWords.review), WordPracticeType.Listen, '开始听写上次')
}
//开始复写昨日
if (statStore.step === 2) {
return goNextStep(taskWords.review, PracticeMode.Identify, '开始复写昨日')
return goNextStep(taskWords.review, WordPracticeType.Identify, '开始复写昨日')
}
//开始默写新词
if (statStore.step === 1) {
return goNextStep(shuffle(taskWords.new), PracticeMode.Dictation, '开始默写新词')
return goNextStep(shuffle(taskWords.new), WordPracticeType.Dictation, '开始默写新词')
}
//开始听写新词
if (statStore.step === 0) {
return goNextStep(shuffle(taskWords.new), PracticeMode.Listen, '开始听写新词')
return goNextStep(shuffle(taskWords.new), WordPracticeType.Listen, '开始听写新词')
}
}
} else {
@@ -390,7 +388,7 @@ useOnKeyboardEventListener(onKeyDown, onKeyUp)
function repeat() {
console.log('重学一遍')
if (settingStore.wordPracticeMode === 0) settingStore.dictation = false
if (settingStore.wordPracticeMode === WordPracticeMode.System) settingStore.dictation = false
if (store.sdict.lastLearnIndex === 0 && store.sdict.complete) {
//如果是刚刚完成那么学习进度要从length减回去因为lastLearnIndex为0了同时改complete为false
store.sdict.lastLearnIndex = store.sdict.length - statStore.newWordNumber
@@ -422,7 +420,7 @@ function skip(e: KeyboardEvent) {
}
function show(e: KeyboardEvent) {
if (![PracticeMode.FollowWrite].includes(practiceMode.value)) onTypeWrong()
if (![WordPracticeType.FollowWrite].includes(settingStore.wordPracticeType)) onTypeWrong()
typingRef.showWord()
}
@@ -466,7 +464,7 @@ function togglePanel() {
}
function continueStudy() {
if (settingStore.wordPracticeMode === 0) settingStore.dictation = false
if (settingStore.wordPracticeMode === WordPracticeMode.System) settingStore.dictation = false
//这里判断是否显示结算弹框,如果显示了结算弹框的话,就不用加进度了
if (!showStatDialog) {
console.log('没学完,强行跳过')

View File

@@ -4,7 +4,7 @@ import { useRouter } from "vue-router";
import BaseIcon from "@/components/BaseIcon.vue";
import { _getAccomplishDate, _getDictDataByUrl, resourceWrap, useNav } from "@/utils";
import BasePage from "@/components/BasePage.vue";
import { DictResource } from "@/types/types.ts";
import {DictResource, WordPracticeMode} from "@/types/types.ts";
import { watch } from "vue";
import { getCurrentStudyWord } from "@/hooks/dict.ts";
import { useRuntimeStore } from "@/stores/runtime.ts";
@@ -221,7 +221,7 @@ const {
<div class="text-4xl font-bold">{{ currentStudy.new.length }}</div>
<div class="text">新词</div>
</div>
<template v-if="settingStore.wordPracticeMode === 0">
<template v-if="settingStore.wordPracticeMode === WordPracticeMode.System">
<div class="flex-1 flex flex-col items-center">
<div class="text-4xl font-bold">{{ currentStudy.review.length }}</div>
<div class="text">复习上次</div>

View File

@@ -3,7 +3,7 @@
import { inject, Ref, watch } from "vue"
import { usePracticeStore } from "@/stores/practice.ts";
import { useSettingStore } from "@/stores/setting.ts";
import { PracticeData, PracticeMode, ShortcutKey } from "@/types/types.ts";
import { PracticeData, WordPracticeType, ShortcutKey } from "@/types/types.ts";
import BaseIcon from "@/components/BaseIcon.vue";
import Tooltip from "@/components/base/Tooltip.vue";
import Progress from '@/components/base/Progress.vue'
@@ -26,7 +26,6 @@ const emit = defineEmits<{
let practiceData = inject<PracticeData>('practiceData')
let isTypingWrongWord = inject<Ref<boolean>>('isTypingWrongWord')
let practiceMode = inject<Ref<PracticeMode>>('practiceMode')
function format(val: number, suffix: string = '', check: number = -1) {
return val === check ? '-' : (val + suffix)
@@ -97,7 +96,7 @@ const progress = $computed(() => {
<div class="name">{{ status }}</div>
</div>
<div class="row">
<div class="num">{{ statisticsStore.total }}{{ practiceMode }}</div>
<div class="num">{{ statisticsStore.total }}</div>
<div class="line"></div>
<div class="name">单词总数</div>
</div>

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import {PracticeMode, ShortcutKey, Word} from "@/types/types.ts";
import {WordPracticeType, ShortcutKey, Word, WordPracticeMode} from "@/types/types.ts";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import {useSettingStore} from "@/stores/setting.ts";
import {usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePlayWordAudio} from "@/hooks/sound.ts";
@@ -38,8 +38,6 @@ let cursor = $ref({
top: 0,
left: 0,
})
let practiceMode = inject<Ref<PracticeMode>>('practiceMode')
const settingStore = useSettingStore()
const statStore = usePracticeStore()
@@ -73,7 +71,7 @@ function reset() {
wordRepeatCount = 0
showWordResult = inputLock = false
if (settingStore.wordSound) {
if (practiceMode.value !== PracticeMode.Dictation) {
if (settingStore.wordPracticeType !== WordPracticeType.Dictation) {
volumeIconRef?.play(400, true)
}
}
@@ -124,7 +122,7 @@ const right = $computed(() => {
})
function know(e) {
if (practiceMode.value === PracticeMode.Identify) {
if (settingStore.wordPracticeType === WordPracticeType.Identify) {
if (!showWordResult) {
inputLock = showWordResult = true
input = props.word.word
@@ -136,7 +134,7 @@ function know(e) {
}
function unknown(e) {
if (practiceMode.value === PracticeMode.Identify) {
if (settingStore.wordPracticeType === WordPracticeType.Identify) {
if (!showWordResult) {
showWordResult = true
emit('wrong')
@@ -169,7 +167,7 @@ async function onTyping(e: KeyboardEvent) {
inputLock = true
let letter = e.key
//默写特殊逻辑
if (practiceMode.value === PracticeMode.Dictation) {
if (settingStore.wordPracticeType === WordPracticeType.Dictation) {
if (e.code === 'Space') {
//如果输入长度大于单词长度/单词不包含空格,并且输入不为空(开始直接输入空格不行),则显示单词;
// 这里inputLock 不设为 false不能再输入了只能删除删除会重置 inputLock或按空格切下一格
@@ -225,10 +223,10 @@ async function onTyping(e: KeyboardEvent) {
//不需要把inputLock设为false输入完成不能再输入了只能删除删除会打开锁
if (input.toLowerCase() === word.toLowerCase()) {
playCorrect()
if ([PracticeMode.Listen, PracticeMode.Identify].includes(practiceMode.value) && !showWordResult) {
if ([WordPracticeType.Listen, WordPracticeType.Identify].includes(settingStore.wordPracticeType) && !showWordResult) {
showWordResult = true
}
if ([PracticeMode.Free, PracticeMode.FollowWrite, PracticeMode.Spell].includes(practiceMode.value)) {
if ([WordPracticeType.FollowWrite, WordPracticeType.Spell].includes(settingStore.wordPracticeType)) {
if (settingStore.autoNextWord) {
if (settingStore.repeatCount == 100) {
if (settingStore.repeatCustomCount <= wordRepeatCount + 1) {
@@ -376,7 +374,7 @@ useEvents([
v-if="settingStore.soundType === 'us' && word.phonetic0">[{{ word.phonetic0 }}]
</div>
<div class="phonetic"
:class="((settingStore.dictation || [PracticeMode.Spell,PracticeMode.Listen,PracticeMode.Dictation].includes(practiceMode)) && !showFullWord && !showWordResult) && 'word-shadow'"
:class="((settingStore.dictation || [WordPracticeType.Spell,WordPracticeType.Listen,WordPracticeType.Dictation].includes(settingStore.wordPracticeType)) && !showFullWord && !showWordResult) && 'word-shadow'"
v-if="settingStore.soundType === 'uk' && word.phonetic1">[{{ word.phonetic1 }}]
</div>
<VolumeIcon
@@ -390,7 +388,7 @@ useEvents([
@mouseenter="showWord"
@mouseleave="mouseleave"
>
<div v-if="practiceMode === PracticeMode.Dictation">
<div v-if="settingStore.wordPracticeType === WordPracticeType.Dictation">
<div class="letter text-align-center w-full inline-block"
v-opacity="showWordResult || showFullWord">
{{ word.word }}
@@ -408,10 +406,10 @@ useEvents([
<template v-else>
<span class="input" v-if="input">{{ input }}</span>
<span class="wrong" v-if="wrong">{{ wrong }}</span>
<template v-if="settingStore.wordPracticeMode === 0">
<template v-if="[PracticeMode.Spell,PracticeMode.Listen,PracticeMode.Dictation].includes(practiceMode)">
<template v-if="settingStore.wordPracticeMode === WordPracticeMode.System">
<template v-if="[WordPracticeType.Spell,WordPracticeType.Listen,WordPracticeType.Dictation].includes(settingStore.wordPracticeType)">
<span class="letter" v-if="!showFullWord">{{
displayWord.split('').map(() => (PracticeMode.Dictation === practiceMode ? '&nbsp;' : '_')).join('')
displayWord.split('').map(() => (WordPracticeType.Dictation === settingStore.wordPracticeType ? '&nbsp;' : '_')).join('')
}}</span>
<span class="letter" v-else>{{ displayWord }}</span>
</template>
@@ -426,7 +424,7 @@ useEvents([
</template>
</div>
<div class="mt-4 flex gap-4" v-if="practiceMode === PracticeMode.Identify && !showWordResult">
<div class="mt-4 flex gap-4" v-if="settingStore.wordPracticeType === WordPracticeType.Identify && !showWordResult">
<BaseButton
:keyboard="`快捷键(${settingStore.shortcutKeyMap[ShortcutKey.KnowWord]})`"
size="large" @click="know">我认识
@@ -438,7 +436,7 @@ useEvents([
</div>
<div class="translate flex flex-col gap-2 my-3"
v-opacity="settingStore.translate || ![PracticeMode.Listen,PracticeMode.Identify].includes(practiceMode) || showWordResult || showFullWord"
v-opacity="settingStore.translate || ![WordPracticeType.Listen,WordPracticeType.Identify].includes(settingStore.wordPracticeType) || showWordResult || showFullWord"
:style="{
fontSize: settingStore.fontSize.wordTranslateFontSize +'px',
}"
@@ -446,14 +444,14 @@ useEvents([
<div class="flex" v-for="(v,i) in word.trans">
<div class="shrink-0" :class="v.pos ? 'w-12 en-article-family' : '-ml-3'">{{ v.pos }}</div>
<span
v-if="([PracticeMode.Listen,PracticeMode.Identify].includes(practiceMode) || settingStore.dictation) && !(showWordResult || showFullWord)"
v-if="([WordPracticeType.Listen,WordPracticeType.Identify].includes(settingStore.wordPracticeType) || settingStore.dictation) && !(showWordResult || showFullWord)"
v-html="hideWordInTranslation(v.cn, word.word)"></span>
<span v-else>{{ v.cn }}</span>
</div>
</div>
</div>
<div class="other anim"
v-opacity="![PracticeMode.Listen,PracticeMode.Dictation,PracticeMode.Identify].includes(practiceMode) || showFullWord || showWordResult">
v-opacity="![WordPracticeType.Listen,WordPracticeType.Dictation,WordPracticeType.Identify].includes(settingStore.wordPracticeType) || showFullWord || showWordResult">
<div class="line-white my-2"></div>
<template v-if="word?.sentences?.length">
<div class="flex flex-col gap-3">