This commit is contained in:
Zyronon
2025-10-22 12:21:16 +00:00
parent 84798f8268
commit 2a40aa7610
5 changed files with 169 additions and 128 deletions

View File

@@ -552,7 +552,7 @@ function importOldData() {
<div class="line"></div>
<SettingItem mainTitle="自动切换"/>
<SettingItem title="自动切换下一个单词"
desc="未开启自动切换时,当输入完成后请使用空格键切换下一个"
desc="未开启自动切换时,当输入完成后请使用 **空格键** 切换下一个"
>
<Switch v-model="settingStore.autoNextWord"/>
</SettingItem>

View File

@@ -1,17 +1,17 @@
<script setup lang="ts">
import { onMounted, provide, watch } from "vue";
import {onMounted, provide, ref, watch} from "vue";
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, ShortcutKey, TaskWords, Word } from "@/types/types.ts";
import { useDisableEventListener, useOnKeyboardEventListener, useStartKeyboardEventListener } from "@/hooks/event.ts";
import {emitter, EventKey, useEvents} from "@/utils/eventBus.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {Dict, PracticeData, ShortcutKey, TaskWords, Word} 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 } from "@/utils";
import { useRoute, useRouter } from "vue-router";
import {getCurrentStudyWord, useWordOptions} from "@/hooks/dict.ts";
import {_getDictDataByUrl, cloneDeep, resourceWrap, shuffle, sleep} from "@/utils";
import {useRoute, useRouter} from "vue-router";
import Footer from "@/pages/word/components/Footer.vue";
import Panel from "@/components/Panel.vue";
import BaseIcon from "@/components/BaseIcon.vue";
@@ -19,15 +19,15 @@ import Tooltip from "@/components/base/Tooltip.vue";
import WordList from "@/components/list/WordList.vue";
import TypeWord from "@/pages/word/components/TypeWord.vue";
import Empty from "@/components/Empty.vue";
import { useBaseStore } from "@/stores/base.ts";
import { usePracticeStore } from "@/stores/practice.ts";
import {useBaseStore} from "@/stores/base.ts";
import {usePracticeStore} from "@/stores/practice.ts";
import Toast from '@/components/base/toast/Toast.ts'
import { getDefaultDict, getDefaultWord } from "@/types/func.ts";
import {getDefaultDict, getDefaultWord} from "@/types/func.ts";
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 {DICT_LIST, PracticeSaveWordKey} from "@/config/env.ts";
import {set} from "idb-keyval";
const {
isWordCollect,
@@ -57,6 +57,9 @@ let data = $ref<PracticeData>({
words: [],
wrongWords: [],
})
let isTypingWrongWord = ref(false)
provide('isTypingWrongWord', isTypingWrongWord)
provide('practiceData', data)
async function loadDict() {
// console.log('load好了开始加载')
@@ -90,7 +93,6 @@ watch(() => store.load, (n) => {
if (n && loading) loadDict()
}, {immediate: true})
onMounted(() => {
//如果是从单词学习主页过来的,就直接使用;否则等待加载
if (runtimeStore.routeData) {
@@ -121,12 +123,12 @@ function initData(initVal: TaskWords, init: boolean = false) {
taskWords = initVal
if (taskWords.new.length === 0) {
if (taskWords.review.length) {
settingStore.dictation = false
readMode()
statStore.step = 2
data.words = taskWords.review
} else {
if (taskWords.write.length) {
settingStore.dictation = true
writeMode()
data.words = taskWords.write
statStore.step = 4
} else {
@@ -135,7 +137,7 @@ function initData(initVal: TaskWords, init: boolean = false) {
}
}
} else {
settingStore.dictation = false
readMode()
data.words = taskWords.new
statStore.step = 0
}
@@ -150,11 +152,10 @@ function initData(initVal: TaskWords, init: boolean = false) {
statStore.reviewWordNumber = taskWords.review.length
statStore.writeWordNumber = taskWords.write.length
statStore.index = 0
isTypingWrongWord.value = false
}
}
provide('practiceData', data)
const word = $computed(() => {
return data.words[data.index] ?? getDefaultWord()
})
@@ -165,10 +166,42 @@ const nextWord: Word = $computed(() => {
return data.words?.[data.index + 1] ?? undefined
})
function next(isTyping: boolean = true) {
// showStatDialog = true
// return
if (isTyping) statStore.inputWordNumber++
function readMode() {
settingStore.dictation = false
settingStore.translate = true
}
function reviewMode() {
settingStore.dictation = true
settingStore.translate = true
}
function writeMode() {
settingStore.dictation = true
settingStore.translate = false
}
function wordLoop() {
let d = Math.floor(data.index / 6) - 1
if (data.index > 0 && data.index % 6 === (d < 0 ? 0 : d)) {
if (!settingStore.dictation) {
reviewMode()
data.index -= 6
} else {
readMode()
data.index++
}
} else {
data.index++
}
}
async function next(isTyping: boolean = true) {
debugger
if (isTyping) {
statStore.inputWordNumber++
}
if (settingStore.wordPracticeMode === 1) {
if (data.index === data.words.length - 1) {
console.log('自由模式,全完学完了')
@@ -179,7 +212,7 @@ function next(isTyping: boolean = true) {
}
} else {
if (data.index === data.words.length - 1) {
if ([0, 2].includes(statStore.step)) {
if ([0, 2].includes(statStore.step) || isTypingWrongWord.value) {
if (!settingStore.dictation) {
let i = data.index
i--
@@ -190,18 +223,28 @@ function next(isTyping: boolean = true) {
}
console.log('i', i)
if (i <= 0) i = -1
if (i + 1 == data.index) {
data.index = 0
}
data.index = i + 1
settingStore.dictation = true
emitter.emit(EventKey.resetWord)
reviewMode()
return
}
} else {
if (settingStore.dictation && !settingStore.translate) {
return readMode()
}
}
if (data.wrongWords.length) {
settingStore.dictation = false
isTypingWrongWord.value = true
readMode()
console.log('当前学完了,但还有错词')
data.words = shuffle(cloneDeep(data.wrongWords))
data.index = 0
data.wrongWords = []
} else {
isTypingWrongWord.value = false
console.log('当前学完了,没错词', statStore.total, statStore.step, data.index)
//学完了
@@ -219,7 +262,8 @@ function next(isTyping: boolean = true) {
statStore.step++
if (taskWords.write.length) {
console.log('开始默认所有单词')
settingStore.dictation = true
Toast.info('默写完成后按空格键切换下一个', {duration: 10000})
writeMode()
data.words = shuffle(taskWords.write)
data.index = 0
} else {
@@ -233,7 +277,8 @@ function next(isTyping: boolean = true) {
statStore.step++
if (taskWords.review.length) {
console.log('开始默写昨日')
settingStore.dictation = true
Toast.info('默写完成后按空格键切换下一个', {duration: 10000})
writeMode()
data.words = shuffle(taskWords.review)
data.index = 0
} else {
@@ -247,7 +292,7 @@ function next(isTyping: boolean = true) {
statStore.step++
if (taskWords.review.length) {
console.log('开始复习昨日')
settingStore.dictation = false
readMode()
data.words = shuffle(taskWords.review)
data.index = 0
} else {
@@ -260,27 +305,24 @@ function next(isTyping: boolean = true) {
if (statStore.step === 0) {
statStore.step++
console.log('开始默写新词')
settingStore.dictation = true
Toast.info('默写完成后按空格键切换下一个', {duration: 10000})
writeMode()
data.words = shuffle(taskWords.new)
data.index = 0
}
}
} else {
if ([0, 2].includes(statStore.step)) {
let d = Math.floor(data.index / 6) - 1
if (data.index > 0 && data.index % 6 === (d < 0 ? 0 : d)) {
if (!settingStore.dictation) {
settingStore.dictation = true
data.index -= 6
} else {
settingStore.dictation = false
data.index++
}
if ([0, 2].includes(statStore.step) || isTypingWrongWord.value) {
wordLoop()
} else {
if (settingStore.dictation && !settingStore.translate) {
readMode()
} else {
writeMode()
await sleep(100)
data.index++
}
} else {
data.index++
// await sleep(2000)
}
}
}
@@ -461,8 +503,8 @@ useEvents([
<template>
<PracticeLayout
v-loading="loading"
panelLeft="var(--word-panel-margin-left)">
v-loading="loading"
panelLeft="var(--word-panel-margin-left)">
<template v-slot:practice>
<div class="practice-word">
<div class="absolute z-1 top-4 w-full" v-if="settingStore.showNearWord">
@@ -471,7 +513,7 @@ useEvents([
v-if="prevWord">
<IconFluentArrowLeft16Regular class="arrow" width="22"/>
<Tooltip
:title="`上一个(${settingStore.shortcutKeyMap[ShortcutKey.Previous]})`"
:title="`上一个(${settingStore.shortcutKeyMap[ShortcutKey.Previous]})`"
>
<div class="word">{{ prevWord.word }}</div>
</Tooltip>
@@ -480,7 +522,7 @@ useEvents([
@click="next(false)"
v-if="nextWord">
<Tooltip
:title="`下一个(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
:title="`下一个(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
>
<div class="word" :class="settingStore.dictation && 'word-shadow'">{{ nextWord.word }}</div>
</Tooltip>
@@ -488,10 +530,10 @@ useEvents([
</div>
</div>
<TypeWord
ref="typingRef"
:word="word"
@wrong="onTypeWrong"
@complete="next"
ref="typingRef"
:word="word"
@wrong="onTypeWrong"
@complete="next"
/>
</div>
</template>
@@ -503,41 +545,41 @@ useEvents([
<span>{{ store.sdict.name }} ({{ store.sdict.lastLearnIndex }} / {{ store.sdict.length }})</span>
<BaseIcon
@click="continueStudy"
:title="`下一组(${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`">
@click="continueStudy"
:title="`下一组(${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`">
<IconFluentArrowRight16Regular class="arrow" width="22"/>
</BaseIcon>
<BaseIcon
@click="randomWrite"
:title="`随机默写(${settingStore.shortcutKeyMap[ShortcutKey.RandomWrite]})`">
@click="randomWrite"
:title="`随机默写(${settingStore.shortcutKeyMap[ShortcutKey.RandomWrite]})`">
<IconFluentArrowShuffle16Regular class="arrow" width="22"/>
</BaseIcon>
</div>
</template>
<div class="panel-page-item pl-4">
<WordList
v-if="data.words.length"
:is-active="settingStore.showPanel"
:static="false"
:show-word="!settingStore.dictation"
:show-translate="settingStore.translate"
:list="data.words"
:activeIndex="data.index"
@click="(val:any) => data.index = val.index"
v-if="data.words.length"
:is-active="settingStore.showPanel"
:static="false"
:show-word="!settingStore.dictation"
:show-translate="settingStore.translate"
:list="data.words"
:activeIndex="data.index"
@click="(val:any) => data.index = val.index"
>
<template v-slot:suffix="{item,index}">
<BaseIcon
:class="!isWordCollect(item)?'collect':'fill'"
@click.stop="toggleWordCollect(item)"
:title="!isWordCollect(item) ? '收藏' : '取消收藏'">
:class="!isWordCollect(item)?'collect':'fill'"
@click.stop="toggleWordCollect(item)"
:title="!isWordCollect(item) ? '收藏' : '取消收藏'">
<IconFluentStar16Regular v-if="!isWordCollect(item)"/>
<IconFluentStar16Filled v-else/>
</BaseIcon>
<BaseIcon
:class="!isWordSimple(item)?'collect':'fill'"
@click.stop="toggleWordSimple(item)"
:title="!isWordSimple(item) ? '标记为已掌握' : '取消标记已掌握'">
:class="!isWordSimple(item)?'collect':'fill'"
@click.stop="toggleWordSimple(item)"
:title="!isWordSimple(item) ? '标记为已掌握' : '取消标记已掌握'">
<IconFluentCheckmarkCircle16Regular v-if="!isWordSimple(item)"/>
<IconFluentCheckmarkCircle16Filled v-else/>
</BaseIcon>
@@ -549,11 +591,11 @@ useEvents([
</template>
<template v-slot:footer>
<Footer
:is-simple="isWordSimple(word)"
@toggle-simple="toggleWordSimpleWrapper"
:is-collect="isWordCollect(word)"
@toggle-collect="toggleWordCollect(word)"
@skip="next(false)"
:is-simple="isWordSimple(word)"
@toggle-simple="toggleWordSimpleWrapper"
:is-collect="isWordCollect(word)"
@toggle-collect="toggleWordCollect(word)"
@skip="next(false)"
/>
</template>
</PracticeLayout>

View File

@@ -1,9 +1,9 @@
<script setup lang="ts">
import { inject, watch } from "vue"
import { usePracticeStore } from "@/stores/practice.ts";
import { useSettingStore } from "@/stores/setting.ts";
import { ShortcutKey, PracticeData } from "@/types/types.ts";
import {inject, Ref, watch} from "vue"
import {usePracticeStore} from "@/stores/practice.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {PracticeData, 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'
@@ -25,25 +25,27 @@ const emit = defineEmits<{
}>()
let practiceData = inject<PracticeData>('practiceData')
let isTypingWrongWord = inject<Ref<boolean>>('isTypingWrongWord')
function format(val: number, suffix: string = '', check: number = -1) {
return val === check ? '-' : (val + suffix)
}
const status = $computed(() => {
let str = '正在'
if (isTypingWrongWord.value) return '复习错词'
let str = ''
switch (statisticsStore.step) {
case 0:
str += `学习新词`
break
case 1:
str += `默写新词`
str += `默写所有新词`
break
case 2:
str += `复习上次`
break
case 3:
str += `默写上次`
str += `默写上次所有`
break
case 4:
str += '默写之前'
@@ -63,17 +65,17 @@ const progress = $computed(() => {
<div class="footer">
<Tooltip :title="settingStore.showToolbar?'收起':'展开'">
<IconFluentChevronLeft20Filled
@click="settingStore.showToolbar = !settingStore.showToolbar"
class="arrow"
:class="!settingStore.showToolbar && 'down'"
color="#999"/>
@click="settingStore.showToolbar = !settingStore.showToolbar"
class="arrow"
:class="!settingStore.showToolbar && 'down'"
color="#999"/>
</Tooltip>
<div class="bottom">
<Progress
:percentage="progress"
:stroke-width="8"
:show-text="false"/>
:percentage="progress"
:stroke-width="8"
:show-text="false"/>
<div class="flex justify-between items-center">
<div class="stat">
<div class="row">
@@ -99,44 +101,44 @@ const progress = $computed(() => {
</div>
<div class="flex gap-2 justify-center items-center">
<BaseIcon
:class="!isSimple?'collect':'fill'"
@click="$emit('toggleSimple')"
:title="(!isSimple ? '标记为已掌握' : '取消标记已掌握')+`(${settingStore.shortcutKeyMap[ShortcutKey.ToggleSimple]})`">
:class="!isSimple?'collect':'fill'"
@click="$emit('toggleSimple')"
:title="(!isSimple ? '标记为已掌握' : '取消标记已掌握')+`(${settingStore.shortcutKeyMap[ShortcutKey.ToggleSimple]})`">
<IconFluentCheckmarkCircle16Regular v-if="!isSimple"/>
<IconFluentCheckmarkCircle16Filled v-else/>
</BaseIcon>
<BaseIcon
:class="!isCollect?'collect':'fill'"
@click.stop="$emit('toggleCollect')"
:title="(!isCollect ? '收藏' : '取消收藏')+`(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`">
:class="!isCollect?'collect':'fill'"
@click.stop="$emit('toggleCollect')"
:title="(!isCollect ? '收藏' : '取消收藏')+`(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`">
<IconFluentStarAdd16Regular v-if="!isCollect"/>
<IconFluentStar16Filled v-else/>
</BaseIcon>
<BaseIcon
@click="emit('skip')"
:title="`跳过(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`">
@click="emit('skip')"
:title="`跳过(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`">
<IconFluentArrowBounce20Regular class="transform-rotate-180"/>
</BaseIcon>
<BaseIcon
@click="settingStore.dictation = !settingStore.dictation"
:title="`开关默写模式(${settingStore.shortcutKeyMap[ShortcutKey.ToggleDictation]})`"
@click="settingStore.dictation = !settingStore.dictation"
:title="`开关默写模式(${settingStore.shortcutKeyMap[ShortcutKey.ToggleDictation]})`"
>
<IconFluentEyeOff16Regular v-if="settingStore.dictation"/>
<IconFluentEye16Regular v-else/>
</BaseIcon>
<BaseIcon
:title="`开关释义显示(${settingStore.shortcutKeyMap[ShortcutKey.ToggleShowTranslate]})`"
@click="settingStore.translate = !settingStore.translate">
:title="`开关释义显示(${settingStore.shortcutKeyMap[ShortcutKey.ToggleShowTranslate]})`"
@click="settingStore.translate = !settingStore.translate">
<IconFluentTranslate16Regular v-if="settingStore.translate"/>
<IconFluentTranslateOff16Regular v-else/>
</BaseIcon>
<BaseIcon
@click="settingStore.showPanel = !settingStore.showPanel"
:title="`单词本(${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`">
@click="settingStore.showPanel = !settingStore.showPanel"
:title="`单词本(${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`">
<IconFluentTextListAbcUppercaseLtr20Regular/>
</BaseIcon>
</div>

View File

@@ -1,15 +1,15 @@
<script setup lang="ts">
import { ShortcutKey, Word } from "@/types/types.ts";
import {ShortcutKey, Word} from "@/types/types.ts";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import { useSettingStore } from "@/stores/setting.ts";
import { usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePlayWordAudio, useTTsPlayAudio } from "@/hooks/sound.ts";
import { emitter, EventKey } from "@/utils/eventBus.ts";
import { nextTick, onMounted, onUnmounted, watch } from "vue";
import {useSettingStore} from "@/stores/setting.ts";
import {usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePlayWordAudio, useTTsPlayAudio} from "@/hooks/sound.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import {nextTick, onMounted, onUnmounted, watch} from "vue";
import Tooltip from "@/components/base/Tooltip.vue";
import SentenceHightLightWord from "@/pages/word/components/SentenceHightLightWord.vue";
import { usePracticeStore } from "@/stores/practice.ts";
import { getDefaultWord } from "@/types/func.ts";
import { _nextTick, sleep } from "@/utils";
import {usePracticeStore} from "@/stores/practice.ts";
import {getDefaultWord} from "@/types/func.ts";
import {_nextTick, sleep} from "@/utils";
interface IProps {
word: Word,
@@ -60,7 +60,9 @@ function updateCurrentWordInfo() {
};
}
watch(() => props.word, () => {
watch(() => props.word, reset, {deep: true})
function reset() {
wrong = input = ''
wordRepeatCount = 0
inputLock = false
@@ -70,7 +72,7 @@ watch(() => props.word, () => {
// 更新当前单词信息
updateCurrentWordInfo();
checkCursorPosition()
}, {deep: true})
}
// 监听输入变化,更新当前单词信息
watch(() => input, () => {
@@ -81,11 +83,7 @@ onMounted(() => {
// 初始化当前单词信息
updateCurrentWordInfo();
emitter.on(EventKey.resetWord, () => {
wrong = input = ''
updateCurrentWordInfo();
})
emitter.on(EventKey.resetWord, reset)
emitter.on(EventKey.onTyping, onTyping)
})
@@ -288,17 +286,17 @@ function checkCursorPosition() {
<div class="typing-word" ref="typingWordRef" v-if="props.word.word.length">
<div class="flex flex-col items-center">
<div class="flex gap-1 mt-26">
<div class="phonetic" v-if="settingStore.soundType === 'us' && word.phonetic0">[{{
(settingStore.dictation && !showFullWord) ? '_'.repeat(word.phonetic0.length) : word.phonetic0
}}]
<div class="phonetic"
:class="(settingStore.dictation && !showFullWord) && 'word-shadow'"
v-if="settingStore.soundType === 'us' && word.phonetic0">[{{ word.phonetic0 }}]
</div>
<div class="phonetic" v-if="settingStore.soundType === 'uk' && word.phonetic1">[{{
(settingStore.dictation && !showFullWord) ? '_'.repeat(word.phonetic1.length) : word.phonetic1
}}]
<div class="phonetic"
:class="(settingStore.dictation && !showFullWord) && 'word-shadow'"
v-if="settingStore.soundType === 'uk' && word.phonetic1">[{{ word.phonetic1 }}]
</div>
<VolumeIcon
:title="`发音(${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
ref="volumeIconRef" :simple="true" :cb="() => playWordAudio(word.word)"/>
:title="`发音(${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
ref="volumeIconRef" :simple="true" :cb="() => playWordAudio(word.word)"/>
</div>
<div class="word my-1"
@@ -331,7 +329,6 @@ function checkCursorPosition() {
</div>
<div class="other">
<div class="line-white my-2"></div>
<template v-if="word?.sentences?.length">
<div class="flex flex-col gap-3">
<div class="sentence" v-for="item in word.sentences">