save
This commit is contained in:
2
Note.md
2
Note.md
@@ -54,3 +54,5 @@ http://enpuz.com/ 语法分析工具
|
||||
背单词页面div,位置应该恒定,不应该随翻译内容变动而跳动
|
||||
|
||||
点击句子播放的音乐,需要可暂停
|
||||
|
||||
footer 的输入数统计有问题,当在列表点一个,然后输入错误之后,不会统计到输入数里面(单词和文章的都有问题)
|
||||
|
||||
@@ -103,12 +103,7 @@ function changeIndex(i: number, dict: Dict) {
|
||||
<div class="dict-name">词数:{{ currentData.list.length }}</div>
|
||||
</header>
|
||||
<div class="content">
|
||||
<WordList
|
||||
class="word-list"
|
||||
@change="(i:number) => changeIndex(i,currentDict)"
|
||||
:isActive="settingStore.showPanel && tabIndex === 0"
|
||||
:list="currentData.list"
|
||||
:activeIndex="currentData.index"/>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<footer v-if="![DictType.customWord,DictType.word].includes(store.current.dictType)">
|
||||
<PopConfirm
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import {$ref} from "vue/macros";
|
||||
import TypingArticle from "./TypingArticle.vue";
|
||||
import {Article, DefaultArticle, TranslateType} from "@/types.ts";
|
||||
import {Article, ArticleWord, DefaultArticle, DefaultWord, DisplayStatistics, TranslateType, Word} from "@/types.ts";
|
||||
import {cloneDeep} from "lodash-es";
|
||||
import TypingWord from "@/components/Practice/PracticeWord/TypingWord.vue";
|
||||
import ArticlePanel from "./ArticlePanel.vue";
|
||||
@@ -10,8 +10,12 @@ import {renewSectionTexts, renewSectionTranslates} from "@/hooks/translate.ts";
|
||||
import {MessageBox} from "@/utils/MessageBox.tsx";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import EditSingleArticleModal from "@/components/Article/EditSingleArticleModal.vue";
|
||||
import {usePracticeStore} from "@/stores/practice.ts";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts";
|
||||
|
||||
const store = useBaseStore()
|
||||
const practiceStore = usePracticeStore()
|
||||
|
||||
let tabIndex = $ref(0)
|
||||
let wordData = $ref({
|
||||
words: [],
|
||||
@@ -42,26 +46,49 @@ onMounted(() => {
|
||||
getCurrentPractice()
|
||||
})
|
||||
|
||||
function setArticle(val: Article) {
|
||||
store.currentDict.articles[store.currentDict.chapterIndex] = cloneDeep(val)
|
||||
articleData.article = cloneDeep(val)
|
||||
practiceStore.inputWordNumber = 0
|
||||
practiceStore.wrongWordNumber = 0
|
||||
practiceStore.repeatNumber = 0
|
||||
practiceStore.total = 0
|
||||
practiceStore.wrongWords = []
|
||||
practiceStore.startDate = Date.now()
|
||||
articleData.article.sections.map((v, i) => {
|
||||
v.map((w, j) => {
|
||||
w.words.map(s => {
|
||||
if (!store.skipWordNamesWithSimpleWords.includes(s.name.toLowerCase()) && !s.isSymbol) {
|
||||
practiceStore.total++
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getCurrentPractice() {
|
||||
// console.log('store.currentDict',store.currentDict)
|
||||
// return
|
||||
if (!store.currentDict.articles.length) return
|
||||
tabIndex = 0
|
||||
articleData.article = cloneDeep(DefaultArticle)
|
||||
|
||||
let currentArticle = store.currentDict.articles[store.currentDict.chapterIndex]
|
||||
let tempArticle = {...DefaultArticle, ...currentArticle}
|
||||
console.log('article', tempArticle)
|
||||
if (tempArticle.sections.length) {
|
||||
articleData.article = tempArticle
|
||||
setArticle(tempArticle)
|
||||
} else {
|
||||
if (tempArticle.useTranslateType === TranslateType.none) {
|
||||
renewSectionTexts(tempArticle)
|
||||
articleData.article = tempArticle
|
||||
setArticle(tempArticle)
|
||||
} else {
|
||||
if (tempArticle.useTranslateType === TranslateType.custom) {
|
||||
if (tempArticle.textCustomTranslate.trim()) {
|
||||
if (tempArticle.textCustomTranslateIsFormat) {
|
||||
renewSectionTexts(tempArticle)
|
||||
renewSectionTranslates(tempArticle, tempArticle.textCustomTranslate)
|
||||
articleData.article = tempArticle
|
||||
setArticle(tempArticle)
|
||||
} else {
|
||||
//说明有本地翻译,但是没格式化成一行一行的
|
||||
MessageBox.confirm('检测到存在本地翻译,但未格式化,是否进行编辑?',
|
||||
@@ -73,7 +100,7 @@ function getCurrentPractice() {
|
||||
() => {
|
||||
renewSectionTexts(tempArticle)
|
||||
tempArticle.useTranslateType = TranslateType.none
|
||||
store.currentDict.articles[store.currentDict.chapterIndex] = articleData.article = tempArticle
|
||||
setArticle(tempArticle)
|
||||
},
|
||||
{
|
||||
confirmButtonText: '去编辑',
|
||||
@@ -92,7 +119,7 @@ function getCurrentPractice() {
|
||||
() => {
|
||||
renewSectionTexts(tempArticle)
|
||||
tempArticle.useTranslateType = TranslateType.none
|
||||
store.currentDict.articles[store.currentDict.chapterIndex] = articleData.article = tempArticle
|
||||
setArticle(tempArticle)
|
||||
},
|
||||
{
|
||||
confirmButtonText: '去编辑',
|
||||
@@ -104,7 +131,7 @@ function getCurrentPractice() {
|
||||
if (tempArticle.useTranslateType === TranslateType.network) {
|
||||
renewSectionTexts(tempArticle)
|
||||
renewSectionTranslates(tempArticle, tempArticle.textNetworkTranslate)
|
||||
store.currentDict.articles[store.currentDict.chapterIndex] = articleData.article = tempArticle
|
||||
setArticle(tempArticle)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,14 +141,67 @@ function saveArticle(val: Article) {
|
||||
console.log('saveArticle', val)
|
||||
showEditArticle = false
|
||||
// articleData.article = cloneDeep(store.currentDict.articles[store.currentDict.chapterIndex])
|
||||
store.currentDict.articles[store.currentDict.chapterIndex] = articleData.article = val
|
||||
setArticle(val)
|
||||
}
|
||||
|
||||
function edit(val: Article) {
|
||||
tabIndex = 1
|
||||
wordData.words = [
|
||||
{
|
||||
...cloneDeep(DefaultWord),
|
||||
name: 'test'
|
||||
}
|
||||
]
|
||||
wordData.index = 0
|
||||
return
|
||||
editArticle = val
|
||||
showEditArticle = true
|
||||
}
|
||||
|
||||
function wrong(word: Word) {
|
||||
let lowerName = word.name.toLowerCase();
|
||||
if (!store.wrong.originWords.find((v: Word) => v.name.toLowerCase() === lowerName)) {
|
||||
store.wrong.originWords.push(word)
|
||||
store.wrong.words.push(word)
|
||||
store.wrong.chapterWords = [store.wrong.words]
|
||||
}
|
||||
if (!store.skipWordNamesWithSimpleWords.includes(lowerName)) {
|
||||
if (!practiceStore.wrongWords.find((v) => v.name.toLowerCase() === lowerName)) {
|
||||
practiceStore.wrongWords.push(word)
|
||||
practiceStore.wrongWordNumber++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function over() {
|
||||
if (practiceStore.wrongWordNumber === 0) {
|
||||
// if (false) {
|
||||
console.log('这章节完了')
|
||||
let now = Date.now()
|
||||
let stat: DisplayStatistics = {
|
||||
startDate: practiceStore.startDate,
|
||||
endDate: now,
|
||||
spend: now - practiceStore.startDate,
|
||||
total: practiceStore.total,
|
||||
correctRate: -1,
|
||||
wrongWordNumber: practiceStore.wrongWordNumber,
|
||||
wrongWords: practiceStore.wrongWords,
|
||||
}
|
||||
stat.correctRate = 100 - Math.trunc(((stat.wrongWordNumber) / (stat.total)) * 100)
|
||||
emitter.emit(EventKey.openStatModal, stat)
|
||||
} else {
|
||||
tabIndex = 1
|
||||
wordData.words = practiceStore.wrongWords
|
||||
wordData.index = 0
|
||||
}
|
||||
}
|
||||
|
||||
function nextWord(word: ArticleWord) {
|
||||
if (!store.skipWordNamesWithSimpleWords.includes(word.name.toLowerCase()) && !word.isSymbol) {
|
||||
practiceStore.inputWordNumber++
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -130,6 +210,11 @@ function edit(val: Article) {
|
||||
<div class="swiper-list" :class="`step${tabIndex}`">
|
||||
<div class="swiper-item">
|
||||
<TypingArticle
|
||||
:active="tabIndex === 0"
|
||||
@edit="edit"
|
||||
@wrong="wrong"
|
||||
@over="over"
|
||||
@nextWord="nextWord"
|
||||
:article="articleData.article"
|
||||
/>
|
||||
</div>
|
||||
@@ -144,7 +229,12 @@ function edit(val: Article) {
|
||||
</div>
|
||||
|
||||
<div class="panel-wrapper">
|
||||
<ArticlePanel :list="[]" v-model:index="index"/>
|
||||
<ArticlePanel
|
||||
v-if="tabIndex === 0"
|
||||
:list="[]"
|
||||
v-model:index="index">
|
||||
1234
|
||||
</ArticlePanel>
|
||||
</div>
|
||||
|
||||
<EditSingleArticleModal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import {computed, nextTick, onMounted, watch} from "vue"
|
||||
import {computed, nextTick, onMounted, onUnmounted, watch} from "vue"
|
||||
import {$computed, $ref} from "vue/macros";
|
||||
import {Article, ArticleWord, DefaultArticle, DisplayStatistics, ShortKeyMap, Word} from "@/types";
|
||||
import {Article, ArticleWord, DefaultArticle, ShortKeyMap, Word} from "@/types";
|
||||
import {useBaseStore} from "@/stores/base";
|
||||
import {usePracticeStore} from "@/stores/practice.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
@@ -9,9 +9,6 @@ import {usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePlayWordAudio} fro
|
||||
import {useOnKeyboardEventListener} from "@/hooks/event.ts";
|
||||
import {cloneDeep} from "lodash-es";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts";
|
||||
import Tooltip from "@/components/Tooltip.vue";
|
||||
import IconWrapper from "@/components/IconWrapper.vue";
|
||||
import {Icon} from "@iconify/vue";
|
||||
import Options from "@/components/Practice/Options.vue";
|
||||
|
||||
interface IProps {
|
||||
@@ -20,6 +17,7 @@ interface IProps {
|
||||
sentenceIndex?: number,
|
||||
wordIndex?: number,
|
||||
stringIndex?: number,
|
||||
active: boolean,
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
@@ -28,17 +26,19 @@ const props = withDefaults(defineProps<IProps>(), {
|
||||
sentenceIndex: 0,
|
||||
wordIndex: 0,
|
||||
stringIndex: 0,
|
||||
active: true,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
ignore: [],
|
||||
next: [],
|
||||
wrong: [val: Word],
|
||||
nextWord: [val: ArticleWord],
|
||||
over: [],
|
||||
edit: [val: Article]
|
||||
}>()
|
||||
|
||||
let isPlay = $ref(false)
|
||||
let articleWrapperRef = $ref<HTMLInputElement>(null)
|
||||
let tabIndex = $ref(0)
|
||||
let sectionIndex = $ref(0)
|
||||
let sentenceIndex = $ref(0)
|
||||
let wordIndex = $ref(0)
|
||||
@@ -50,10 +50,6 @@ let hoverIndex = $ref({
|
||||
sectionIndex: -1,
|
||||
sentenceIndex: -1,
|
||||
})
|
||||
let wordData = $ref({
|
||||
words: [],
|
||||
index: -1
|
||||
})
|
||||
const currentIndex = computed(() => {
|
||||
return `${sectionIndex}${sentenceIndex}${wordIndex}`
|
||||
})
|
||||
@@ -75,27 +71,9 @@ watch(() => props.article, () => {
|
||||
sentenceIndex = props.sentenceIndex
|
||||
wordIndex = props.wordIndex
|
||||
stringIndex = props.stringIndex
|
||||
|
||||
tabIndex = 0
|
||||
practiceStore.inputWordNumber = 0
|
||||
practiceStore.wrongWordNumber = 0
|
||||
practiceStore.repeatNumber = 0
|
||||
practiceStore.total = 0
|
||||
props.article.sections.map((v, i) => {
|
||||
v.map((w, j) => {
|
||||
w.words.map(s => {
|
||||
if (!store.skipWordNamesWithSimpleWords.includes(s.name.toLowerCase()) && !s.isSymbol) {
|
||||
practiceStore.total++
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
practiceStore.wrongWords = []
|
||||
practiceStore.startDate = Date.now()
|
||||
calcTranslateLocation()
|
||||
}, {immediate: true})
|
||||
|
||||
|
||||
watch(() => settingStore.dictation, () => {
|
||||
calcTranslateLocation()
|
||||
})
|
||||
@@ -104,8 +82,143 @@ onMounted(() => {
|
||||
emitter.on(EventKey.resetWord, () => {
|
||||
wrong = input = ''
|
||||
})
|
||||
emitter.on(EventKey.onTyping, onTyping)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
emitter.off(EventKey.resetWord,)
|
||||
emitter.off(EventKey.onTyping, onTyping)
|
||||
})
|
||||
|
||||
function nextSentence() {
|
||||
// wordData.words = [
|
||||
// {"name": "pharmacy", "trans": ["药房;配药学,药剂学;制药业;一批备用药品"], "usphone": "'fɑrməsi", "ukphone": "'fɑːməsɪ"},
|
||||
// // {"name": "foregone", "trans": ["过去的;先前的;预知的;预先决定的", "发生在…之前(forego的过去分词)"], "usphone": "'fɔrɡɔn", "ukphone": "fɔː'gɒn"}, {"name": "president", "trans": ["总统;董事长;校长;主席"], "usphone": "'prɛzɪdənt", "ukphone": "'prezɪd(ə)nt"}, {"name": "plastic", "trans": ["塑料的;(外科)造型的;可塑的", "塑料制品;整形;可塑体"], "usphone": "'plæstɪk", "ukphone": "'plæstɪk"}, {"name": "provisionally", "trans": ["临时地,暂时地"], "usphone": "", "ukphone": ""}, {"name": "incentive", "trans": ["动机;刺激", "激励的;刺激的"], "usphone": "ɪn'sɛntɪv", "ukphone": "ɪn'sentɪv"}, {"name": "calculate", "trans": ["计算;以为;作打算"], "usphone": "'kælkjulet", "ukphone": "'kælkjʊleɪt"}
|
||||
// ]
|
||||
// return
|
||||
|
||||
let currentSection = props.article.sections[sectionIndex]
|
||||
|
||||
isSpace = false
|
||||
stringIndex = 0
|
||||
wordIndex = 0
|
||||
input = wrong = ''
|
||||
|
||||
//todo 计得把略过的单词加上统计里面去
|
||||
// if (!store.skipWordNamesWithSimpleWords.includes(currentWord.name.toLowerCase()) && !currentWord.isSymbol) {
|
||||
// practiceStore.inputNumber++
|
||||
// }
|
||||
|
||||
sentenceIndex++
|
||||
if (!currentSection[sentenceIndex]) {
|
||||
sentenceIndex = 0
|
||||
sectionIndex++
|
||||
if (!props.article.sections[sectionIndex]) {
|
||||
console.log('打完了')
|
||||
emit('over')
|
||||
}
|
||||
} else {
|
||||
if (settingStore.dictation) {
|
||||
calcTranslateLocation()
|
||||
}
|
||||
playWordAudio(currentSection[sentenceIndex].text)
|
||||
}
|
||||
}
|
||||
|
||||
function onTyping(e: KeyboardEvent) {
|
||||
if (!props.active) return
|
||||
if (!props.article.sections.length) return
|
||||
// console.log('keyDown', e.key, e.code, e.keyCode)
|
||||
wrong = ''
|
||||
let currentSection = props.article.sections[sectionIndex]
|
||||
let currentSentence = currentSection[sentenceIndex]
|
||||
let currentWord: ArticleWord = currentSentence.words[wordIndex]
|
||||
|
||||
const nextWord = () => {
|
||||
isSpace = false
|
||||
stringIndex = 0
|
||||
wordIndex++
|
||||
|
||||
emit('nextWord', currentWord)
|
||||
|
||||
|
||||
|
||||
if (!currentSentence.words[wordIndex]) {
|
||||
wordIndex = 0
|
||||
sentenceIndex++
|
||||
if (!currentSection[sentenceIndex]) {
|
||||
sentenceIndex = 0
|
||||
sectionIndex++
|
||||
|
||||
if (!props.article.sections[sectionIndex]) {
|
||||
console.log('打完了')
|
||||
}
|
||||
} else {
|
||||
if (settingStore.dictation) {
|
||||
calcTranslateLocation()
|
||||
}
|
||||
playWordAudio(currentSection[sentenceIndex].text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isSpace) {
|
||||
if (e.code === 'Space') {
|
||||
nextWord()
|
||||
} else {
|
||||
wrong = ' '
|
||||
playBeep()
|
||||
|
||||
setTimeout(() => {
|
||||
wrong = ''
|
||||
wrong = input = ''
|
||||
}, 500)
|
||||
}
|
||||
playKeyboardAudio()
|
||||
} else {
|
||||
let letter = e.key
|
||||
|
||||
let key = currentWord.name[stringIndex]
|
||||
// console.log('key', key,)
|
||||
|
||||
let isRight = false
|
||||
if (settingStore.ignoreCase) {
|
||||
isRight = key.toLowerCase() === letter.toLowerCase()
|
||||
} else {
|
||||
isRight = key === letter
|
||||
}
|
||||
if (isRight) {
|
||||
input += letter
|
||||
wrong = ''
|
||||
// console.log('匹配上了')
|
||||
stringIndex++
|
||||
//如果当前词没有index,说明这个词完了,下一个是空格
|
||||
if (!currentWord.name[stringIndex]) {
|
||||
input = wrong = ''
|
||||
if (!currentWord.isSymbol) {
|
||||
playCorrect()
|
||||
}
|
||||
if (currentWord.nextSpace) {
|
||||
isSpace = true
|
||||
} else {
|
||||
nextWord()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
emit('wrong', currentWord)
|
||||
wrong = letter
|
||||
playBeep()
|
||||
setTimeout(() => {
|
||||
wrong = ''
|
||||
}, 500)
|
||||
// console.log('未匹配')
|
||||
}
|
||||
playKeyboardAudio()
|
||||
}
|
||||
e.preventDefault()
|
||||
|
||||
}
|
||||
|
||||
function calcTranslateLocation() {
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
@@ -148,201 +261,31 @@ function play() {
|
||||
}
|
||||
|
||||
function onKeyDown(e: KeyboardEvent) {
|
||||
if (tabIndex !== 0) return
|
||||
if (!props.article.sections.length) return
|
||||
// console.log('keyDown', e.key, e.code, e.keyCode)
|
||||
wrong = ''
|
||||
let currentSection = props.article.sections[sectionIndex]
|
||||
let currentSentence = currentSection[sentenceIndex]
|
||||
let currentWord: ArticleWord = currentSentence.words[wordIndex]
|
||||
|
||||
const nextWord = () => {
|
||||
isSpace = false
|
||||
stringIndex = 0
|
||||
wordIndex++
|
||||
|
||||
if (!store.skipWordNamesWithSimpleWords.includes(currentWord.name.toLowerCase()) && !currentWord.isSymbol) {
|
||||
practiceStore.inputWordNumber++
|
||||
}
|
||||
|
||||
if (!currentSentence.words[wordIndex]) {
|
||||
wordIndex = 0
|
||||
sentenceIndex++
|
||||
if (!currentSection[sentenceIndex]) {
|
||||
sentenceIndex = 0
|
||||
sectionIndex++
|
||||
|
||||
if (!props.article.sections[sectionIndex]) {
|
||||
console.log('打完了')
|
||||
}
|
||||
} else {
|
||||
if (settingStore.dictation) {
|
||||
calcTranslateLocation()
|
||||
}
|
||||
playWordAudio(currentSection[sentenceIndex].text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const nextSentence = () => {
|
||||
// tabIndex = 1
|
||||
// // wordData.words = practiceStore.wrongWords
|
||||
// wordData.words = [
|
||||
// {"name": "pharmacy", "trans": ["药房;配药学,药剂学;制药业;一批备用药品"], "usphone": "'fɑrməsi", "ukphone": "'fɑːməsɪ"},
|
||||
// // {"name": "foregone", "trans": ["过去的;先前的;预知的;预先决定的", "发生在…之前(forego的过去分词)"], "usphone": "'fɔrɡɔn", "ukphone": "fɔː'gɒn"}, {"name": "president", "trans": ["总统;董事长;校长;主席"], "usphone": "'prɛzɪdənt", "ukphone": "'prezɪd(ə)nt"}, {"name": "plastic", "trans": ["塑料的;(外科)造型的;可塑的", "塑料制品;整形;可塑体"], "usphone": "'plæstɪk", "ukphone": "'plæstɪk"}, {"name": "provisionally", "trans": ["临时地,暂时地"], "usphone": "", "ukphone": ""}, {"name": "incentive", "trans": ["动机;刺激", "激励的;刺激的"], "usphone": "ɪn'sɛntɪv", "ukphone": "ɪn'sentɪv"}, {"name": "calculate", "trans": ["计算;以为;作打算"], "usphone": "'kælkjulet", "ukphone": "'kælkjʊleɪt"}
|
||||
// ]
|
||||
// return
|
||||
|
||||
isSpace = false
|
||||
stringIndex = 0
|
||||
wordIndex = 0
|
||||
input = wrong = ''
|
||||
|
||||
//todo 计得把略过的单词加上统计里面去
|
||||
// if (!store.skipWordNamesWithSimpleWords.includes(currentWord.name.toLowerCase()) && !currentWord.isSymbol) {
|
||||
// practiceStore.inputNumber++
|
||||
// }
|
||||
|
||||
sentenceIndex++
|
||||
if (!currentSection[sentenceIndex]) {
|
||||
sentenceIndex = 0
|
||||
sectionIndex++
|
||||
if (!props.article.sections[sectionIndex]) {
|
||||
console.log('打完了')
|
||||
if (practiceStore.wrongWordNumber === 0) {
|
||||
// if (false) {
|
||||
console.log('这章节完了')
|
||||
let now = Date.now()
|
||||
let stat: DisplayStatistics = {
|
||||
startDate: practiceStore.startDate,
|
||||
endDate: now,
|
||||
spend: now - practiceStore.startDate,
|
||||
total: practiceStore.total,
|
||||
correctRate: -1,
|
||||
wrongWordNumber: practiceStore.wrongWordNumber,
|
||||
wrongWords: practiceStore.wrongWords,
|
||||
}
|
||||
stat.correctRate = 100 - Math.trunc(((stat.wrongWordNumber) / (stat.total)) * 100)
|
||||
emitter.emit(EventKey.openStatModal, stat)
|
||||
} else {
|
||||
tabIndex = 1
|
||||
wordData.words = practiceStore.wrongWords
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (settingStore.dictation) {
|
||||
calcTranslateLocation()
|
||||
}
|
||||
playWordAudio(currentSection[sentenceIndex].text)
|
||||
}
|
||||
}
|
||||
//非英文模式下,输入区域的 keyCode 均为 229时,
|
||||
if ((e.keyCode >= 65 && e.keyCode <= 90)
|
||||
|| (e.keyCode >= 48 && e.keyCode <= 57)
|
||||
|| e.code === 'Space'
|
||||
|| e.code === 'Slash'
|
||||
|| e.code === 'Quote'
|
||||
|| e.code === 'Comma'
|
||||
|| e.code === 'BracketLeft'
|
||||
|| e.code === 'BracketRight'
|
||||
|| e.code === 'Period'
|
||||
|| e.code === 'Minus'
|
||||
|| e.code === 'Equal'
|
||||
|| e.code === 'Semicolon'
|
||||
|| e.code === 'Backquote'
|
||||
|| e.keyCode === 229
|
||||
) {
|
||||
if (isSpace) {
|
||||
if (e.code === 'Space') {
|
||||
nextWord()
|
||||
} else {
|
||||
wrong = ' '
|
||||
playBeep()
|
||||
|
||||
setTimeout(() => {
|
||||
wrong = ''
|
||||
wrong = input = ''
|
||||
}, 500)
|
||||
}
|
||||
playKeyboardAudio()
|
||||
} else {
|
||||
let letter = e.key
|
||||
|
||||
let key = currentWord.name[stringIndex]
|
||||
// console.log('key', key,)
|
||||
|
||||
let isWrong = false
|
||||
if (settingStore.ignoreCase) {
|
||||
isWrong = key.toLowerCase() !== letter.toLowerCase()
|
||||
} else {
|
||||
isWrong = key !== letter
|
||||
}
|
||||
if (!isWrong) {
|
||||
input += letter
|
||||
if (!props.active) return
|
||||
switch (e.key) {
|
||||
case 'Backspace':
|
||||
if (wrong) {
|
||||
wrong = ''
|
||||
// console.log('匹配上了')
|
||||
stringIndex++
|
||||
//如果当前词没有index,说明这个词完了,下一个是空格
|
||||
if (!currentWord.name[stringIndex]) {
|
||||
input = wrong = ''
|
||||
if (!currentWord.isSymbol) {
|
||||
playCorrect()
|
||||
}
|
||||
if (currentWord.nextSpace) {
|
||||
isSpace = true
|
||||
} else {
|
||||
nextWord()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!store.wrong.originWords.find((v: Word) => v.name.toLowerCase() === currentWord.name.toLowerCase())) {
|
||||
store.wrong.originWords.push(currentWord)
|
||||
store.wrong.words.push(currentWord)
|
||||
store.wrong.chapterWords = [store.wrong.words]
|
||||
}
|
||||
|
||||
if (!store.skipWordNamesWithSimpleWords.includes(currentWord.name.toLowerCase())) {
|
||||
if (!practiceStore.wrongWords.find((v) => v.name.toLowerCase() === currentWord.name.toLowerCase())) {
|
||||
practiceStore.wrongWords.push(currentWord)
|
||||
practiceStore.wrongWordNumber++
|
||||
}
|
||||
}
|
||||
|
||||
wrong = letter
|
||||
playBeep()
|
||||
setTimeout(() => {
|
||||
wrong = ''
|
||||
}, 500)
|
||||
// console.log('未匹配')
|
||||
input = input.slice(0, -1)
|
||||
}
|
||||
playKeyboardAudio()
|
||||
}
|
||||
} else {
|
||||
switch (e.key) {
|
||||
case 'Backspace':
|
||||
if (wrong) {
|
||||
wrong = ''
|
||||
} else {
|
||||
input = input.slice(0, -1)
|
||||
}
|
||||
break
|
||||
case ShortKeyMap.Collect:
|
||||
break
|
||||
case ShortKeyMap.Collect:
|
||||
|
||||
break
|
||||
case ShortKeyMap.Remove:
|
||||
break
|
||||
case ShortKeyMap.Ignore:
|
||||
nextSentence()
|
||||
break
|
||||
case ShortKeyMap.Show:
|
||||
if (settingStore.allowWordTip) {
|
||||
hoverIndex = {
|
||||
sectionIndex: sectionIndex,
|
||||
sentenceIndex: sentenceIndex,
|
||||
}
|
||||
break
|
||||
case ShortKeyMap.Remove:
|
||||
break
|
||||
case ShortKeyMap.Ignore:
|
||||
nextSentence()
|
||||
break
|
||||
case ShortKeyMap.Show:
|
||||
if (settingStore.allowWordTip) {
|
||||
hoverIndex = {
|
||||
sectionIndex: sectionIndex,
|
||||
sentenceIndex: sentenceIndex,
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// console.log(
|
||||
|
||||
@@ -13,7 +13,6 @@ let wordData = $ref({
|
||||
words: [],
|
||||
index: -1
|
||||
})
|
||||
let index = $ref(0)
|
||||
|
||||
watch([
|
||||
() => store.load,
|
||||
@@ -38,15 +37,14 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="practice-word">
|
||||
<TypingWord/>
|
||||
<div class="panel-wrapper">
|
||||
<WordPanel :list="[]" v-model:index="index"/>
|
||||
<!-- <WordPanel :list="data.words" v-model:index="data.index"/>-->
|
||||
</div>
|
||||
<div class="practice">
|
||||
<TypingWord :words="wordData.words" :index="wordData.index"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.practice {
|
||||
//height: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -6,10 +6,9 @@ import {useBaseStore} from "@/stores/base.ts";
|
||||
import {usePracticeStore} from "@/stores/practice.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePlayWordAudio} from "@/hooks/sound.ts";
|
||||
import {onMounted} from "vue/dist/vue";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts";
|
||||
import {cloneDeep} from "lodash-es";
|
||||
import {onUnmounted, watch} from "vue";
|
||||
import {onUnmounted, watch, onMounted} from "vue";
|
||||
|
||||
interface IProps {
|
||||
word: Word,
|
||||
@@ -30,8 +29,6 @@ let showFullWord = $ref(false)
|
||||
//输入锁定,因为跳转到下一个单词有延时,如果重复在延时期间内重复输入,导致会跳转N次
|
||||
let inputLock = false
|
||||
let wordRepeatCount = 0
|
||||
const store = useBaseStore()
|
||||
const practiceStore = usePracticeStore()
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
const playBeep = usePlayBeep()
|
||||
@@ -52,6 +49,7 @@ watch(() => props.word, () => {
|
||||
volumeIconRef?.play()
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
emitter.on(EventKey.resetWord, () => {
|
||||
wrong = input = ''
|
||||
@@ -65,7 +63,6 @@ onUnmounted(() => {
|
||||
emitter.off(EventKey.onTyping, onTyping)
|
||||
})
|
||||
|
||||
|
||||
function repeat() {
|
||||
setTimeout(() => {
|
||||
wrong = input = ''
|
||||
@@ -78,7 +75,6 @@ function repeat() {
|
||||
}, settingStore.waitTimeForChangeWord)
|
||||
}
|
||||
|
||||
|
||||
async function onTyping(e: KeyboardEvent) {
|
||||
if (inputLock) return
|
||||
inputLock = true
|
||||
@@ -86,11 +82,11 @@ async function onTyping(e: KeyboardEvent) {
|
||||
let isTypingRight = false
|
||||
let isWordRight = false
|
||||
if (settingStore.ignoreCase) {
|
||||
isTypingRight = letter.toLowerCase() === props.word.name[input.length + 1].toLowerCase()
|
||||
isWordRight = letter.toLowerCase() === props.word.name.slice(-1).toLowerCase()
|
||||
isTypingRight = letter.toLowerCase() === props.word.name[input.length].toLowerCase()
|
||||
isWordRight = (input + letter).toLowerCase() === props.word.name.toLowerCase()
|
||||
} else {
|
||||
isTypingRight = letter === props.word.name[input.length + 1]
|
||||
isWordRight = letter === props.word.name.slice(-1)
|
||||
isTypingRight = letter === props.word.name[input.length]
|
||||
isWordRight = (input + letter) === props.word.name
|
||||
}
|
||||
if (isTypingRight) {
|
||||
input += letter
|
||||
@@ -126,6 +122,27 @@ async function onTyping(e: KeyboardEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
function del() {
|
||||
playKeyboardAudio()
|
||||
|
||||
if (wrong) {
|
||||
wrong = ''
|
||||
} else {
|
||||
input = input.slice(0, -1)
|
||||
}
|
||||
}
|
||||
|
||||
function showWord() {
|
||||
if (settingStore.allowWordTip) {
|
||||
showFullWord = true
|
||||
}
|
||||
}
|
||||
|
||||
function hideWord() {
|
||||
showFullWord = false
|
||||
}
|
||||
|
||||
defineExpose({del, showWord, hideWord})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -154,7 +171,7 @@ async function onTyping(e: KeyboardEvent) {
|
||||
</template>
|
||||
<span class="letter" v-else>{{ displayWord }}</span>
|
||||
</div>
|
||||
<VolumeIcon ref="volumeIconRef" :simple="true" :cb="playWordAudio(word.name)"/>
|
||||
<VolumeIcon ref="volumeIconRef" :simple="true" :cb="()=>playWordAudio(word.name)"/>
|
||||
</div>
|
||||
<div class="phonetic">{{ settingStore.wordSoundType === 'us' ? word.usphone : word.ukphone }}</div>
|
||||
</div>
|
||||
@@ -164,6 +181,11 @@ async function onTyping(e: KeyboardEvent) {
|
||||
@import "@/assets/css/variable";
|
||||
|
||||
.typing-word {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.phonetic, .translate {
|
||||
font-size: 20rem;
|
||||
margin-left: -30rem;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import {onMounted, watch} from "vue"
|
||||
import {watch} from "vue"
|
||||
import {$computed, $ref} from "vue/macros"
|
||||
import {useBaseStore} from "@/stores/base.ts"
|
||||
import {DictType, DisplayStatistics, ShortKeyMap, Word} from "../../../types";
|
||||
@@ -7,13 +7,12 @@ import {emitter, EventKey} from "@/utils/eventBus.ts"
|
||||
import {cloneDeep} from "lodash-es"
|
||||
import {usePracticeStore} from "@/stores/practice.ts"
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePlayWordAudio} from "@/hooks/sound.ts";
|
||||
import {useOnKeyboardEventListener} from "@/hooks/event.ts";
|
||||
import {Icon} from "@iconify/vue";
|
||||
import VolumeIcon from "@/components/VolumeIcon.vue";
|
||||
import Tooltip from "@/components/Tooltip.vue";
|
||||
import Options from "@/components/Practice/Options.vue";
|
||||
import Typing from "@/components/Practice/PracticeWord/Typing.vue";
|
||||
import WordPanel from "@/components/Practice/PracticeWord/WordPanel.vue";
|
||||
|
||||
interface IProps {
|
||||
words: Word[],
|
||||
@@ -32,8 +31,7 @@ let data = $ref({
|
||||
originWrongWords: [],
|
||||
})
|
||||
|
||||
let input = $ref('')
|
||||
let wrong = $ref('')
|
||||
let typingRef: any = $ref()
|
||||
const store = useBaseStore()
|
||||
const practiceStore = usePracticeStore()
|
||||
const settingStore = useSettingStore()
|
||||
@@ -70,10 +68,6 @@ const nextWord: Word = $computed(() => {
|
||||
return data.words?.[data.index + 1] ?? undefined
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
})
|
||||
|
||||
function next(isTyping: boolean = true) {
|
||||
if (data.index === data.words.length - 1) {
|
||||
if (data.wrongWords.length) {
|
||||
@@ -83,9 +77,8 @@ function next(isTyping: boolean = true) {
|
||||
if (!data.originWrongWords.length) {
|
||||
data.originWrongWords = cloneDeep(data.wrongWords)
|
||||
}
|
||||
data.index = 0
|
||||
practiceStore.total = data.words.length
|
||||
practiceStore.index = 0
|
||||
practiceStore.index = data.index = 0
|
||||
practiceStore.inputWordNumber = 0
|
||||
practiceStore.wrongWordNumber = 0
|
||||
practiceStore.repeatNumber++
|
||||
@@ -143,7 +136,7 @@ function remove() {
|
||||
}
|
||||
|
||||
function onKeyUp(e: KeyboardEvent) {
|
||||
// showFullWord = false
|
||||
typingRef.hideWord()
|
||||
}
|
||||
|
||||
function wordWrong() {
|
||||
@@ -162,11 +155,7 @@ async function onKeyDown(e: KeyboardEvent) {
|
||||
// console.log('e', e)
|
||||
switch (e.key) {
|
||||
case 'Backspace':
|
||||
if (wrong) {
|
||||
wrong = ''
|
||||
} else {
|
||||
input = input.slice(0, -1)
|
||||
}
|
||||
typingRef.del()
|
||||
break
|
||||
case ShortKeyMap.Collect:
|
||||
collect()
|
||||
@@ -179,9 +168,7 @@ async function onKeyDown(e: KeyboardEvent) {
|
||||
e.preventDefault()
|
||||
break
|
||||
case ShortKeyMap.Show:
|
||||
if (settingStore.allowWordTip) {
|
||||
// showFullWord = true
|
||||
}
|
||||
typingRef.showWord()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -191,7 +178,7 @@ useOnKeyboardEventListener(onKeyDown, onKeyUp)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="type-word">
|
||||
<div class="practice-word">
|
||||
<div class="near-word" v-if="settingStore.showNearWord">
|
||||
<div class="prev"
|
||||
@click="prev"
|
||||
@@ -209,6 +196,7 @@ useOnKeyboardEventListener(onKeyDown, onKeyUp)
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Typing
|
||||
ref="typingRef"
|
||||
:word="word"
|
||||
@wrong="wordWrong"
|
||||
@next="next"
|
||||
@@ -218,13 +206,15 @@ useOnKeyboardEventListener(onKeyDown, onKeyUp)
|
||||
@skip="skip"
|
||||
@collect="collect"
|
||||
/>
|
||||
<WordPanel :list="data.words" v-model:index="data.index"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/assets/css/variable";
|
||||
|
||||
.type-word {
|
||||
.practice-word {
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
//display: none;
|
||||
@@ -280,5 +270,6 @@ useOnKeyboardEventListener(onKeyDown, onKeyUp)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -1,431 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import {onMounted, watch} from "vue"
|
||||
import {$computed, $ref} from "vue/macros"
|
||||
import {useBaseStore} from "@/stores/base.ts"
|
||||
import {DictType, DisplayStatistics, ShortKeyMap, Word} from "../../../types";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts"
|
||||
import {cloneDeep} from "lodash-es"
|
||||
import {usePracticeStore} from "@/stores/practice.ts"
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePlayWordAudio} from "@/hooks/sound.ts";
|
||||
import {useOnKeyboardEventListener} from "@/hooks/event.ts";
|
||||
import {Icon} from "@iconify/vue";
|
||||
import VolumeIcon from "@/components/VolumeIcon.vue";
|
||||
import Tooltip from "@/components/Tooltip.vue";
|
||||
import Options from "@/components/Practice/Options.vue";
|
||||
|
||||
interface IProps {
|
||||
words: Word[],
|
||||
index: number,
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
words: [],
|
||||
index: -1
|
||||
})
|
||||
|
||||
let data = $ref({
|
||||
index: props.index,
|
||||
words: props.words,
|
||||
wrongWords: [],
|
||||
originWrongWords: [],
|
||||
})
|
||||
|
||||
let input = $ref('')
|
||||
let wrong = $ref('')
|
||||
let showFullWord = $ref(false)
|
||||
//输入锁定,因为跳转到下一个单词有延时,如果重复在延时期间内重复输入,导致会跳转N次
|
||||
let inputLock = $ref(false)
|
||||
let wordRepeatCount = $ref(0)
|
||||
const store = useBaseStore()
|
||||
const practiceStore = usePracticeStore()
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
const playBeep = usePlayBeep()
|
||||
const playCorrect = usePlayCorrect()
|
||||
const playKeyboardAudio = usePlayKeyboardAudio()
|
||||
const playWordAudio = usePlayWordAudio()
|
||||
const volumeIconRef: any = $ref()
|
||||
|
||||
watch(() => props.words, () => {
|
||||
data.words = props.words
|
||||
data.index = props.index
|
||||
data.originWrongWords = []
|
||||
data.wrongWords = []
|
||||
|
||||
practiceStore.wrongWords = []
|
||||
practiceStore.repeatNumber = 0
|
||||
practiceStore.startDate = Date.now()
|
||||
practiceStore.correctRate = -1
|
||||
practiceStore.total = props.words.length
|
||||
practiceStore.inputWordNumber = 0
|
||||
practiceStore.wrongWordNumber = 0
|
||||
}, {immediate: true})
|
||||
|
||||
watch(() => data.index, (n) => {
|
||||
wrong = input = ''
|
||||
practiceStore.index = n
|
||||
wordRepeatCount = 0
|
||||
inputLock = false
|
||||
if (settingStore.wordSound) {
|
||||
playWordAudio(word.name)
|
||||
volumeIconRef?.play()
|
||||
}
|
||||
})
|
||||
|
||||
const word = $computed(() => {
|
||||
return data.words[data.index] ?? {
|
||||
trans: [],
|
||||
name: '',
|
||||
usphone: '',
|
||||
ukphone: '',
|
||||
}
|
||||
})
|
||||
|
||||
const prevWord: Word = $computed(() => {
|
||||
return data.words?.[data.index - 1] ?? undefined
|
||||
})
|
||||
|
||||
const nextWord: Word = $computed(() => {
|
||||
return data.words?.[data.index + 1] ?? undefined
|
||||
})
|
||||
|
||||
let resetWord = $computed(() => {
|
||||
return word.name.slice(input.length + wrong.length)
|
||||
})
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
emitter.on(EventKey.resetWord, () => {
|
||||
wrong = input = ''
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
function next(isTyping: boolean = true) {
|
||||
if (data.index === data.words.length - 1) {
|
||||
if (data.wrongWords.length) {
|
||||
console.log('当前背完了,但还有错词')
|
||||
data.words = cloneDeep(data.wrongWords)
|
||||
if (!data.originWrongWords.length) {
|
||||
data.originWrongWords = cloneDeep(data.wrongWords)
|
||||
}
|
||||
data.index = 0
|
||||
practiceStore.total = data.words.length
|
||||
practiceStore.index = 0
|
||||
practiceStore.inputWordNumber = 0
|
||||
practiceStore.wrongWordNumber = 0
|
||||
practiceStore.repeatNumber++
|
||||
data.wrongWords = []
|
||||
} else {
|
||||
console.log('这章节完了')
|
||||
isTyping && practiceStore.inputWordNumber++
|
||||
let now = Date.now()
|
||||
let stat: DisplayStatistics = {
|
||||
startDate: practiceStore.startDate,
|
||||
endDate: now,
|
||||
spend: now - practiceStore.startDate,
|
||||
total: props.words.length,
|
||||
correctRate: -1,
|
||||
wrongWordNumber: data.originWrongWords.length,
|
||||
wrongWords: data.originWrongWords,
|
||||
}
|
||||
stat.correctRate = 100 - Math.trunc(((stat.wrongWordNumber) / (stat.total)) * 100)
|
||||
emitter.emit(EventKey.openStatModal, stat)
|
||||
}
|
||||
} else {
|
||||
data.index++
|
||||
isTyping && practiceStore.inputWordNumber++
|
||||
console.log('这个词完了')
|
||||
if ([DictType.customWord, DictType.word].includes(store.current.dictType)
|
||||
&& store.skipWordNames.includes(word.name.toLowerCase())) {
|
||||
next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function prev() {
|
||||
data.index--
|
||||
}
|
||||
|
||||
function skip() {
|
||||
next(false)
|
||||
}
|
||||
|
||||
function collect() {
|
||||
if (!store.collect.originWords.find((v: Word) => v.name.toLowerCase() === word.name.toLowerCase())) {
|
||||
store.collect.originWords.push(word)
|
||||
store.collect.words.push(word)
|
||||
store.collect.chapterWords = [store.collect.words]
|
||||
}
|
||||
}
|
||||
|
||||
function remove() {
|
||||
if (!store.skipWordNames.includes(word.name.toLowerCase())) {
|
||||
store.skip.originWords.push(word)
|
||||
store.skip.words.push(word)
|
||||
store.skip.chapterWords = [store.skip.words]
|
||||
}
|
||||
next(false)
|
||||
}
|
||||
|
||||
function onKeyUp(e: KeyboardEvent) {
|
||||
showFullWord = false
|
||||
}
|
||||
|
||||
function repeat() {
|
||||
setTimeout(() => {
|
||||
wrong = input = ''
|
||||
wordRepeatCount++
|
||||
inputLock = false
|
||||
|
||||
if (settingStore.wordSound) {
|
||||
playWordAudio(word.name)
|
||||
volumeIconRef?.play()
|
||||
}
|
||||
}, settingStore.waitTimeForChangeWord)
|
||||
}
|
||||
|
||||
async function onKeyDown(e: KeyboardEvent) {
|
||||
//TODO 还有横杠
|
||||
//非英文模式下,输入区域的 keyCode 均为 229时,
|
||||
if ((e.keyCode >= 65 && e.keyCode <= 90)
|
||||
|| (e.keyCode >= 48 && e.keyCode <= 57)
|
||||
|| e.code === 'Space'
|
||||
|| e.code === 'Slash'
|
||||
|| e.code === 'Quote'
|
||||
|| e.code === 'Comma'
|
||||
|| e.code === 'BracketLeft'
|
||||
|| e.code === 'BracketRight'
|
||||
|| e.code === 'Period'
|
||||
|| e.code === 'Minus'
|
||||
|| e.code === 'Equal'
|
||||
|| e.code === 'Semicolon'
|
||||
|| e.code === 'Backquote'
|
||||
|| e.keyCode === 229
|
||||
) {
|
||||
if (inputLock) return
|
||||
inputLock = true
|
||||
let letter = e.key
|
||||
let isWrong = false
|
||||
if (settingStore.ignoreCase) {
|
||||
isWrong = (input + letter).toLowerCase() !== word.name.toLowerCase().slice(0, input.length + 1)
|
||||
} else {
|
||||
isWrong = (input + letter) !== word.name.slice(0, input.length + 1)
|
||||
}
|
||||
if (isWrong) {
|
||||
if (!store.wrong.originWords.find((v: Word) => v.name.toLowerCase() === word.name.toLowerCase())) {
|
||||
store.wrong.originWords.push(word)
|
||||
store.wrong.words.push(word)
|
||||
store.wrong.chapterWords = [store.wrong.words]
|
||||
}
|
||||
if (!data.wrongWords.find((v: Word) => v.name.toLowerCase() === word.name.toLowerCase())) {
|
||||
data.wrongWords.push(word)
|
||||
practiceStore.wrongWordNumber++
|
||||
}
|
||||
wrong = letter
|
||||
playKeyboardAudio()
|
||||
playBeep()
|
||||
setTimeout(() => {
|
||||
wrong = ''
|
||||
}, 500)
|
||||
} else {
|
||||
input += letter
|
||||
wrong = ''
|
||||
playKeyboardAudio()
|
||||
}
|
||||
if (input.toLowerCase() === word.name.toLowerCase()) {
|
||||
playCorrect()
|
||||
if (settingStore.repeatCount == 100) {
|
||||
if (settingStore.repeatCustomCount <= wordRepeatCount + 1) {
|
||||
setTimeout(next, settingStore.waitTimeForChangeWord)
|
||||
} else {
|
||||
repeat()
|
||||
}
|
||||
} else {
|
||||
if (settingStore.repeatCount <= wordRepeatCount + 1) {
|
||||
setTimeout(next, settingStore.waitTimeForChangeWord)
|
||||
} else {
|
||||
repeat()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
inputLock = false
|
||||
}
|
||||
} else {
|
||||
// console.log('e', e)
|
||||
switch (e.key) {
|
||||
case 'Backspace':
|
||||
if (wrong) {
|
||||
wrong = ''
|
||||
} else {
|
||||
input = input.slice(0, -1)
|
||||
}
|
||||
break
|
||||
case ShortKeyMap.Collect:
|
||||
collect()
|
||||
break
|
||||
case ShortKeyMap.Remove:
|
||||
remove()
|
||||
break
|
||||
case ShortKeyMap.Ignore:
|
||||
skip()
|
||||
e.preventDefault()
|
||||
break
|
||||
case ShortKeyMap.Show:
|
||||
if (settingStore.allowWordTip) {
|
||||
showFullWord = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useOnKeyboardEventListener(onKeyDown, onKeyUp)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="type-word">
|
||||
<div class="near-word" v-if="settingStore.showNearWord">
|
||||
<div class="prev"
|
||||
@click="prev"
|
||||
v-if="prevWord">
|
||||
<Icon class="arrow" icon="bi:arrow-left" width="22"/>
|
||||
<div class="word">{{ prevWord.name }}</div>
|
||||
</div>
|
||||
<Tooltip title="快捷键:Tab">
|
||||
<div class="next"
|
||||
@click="next(false)"
|
||||
v-if="nextWord">
|
||||
<div class="word" :class="settingStore.dictation && 'shadow'">{{ nextWord.name }}</div>
|
||||
<Icon class="arrow" icon="bi:arrow-right" width="22"/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="translate"
|
||||
:style="{
|
||||
fontSize: settingStore.fontSize.wordTranslateFontSize +'rem',
|
||||
opacity: settingStore.translate ? 1 : 0
|
||||
}"
|
||||
>
|
||||
<div v-for="i in word.trans">{{ i }}</div>
|
||||
</div>
|
||||
<div class="word-wrapper">
|
||||
<div class="word"
|
||||
:class="wrong && 'is-wrong'"
|
||||
:style="{fontSize: settingStore.fontSize.wordForeignFontSize +'rem'}"
|
||||
>
|
||||
<span class="input" v-if="input">{{ input }}</span>
|
||||
<span class="wrong" v-if="wrong">{{ wrong }}</span>
|
||||
<template v-if="settingStore.dictation">
|
||||
<span class="letter" v-if="!showFullWord"
|
||||
@mouseenter="settingStore.allowWordTip && (showFullWord = true)">{{
|
||||
resetWord.split('').map(v => '_').join('')
|
||||
}}</span>
|
||||
<span class="letter" v-else @mouseleave="showFullWord = false">{{ resetWord }}</span>
|
||||
</template>
|
||||
<span class="letter" v-else>{{ resetWord }}</span>
|
||||
</div>
|
||||
<VolumeIcon ref="volumeIconRef" :simple="true" @click="playWordAudio(word.name)"/>
|
||||
</div>
|
||||
<div class="phonetic">{{ settingStore.wordSoundType === 'us' ? word.usphone : word.ukphone }}</div>
|
||||
<Options
|
||||
@remove="remove"
|
||||
@skip="skip"
|
||||
@collect="collect"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/assets/css/variable";
|
||||
|
||||
.type-word {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
//display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
font-size: 14rem;
|
||||
color: gray;
|
||||
gap: 6rem;
|
||||
position: relative;
|
||||
width: var(--toolbar-width);
|
||||
|
||||
.near-word {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
|
||||
& > div {
|
||||
width: 45%;
|
||||
align-items: center;
|
||||
|
||||
.arrow {
|
||||
min-width: 22rem;
|
||||
min-height: 22rem;
|
||||
}
|
||||
}
|
||||
|
||||
.word {
|
||||
font-size: 24rem;
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
|
||||
.prev {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
float: left;
|
||||
gap: 10rem;
|
||||
}
|
||||
|
||||
.next {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10rem;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
color: transparent !important;
|
||||
text-shadow: #b0b0b0 0 0 6px;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.phonetic, .translate {
|
||||
font-size: 20rem;
|
||||
margin-left: -30rem;
|
||||
transition: all .3s;
|
||||
}
|
||||
|
||||
.word-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rem;
|
||||
|
||||
.word {
|
||||
font-size: 48rem;
|
||||
line-height: 1;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
|
||||
letter-spacing: 5rem;
|
||||
|
||||
.input {
|
||||
color: rgb(22, 163, 74);
|
||||
}
|
||||
|
||||
.wrong {
|
||||
color: rgba(red, 0.6);
|
||||
}
|
||||
|
||||
&.is-wrong {
|
||||
animation: shake 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -65,7 +65,7 @@ async function selectDict(item: DictResource) {
|
||||
}
|
||||
} else {
|
||||
let data: Dict = {
|
||||
...DefaultDict,
|
||||
...cloneDeep(DefaultDict),
|
||||
...item,
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ const emit = defineEmits(['click'])
|
||||
|
||||
function play(time = props.time) {
|
||||
if (count === 0) {
|
||||
props?.cb()
|
||||
props?.cb?.()
|
||||
}
|
||||
count++
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -24,26 +24,26 @@ export const useBaseStore = defineStore('base', {
|
||||
state: (): State => {
|
||||
return {
|
||||
collect: {
|
||||
...DefaultDict,
|
||||
...cloneDeep(DefaultDict),
|
||||
id: 'collect',
|
||||
name: '收藏',
|
||||
type: DictType.collect,
|
||||
},
|
||||
skip: {
|
||||
...DefaultDict,
|
||||
...cloneDeep(DefaultDict),
|
||||
id: 'skip',
|
||||
name: '简单词',
|
||||
type: DictType.skip,
|
||||
},
|
||||
wrong: {
|
||||
...DefaultDict,
|
||||
...cloneDeep(DefaultDict),
|
||||
id: 'wrong',
|
||||
name: '错词本',
|
||||
type: DictType.wrong,
|
||||
},
|
||||
myDicts: [
|
||||
{
|
||||
...DefaultDict,
|
||||
...cloneDeep(DefaultDict),
|
||||
id: '新概念英语2-课文',
|
||||
name: '新概念英语2-课文',
|
||||
type: DictType.article,
|
||||
@@ -52,7 +52,7 @@ export const useBaseStore = defineStore('base', {
|
||||
language: 'en',
|
||||
},
|
||||
{
|
||||
...DefaultDict,
|
||||
...cloneDeep(DefaultDict),
|
||||
id: '新概念英语2',
|
||||
name: '新概念英语2',
|
||||
type: DictType.word,
|
||||
@@ -63,9 +63,10 @@ export const useBaseStore = defineStore('base', {
|
||||
}
|
||||
],
|
||||
current: {
|
||||
dictType: DictType.word,
|
||||
// dictType: DictType.article,
|
||||
index: 1,
|
||||
// dictType: DictType.word,
|
||||
// index: 1,
|
||||
dictType: DictType.article,
|
||||
index: 0,
|
||||
editIndex: 0,
|
||||
repeatNumber: 0,
|
||||
},
|
||||
|
||||
@@ -6,8 +6,8 @@ export interface PracticeState {
|
||||
repeatNumber: number,
|
||||
startDate: number,
|
||||
total: number,
|
||||
index: number,
|
||||
inputWordNumber: number,
|
||||
index: number,//当前输入的第几个,用于和total计算进度
|
||||
inputWordNumber: number,//当前总输入了多少个单词(不包含跳过)
|
||||
wrongWordNumber: number,
|
||||
correctRate: number,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user