diff --git a/src/App.vue b/src/App.vue index 76fa4610..599e73ae 100644 --- a/src/App.vue +++ b/src/App.vue @@ -4,7 +4,7 @@ import Statistics from "@/components/Modal/Statistics.vue"; import {onMounted, watch} from "vue"; import {useBaseStore} from "@/stores/base.ts"; import {SaveKey} from "@/types.ts" -import Practice from "@/components/Practice.vue" +import Practice from "@/components/Practice/Practice.vue" const store = useBaseStore() // 查询当前系统主题颜色 diff --git a/src/components/Practice/Footer.vue b/src/components/Practice/Footer.vue index c7a88bb4..7c8a71c0 100644 --- a/src/components/Practice/Footer.vue +++ b/src/components/Practice/Footer.vue @@ -5,32 +5,25 @@ import {onMounted, onUnmounted} from "vue" import {useBaseStore} from "@/stores/base.ts" import Tooltip from "@/components/Tooltip.vue" import {Down} from "@icon-park/vue-next" +import {usePracticeStore} from "@/components/Practice/usePracticeStore.ts"; -interface IProps { - total: number, - inputNumber: number - wrongNumber: number - correctRate: number -} - -const props = defineProps() - +const practiceStore = usePracticeStore() const store = useBaseStore() -function format(val: number, suffix: string = '') { - return val === -1 ? '-' : (val + suffix) +function format(val: number, suffix: string = '', check: number = -1) { + return val === check ? '-' : (val + suffix) } const progress = $computed(() => { - if (!store.chapter.length) return 0 - return ((store.current.index / store.current.statistics.wordNumber) * 100) + if (!practiceStore.total) return 0 + return ((practiceStore.inputNumber / practiceStore.total) * 100) }) let speedMinute = $ref(0) let timer = $ref(0) onMounted(() => { timer = setInterval(() => { - speedMinute = Math.floor((Date.now() - store.current.statistics.startDate) / 1000 / 60) + speedMinute = Math.floor((Date.now() - practiceStore.startDate) / 1000 / 60) }, 1000) }) @@ -60,22 +53,22 @@ onUnmounted(() => {
时间
-
{{ store.current.statistics.wordNumber }}
+
{{ practiceStore.total }}
单词总数
-
{{ store.current.index }}
+
{{ format(practiceStore.inputNumber, '', 0) }}
输入数
-
{{ format(store.current.wrongWords.length) }}
+
{{ format(practiceStore.wrongNumber, '', 0) }}
错误数
-
{{ format(store.current.statistics.correctRate, '%') }}
+
{{ format(practiceStore.correctRate, '%') }}
正确率
@@ -93,7 +86,7 @@ onUnmounted(() => { @import "@/assets/css/colors.scss"; .footer { - width: 100%; + width: var(--toolbar-width); margin-bottom: 30rem; transition: all .3s; position: relative; diff --git a/src/components/Practice/Footer2.vue b/src/components/Practice/Footer2.vue deleted file mode 100644 index d831b309..00000000 --- a/src/components/Practice/Footer2.vue +++ /dev/null @@ -1,172 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/components/Practice/Practice.vue b/src/components/Practice/Practice.vue index 103ff4ce..c59a162b 100644 --- a/src/components/Practice/Practice.vue +++ b/src/components/Practice/Practice.vue @@ -1,15 +1,32 @@ @@ -22,5 +39,4 @@ import TypeArticle from "@/components/TypeArticle.vue" justify-content: center; align-items: center; } - \ No newline at end of file diff --git a/src/components/Practice/TypeArticle.vue b/src/components/Practice/TypeArticle.vue index 5c03ef2e..83f4ba12 100644 --- a/src/components/Practice/TypeArticle.vue +++ b/src/components/Practice/TypeArticle.vue @@ -3,26 +3,24 @@ import {usePlayWordAudio} from "@/hooks/usePlayWordAudio.ts" import {computed, nextTick, onMounted, reactive, watch} from "vue" import {cloneDeep} from "lodash" -import 快速打字的机械键盘声音Mp3 from '../assets/sound/key-sounds/快速打字的机械键盘声音.mp3' -import 键盘快速打字的声音Mp3 from '../assets/sound/key-sounds/键盘快速打字的声音.mp3' -import 电话打字的声音Mp3 from '../assets/sound/key-sounds/电话打字的声音.mp3' -import 老式机械 from '../assets/sound/key-sounds/老式机械.mp3' -import 机械0 from '../assets/sound/key-sounds/jixie/机械0.mp3' -import 机械1 from '../assets/sound/key-sounds/jixie/机械1.mp3' -import 机械2 from '../assets/sound/key-sounds/jixie/机械2.mp3' -import 机械3 from '../assets/sound/key-sounds/jixie/机械3.mp3' -import beep from '../assets/sound/beep.wav' -import correct from '../assets/sound/correct.wav' +import 快速打字的机械键盘声音Mp3 from '../..//assets/sound/key-sounds/快速打字的机械键盘声音.mp3' +import 键盘快速打字的声音Mp3 from '../..//assets/sound/key-sounds/键盘快速打字的声音.mp3' +import 电话打字的声音Mp3 from '../..//assets/sound/key-sounds/电话打字的声音.mp3' +import 老式机械 from '../..//assets/sound/key-sounds/老式机械.mp3' +import 机械0 from '../..//assets/sound/key-sounds/jixie/机械0.mp3' +import 机械1 from '../..//assets/sound/key-sounds/jixie/机械1.mp3' +import 机械2 from '../..//assets/sound/key-sounds/jixie/机械2.mp3' +import 机械3 from '../..//assets/sound/key-sounds/jixie/机械3.mp3' +import beep from '../..//assets/sound/beep.wav' +import correct from '../..//assets/sound/correct.wav' import {useSound} from "@/hooks/useSound.ts" import {CnKeyboardMap, useSplitArticle} from "@/hooks/useSplitArticle"; import {$computed, $ref} from "vue/macros"; -import {Article, DictType, SaveKey, Sentence, ShortKeyMap, Word} from "@/types"; +import {Article, DefaultWord, DictType, SaveKey, Sentence, ShortKeyMap, Word} from "@/types"; import {useBaseStore} from "@/stores/base"; -import Footer2 from "@/components/Footer2.vue" -import {Swiper, SwiperSlide} from "swiper/vue"; -import 'swiper/css'; -import {Swiper as SwiperClass} from "swiper/types"; -import Type2 from "@/components/Type2.vue" +import Footer from "@/components/Practice/Footer.vue" +import {usePracticeStore} from "@/components/Practice/usePracticeStore.ts"; +import {useEventListener} from "@/hooks/useEvent.ts"; let article1 = `How does the older investor differ in his approach to investment from the younger investor? There is no shortage of tipsters around offering 'get-rich-quick' opportunities. But if you are a serious private investor, leave the Las Vegas mentality to those with money to fritter. The serious investor needs a proper 'portfolio' -- a well-planned selection of investments, with a definite structure and a clear aim. But exactly how does a newcomer to the stock market go about achieving that? @@ -48,9 +46,6 @@ Whether the remarkable growth of organized camping means the eventual death of t NIGEL BUXTON The Great Escape from The Weekend Telegraph` // article2 = `Economy is one powerful motive for camping? since after the initial outlay upon equipment, or through hiring it, the total expense can be far less than the cost of hotels. But, contrary to a popular assumption, it is far from being the only one, or even the greatest. The man who manoeuvres carelessly into his twenty pounds' worth of space at one of Europe's myriad permanent sites may find himself bumping a Bentley. More likely, Ford Escort will be hub to hub with Renault or Mercedes, but rarely with bicycles made for two.` -let isPlay = $ref(false) -let inputRef = $ref(null) -let articleWrapperRef = $ref(null) const [playAudio] = usePlayWordAudio() // const [playKeySound, setAudio] = useSound([机械0, 机械1, 机械2, 机械3], 1) @@ -60,7 +55,10 @@ const [playBeep] = useSound([beep], 1) const [playCorrect] = useSound([correct], 1) const store = useBaseStore() +const practiceStore = usePracticeStore() +let isPlay = $ref(false) +let articleWrapperRef = $ref(null) let sectionIndex = $ref(0) let sentenceIndex = $ref(0) let wordIndex = $ref(6) @@ -74,14 +72,6 @@ let hoverIndex = $ref({ sectionIndex: 0, sentenceIndex: 0, }) -let statistics = $ref({ - wrongWords: [], - total: 0, - startDate: 0, - inputNumber: 0, - wrongNumber: 0, - correctRate: -1, -}) let article = reactive
({ article: article1, @@ -93,29 +83,19 @@ let article = reactive
({ translate: [], }) -watch(statistics, () => { - if (statistics.inputNumber < 1) { - return statistics.correctRate = -1 - } - if (statistics.wrongNumber > statistics.inputNumber) { - return statistics.correctRate = 0 - } - statistics.correctRate = 100 - Math.trunc(((statistics.wrongNumber) / (statistics.inputNumber)) * 100) -}) onMounted(() => { let sections = useSplitArticle(article.article) - let wordNumber = 0 + practiceStore.total = 0 sections.map(v => { v.map(w => { w.words.map(s => { if (!store.skipWordNamesWithSimpleWords.includes(s.toLowerCase())) { - wordNumber++ + practiceStore.total++ } }) }) }) - statistics.total = wordNumber - statistics.startDate = Date.now() + practiceStore.startDate = Date.now() let temp = useSplitArticle(article.articleTranslate, 'cn', CnKeyboardMap) temp.map((v, i) => { @@ -168,9 +148,6 @@ function play() { window.speechSynthesis.speak(msg); } -function focus() { - inputRef.focus() -} const currentIndex = computed(() => { return `${sectionIndex}${sentenceIndex}${wordIndex}` @@ -178,9 +155,7 @@ const currentIndex = computed(() => { function onKeyDown(e: KeyboardEvent) { // console.log('keyDown', e.key, e.code, e.keyCode) - wrong = '' - let currentSection = article.sections[sectionIndex] let currentSentence = currentSection[sentenceIndex] let currentWord = currentSentence.words[wordIndex] @@ -191,7 +166,7 @@ function onKeyDown(e: KeyboardEvent) { index = 0 wordIndex++ if (!store.skipWordNamesWithSimpleWords.includes(currentWord.toLowerCase())) { - statistics.inputNumber++ + practiceStore.inputNumber++ } playCorrect() @@ -269,9 +244,9 @@ function onKeyDown(e: KeyboardEvent) { } if (!store.skipWordNamesWithSimpleWords.includes(currentWord.toLowerCase())) { - if (!statistics.wrongWords.find((v) => v.toLowerCase() === currentWord.toLowerCase())) { - statistics.wrongWords.push(currentWord) - statistics.wrongNumber++ + if (!practiceStore.wrongWords.find((v) => v.name.toLowerCase() === currentWord.toLowerCase())) { + practiceStore.wrongWords.push(word) + practiceStore.wrongNumber++ } } @@ -317,7 +292,6 @@ function onKeyDown(e: KeyboardEvent) { // 'wordIndex', wordIndex, // 'index', index, // ) - inputRef.value = '' e.preventDefault() } @@ -328,6 +302,9 @@ function onKeyUp() { } } +useEventListener('keydown', onKeyDown) +useEventListener('keyup', onKeyUp) + function playWord(word: string) { playAudio(word) } @@ -435,25 +412,12 @@ function otherWord(word: string, i: number, i2: number, i3: number) {
- - + +
- - - \ No newline at end of file diff --git a/src/components/Practice/TypeWord.vue b/src/components/Practice/TypeWord.vue index 5078696e..c90fe3df 100644 --- a/src/components/Practice/TypeWord.vue +++ b/src/components/Practice/TypeWord.vue @@ -25,6 +25,42 @@ import { import {emitter, EventKey} from "@/utils/eventBus.ts" import {cloneDeep} from "lodash" import {usePracticeStore} from "@/components/Practice/usePracticeStore.ts" +import {useEventListener} from "@/hooks/useEvent.ts"; + +interface IProps { + words: Word[], + index: number, +} + +const props = withDefaults(defineProps(), { + words: [], + index: -1 +}) + +let data = $ref({ + index: props.index, + words: props.words, + wrongWords: [], + originWrongWords: [], + repeatNumber: 0, + startDate: Date.now(), + correctRate: -1, +}) + +watch(() => props.words, (n: Word[]) => { + data.words = n + data.index = n.length ? 0 : -1 +}) + + +let word = $computed(() => { + return data.words[data.index] ?? { + trans: [], + name: '', + usphone: '', + ukphone: '', + } +}) let input = $ref('') let wrong = $ref('') @@ -42,43 +78,38 @@ const [playAudio] = usePlayWordAudio() const practiceStore = usePracticeStore() let resetWord = $computed(() => { - return practiceStore.word.name.slice(input.length + wrong.length) + return word.name.slice(input.length + wrong.length) }) onMounted(() => { - window.addEventListener('keydown', onKeyDown) - window.addEventListener('keyup', onKeyUp) - emitter.on(EventKey.resetWord, () => { input = '' }) }) -onUnmounted(() => { - // console.log('onUnmounted') - window.removeEventListener('keydown', onKeyDown) - window.removeEventListener('keyup', onKeyUp) -}) + +useEventListener('keydown', onKeyDown) +useEventListener('keyup', onKeyUp) function next() { - if (practiceStore.index === practiceStore.words.length - 1) { - if (practiceStore.wrongWords.length) { - practiceStore.words = cloneDeep(practiceStore.wrongWords) - if (!practiceStore.originWrongWords.length) { - practiceStore.originWrongWords = cloneDeep(practiceStore.wrongWords) + if (data.index === data.words.length - 1) { + if (data.wrongWords.length) { + data.words = cloneDeep(data.wrongWords) + if (!data.originWrongWords.length) { + data.originWrongWords = cloneDeep(data.wrongWords) } - practiceStore.index = 0 - practiceStore.repeatNumber++ - practiceStore.wrongWords = [] + data.index = 0 + data.repeatNumber++ + data.wrongWords = [] } else { console.log('这本书完了') emitter.emit(EventKey.openStatModal) } } else { - practiceStore.index++ + data.index++ console.log('这个词完了') if ([DictType.customDict, DictType.innerDict].includes(store.current.dictType) - && store.skipWordNames.includes(practiceStore.word.name.toLowerCase())) { + && store.skipWordNames.includes(word.name.toLowerCase())) { next() } } @@ -94,18 +125,18 @@ async function onKeyDown(e: KeyboardEvent) { //TODO 还有横杠 if ((e.keyCode >= 65 && e.keyCode <= 90) || e.code === 'Space') { let letter = e.key - if ((input + letter).toLowerCase() === practiceStore.word.name.toLowerCase().slice(0, input.length + 1)) { + if ((input + letter).toLowerCase() === word.name.toLowerCase().slice(0, input.length + 1)) { input += letter wrong = '' playKeySound() } else { - if (!store.wrongWordDict.originWords.find((v: Word) => v.name.toLowerCase() === practiceStore.word.name.toLowerCase())) { - store.wrongWordDict.originWords.push(practiceStore.word) - store.wrongWordDict.words.push(practiceStore.word) + if (!store.wrongWordDict.originWords.find((v: Word) => v.name.toLowerCase() === word.name.toLowerCase())) { + store.wrongWordDict.originWords.push(word) + store.wrongWordDict.words.push(word) store.wrongWordDict.chapterWords = [store.wrongWordDict.words] } - if (!practiceStore.wrongWords.find((v: Word) => v.name.toLowerCase() === practiceStore.word.name.toLowerCase())) { - practiceStore.wrongWords.push(practiceStore.word) + if (!data.wrongWords.find((v: Word) => v.name.toLowerCase() === word.name.toLowerCase())) { + data.wrongWords.push(word) } wrong = letter playKeySound() @@ -114,7 +145,7 @@ async function onKeyDown(e: KeyboardEvent) { wrong = '' }, 500) } - if (input.toLowerCase() === practiceStore.word.name.toLowerCase()) { + if (input.toLowerCase() === word.name.toLowerCase()) { playCorrect() setTimeout(next, 300) } @@ -129,17 +160,17 @@ async function onKeyDown(e: KeyboardEvent) { } break case ShortKeyMap.Collect: - if (!store.newWordDict.originWords.find((v: Word) => v.name === practiceStore.word.name)) { - store.newWordDict.originWords.push(practiceStore.word) - store.newWordDict.words.push(practiceStore.word) + if (!store.newWordDict.originWords.find((v: Word) => v.name === word.name)) { + store.newWordDict.originWords.push(word) + store.newWordDict.words.push(word) store.newWordDict.chapterWords = [store.newWordDict.words] } activeIndex = 1 break case ShortKeyMap.Remove: - if (!store.skipWordNames.includes(practiceStore.word.name)) { - store.skipWordDict.originWords.push(practiceStore.word) - store.skipWordDict.words.push(practiceStore.word) + if (!store.skipWordNames.includes(word.name)) { + store.skipWordDict.originWords.push(word) + store.skipWordDict.words.push(word) store.skipWordDict.chapterWords = [store.skipWordDict.words] } activeIndex = 0 @@ -163,7 +194,7 @@ async function onKeyDown(e: KeyboardEvent) { {{ resetWord }} -
播放
+
播放
-
{{ practiceStore.word.usphone }}
+
{{ word.usphone }}
忽略 @@ -196,6 +227,7 @@ async function onKeyDown(e: KeyboardEvent) { @import "@/assets/css/colors.scss"; .type-word { + flex: 1; display: flex; //display: none; align-items: center; diff --git a/src/components/Practice/TypeWordWrapper.vue b/src/components/Practice/TypeWordWrapper.vue deleted file mode 100644 index 6b3ee69e..00000000 --- a/src/components/Practice/TypeWordWrapper.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/components/Practice/usePracticeStore.ts b/src/components/Practice/usePracticeStore.ts index cc0851b6..1e5bebf1 100644 --- a/src/components/Practice/usePracticeStore.ts +++ b/src/components/Practice/usePracticeStore.ts @@ -1,42 +1,28 @@ import {defineStore} from "pinia" -import {State, Word} from "@/types.ts" +import {Word} from "@/types.ts" export interface PracticeState { - index: number, - words: Word[], - wrongWords: Word[], - originWrongWords: Word[], - repeatNumber: number, - startDate: number, - correctRate: number, - total: number, - inputNumber: number, - wrongNumber: number, + type: 'word' | 'article', + wrongWords: Word[], + repeatNumber: number, + startDate: number, + total: number, + inputNumber: number, + wrongNumber: number, + correctRate: number, } export const usePracticeStore = defineStore('practice', { - state: (): PracticeState => { - return { - index: -1, - words: [], - wrongWords: [], - originWrongWords: [], - repeatNumber: 0, - startDate: Date.now(), - correctRate: -1, - total: -1, - inputNumber: -1, - wrongNumber: -1, - } - }, - getters: { - word(state: PracticeState): Word { - return state.words[state.index] ?? { - trans: [], - name: '', - usphone: '', - ukphone: '', - } - }, + state: (): PracticeState => { + return { + type: 'word', + wrongWords: [], + repeatNumber: 0, + startDate: Date.now(), + correctRate: -1, + total: -1, + inputNumber: -1, + wrongNumber: -1, } + }, }) \ No newline at end of file diff --git a/src/hooks/useEvent.ts b/src/hooks/useEvent.ts new file mode 100644 index 00000000..ee267bd6 --- /dev/null +++ b/src/hooks/useEvent.ts @@ -0,0 +1,7 @@ +// event.js +import {onMounted, onUnmounted} from 'vue' + +export function useEventListener(type: string, listener: EventListenerOrEventListenerObject) { + onMounted(() => window.addEventListener(type, listener)) + onUnmounted(() => window.removeEventListener(type, listener)) +} \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index 2bf1078a..cff574f6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,7 +5,14 @@ export type Word = { "trans": string[] } -export const SaveKey = 'bb-word-config' +export const DefaultWord: Word = { + name: '', + usphone: '', + ukphone: '', + trans: [] +} + +export const SaveKey = 'type-word-config' export const PronunciationApi = 'https://dict.youdao.com/dictvoice?audio='