This commit is contained in:
zyronon
2023-10-01 16:59:25 +08:00
parent 76846f1fe7
commit fb04478283
6 changed files with 132 additions and 30 deletions

View File

@@ -18,8 +18,8 @@ function format(val: number, suffix: string = '', check: number = -1) {
const progress = $computed(() => {
if (!practiceStore.total) return 0
if (practiceStore.inputWordNumber > practiceStore.total) return 100
return ((practiceStore.inputWordNumber / practiceStore.total) * 100)
if (practiceStore.index > practiceStore.total) return 100
return ((practiceStore.index / practiceStore.total) * 100)
})
let speedMinute = $ref(0)

View File

@@ -1,15 +1,18 @@
<script setup lang="ts">
import {onMounted, watch, watchEffect} from "vue"
import {onMounted, watch} from "vue"
import {$computed, $ref} from "vue/macros"
import {useBaseStore} from "@/stores/base.ts"
import {DictType, DisplayStatistics, ShortKeyMap, Statistics, Word} from "../../types";
import {DictType, DisplayStatistics, ShortKeyMap, Word} from "../../types";
import BaseButton from "@/components/BaseButton.vue";
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 {useEventListener, useOnKeyboardEventListener} from "@/hooks/event.ts";
import {useOnKeyboardEventListener} from "@/hooks/event.ts";
import {Icon} from "@iconify/vue";
import VolumeIcon from "@/components/VolumeIcon.vue";
import Tooltip from "@/components/Tooltip.vue";
interface IProps {
words: Word[],
@@ -28,7 +31,6 @@ let data = $ref({
originWrongWords: [],
})
let input = $ref('')
let wrong = $ref('')
let showFullWord = $ref(false)
@@ -41,7 +43,7 @@ const playBeep = usePlayBeep()
const playCorrect = usePlayCorrect()
const playKeyboardAudio = usePlayKeyboardAudio()
const playWordAudio = usePlayWordAudio()
const volumeIconRef: any = $ref()
watch(() => props.words, () => {
data.words = props.words
@@ -56,8 +58,7 @@ watch(() => props.words, () => {
practiceStore.wrongWordNumber = 0
}, {immediate: true})
let word = $computed(() => {
const word = $computed(() => {
return data.words[data.index] ?? {
trans: [],
name: '',
@@ -66,6 +67,14 @@ let word = $computed(() => {
}
})
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)
})
@@ -76,7 +85,7 @@ onMounted(() => {
})
})
function next() {
function next(isTyping: boolean = true) {
if (data.index === data.words.length - 1) {
if (data.wrongWords.length) {
console.log('当前背完了,但还有错词')
@@ -86,12 +95,14 @@ function next() {
}
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,
@@ -107,16 +118,27 @@ function next() {
}
} else {
data.index++
practiceStore.inputWordNumber++
practiceStore.index++
isTyping && practiceStore.inputWordNumber++
console.log('这个词完了')
if ([DictType.customDict, DictType.publicDict].includes(store.current.dictType)
&& store.skipWordNames.includes(word.name.toLowerCase())) {
next()
} else {
playWordAudio(word.name)
volumeIconRef?.play()
}
}
wrong = input = ''
}
function prev() {
data.index--
playWordAudio(word.name)
volumeIconRef?.play()
wrong = input = ''
}
function onKeyUp(e: KeyboardEvent) {
showFullWord = false
}
@@ -181,12 +203,12 @@ async function onKeyDown(e: KeyboardEvent) {
store.skipWordDict.chapterWords = [store.skipWordDict.words]
}
activeBtnIndex = 0
next()
next(false)
break
case ShortKeyMap.Ignore:
e.preventDefault()
activeBtnIndex = 2
next()
next(false)
break
case ShortKeyMap.Show:
if (settingStore.allowWordTip) {
@@ -206,6 +228,28 @@ useOnKeyboardEventListener(onKeyDown, onKeyUp)
<template>
<div class="type-word">
<div class="near-word" v-if="settingStore.showNearWord">
<div class="prev"
@click="prev"
v-if="prevWord">
<Icon icon="bi:arrow-left" width="22"/>
<div class="word">
<div>{{ prevWord.name }}</div>
<div>{{ prevWord.trans.join('') }}</div>
</div>
</div>
<Tooltip title="快捷键Tab">
<div class="next"
@click="next(false)"
v-if="nextWord">
<div class="word">
<div :class="settingStore.dictation && 'shadow'">{{ nextWord.name }}</div>
<div>{{ nextWord.trans.join('') }}</div>
</div>
<Icon icon="bi:arrow-right" width="22"/>
</div>
</Tooltip>
</div>
<div class="translate"
:style="{fontSize: settingStore.fontSize.wordTranslateFontSize +'rem'}"
>{{ word.trans.join('') }}
@@ -226,7 +270,7 @@ useOnKeyboardEventListener(onKeyDown, onKeyUp)
</template>
<span class="letter" v-else>{{ resetWord }}</span>
</div>
<div class="audio" @click="playWordAudio(word.name)">播放</div>
<VolumeIcon ref="volumeIconRef" :simple="true" @click="playWordAudio(word.name)"/>
</div>
<div class="phonetic">{{ word.usphone }}</div>
<div class="options">
@@ -237,7 +281,7 @@ useOnKeyboardEventListener(onKeyDown, onKeyUp)
收藏
</BaseButton>
<BaseButton keyboard="Tab" :active="activeBtnIndex === 2">
下一个
跳过
</BaseButton>
</div>
</div>
@@ -256,6 +300,49 @@ useOnKeyboardEventListener(onKeyDown, onKeyUp)
font-size: 14rem;
color: gray;
gap: 2rem;
position: relative;
.near-word {
position: absolute;
top: 0;
width: var(--toolbar-width);
.word {
div {
font-size: 24rem;
}
div:last-child {
font-size: 14rem;
}
}
.prev {
cursor: pointer;
display: flex;
float: left;
align-items: center;
gap: 10rem;
}
.next {
cursor: pointer;
display: flex;
align-items: center;
gap: 10rem;
float: right;
.word {
text-align: right;
}
}
.shadow {
color: transparent !important;
text-shadow: #b0b0b0 0 0 6px;
user-select: none;
}
}
.options {
margin-top: 10rem;
@@ -294,6 +381,4 @@ useOnKeyboardEventListener(onKeyDown, onKeyUp)
}
}
}
</style>

View File

@@ -89,7 +89,7 @@ const skipWordDictActiveIndex = computed(() => {
<div class="side" v-if="store.sideIsOpen">
<header>
<div class="tabs">
<div class="tab" :class="tabIndex===0&&'active'" @click="slideTo(0)">{{ store.dict.name }}</div>
<div class="tab" :class="tabIndex===0&&'active'" @click="slideTo(0)">{{ store.currentDict.name }}</div>
<div class="tab" :class="tabIndex===1&&'active'" @click="slideTo(1)">{{ store.newWordDict.name }}</div>
<div class="tab" :class="tabIndex===2&&'active'" @click="slideTo(2)">{{ store.wrongWordDict.name }}</div>
<div class="tab" :class="tabIndex===3&&'active'" @click="slideTo(3)">{{ store.skipWordDict.name }}</div>
@@ -100,18 +100,18 @@ const skipWordDictActiveIndex = computed(() => {
<swiper-slide>
<div class="page0">
<header>
<div class="dict-name">{{ store.dict.chapterIndex + 1 }}.</div>
<div class="dict-name">{{ store.currentDict.chapterIndex + 1 }}.</div>
</header>
<WordList
class="word-list"
@change="(e:number) => store.changeDict(store.dict,store.dict.chapterIndex,e)"
@change="(e:number) => store.changeDict(store.currentDict,store.currentDict.chapterIndex,e)"
:isActive="store.sideIsOpen && tabIndex === 0"
:list="store.dict.chapterWords[store.dict.chapterIndex]??[]"
:list="store.currentDict.chapterWords[store.currentDict.chapterIndex]??[]"
:activeIndex="dictActiveIndex"/>
<footer v-if="![DictType.customDict,DictType.publicDict].includes(store.current.dictType)">
<PopConfirm
:title="`确认切换?`"
@confirm="store.changeDict(store.dict)"
@confirm="store.changeDict(store.currentDict)"
>
<BaseButton>切换</BaseButton>
</PopConfirm>

View File

@@ -185,7 +185,7 @@ useWatchAllSound()
</div>
<div class="line"></div>
<div class="row">
<label class="item-title">字体设置</label>
<label class="item-title">字体设置(仅可调整单词练习)</label>
</div>
<div class="row">
<label class="sut-title">外语字体</label>
@@ -234,7 +234,7 @@ useWatchAllSound()
.setting-modal {
width: 40vw;
height: 80vh;
height: 650rem;
display: flex;
color: var(--color-font-1);

View File

@@ -3,8 +3,12 @@ import {Icon} from "@iconify/vue";
import {$ref} from "vue/macros";
import IconWrapper from "@/components/IconWrapper.vue";
const props = withDefaults(defineProps<{ time?: number }>(), {
time: 400
const props = withDefaults(defineProps<{
time?: number,
simple?: boolean
}>(), {
time: 400,
simple: false
})
let step = $ref(2)
let count = $ref(0)
@@ -35,10 +39,17 @@ function click() {
emit('click')
play()
}
defineExpose({play})
</script>
<template>
<IconWrapper @click.stop="click">
<div class="center" @click.stop="click" v-if="props.simple">
<Icon v-if="step === 0" icon="bx:volume"/>
<Icon v-if="step === 1" icon="bx:volume-low"/>
<Icon v-if="step === 2" icon="bx:volume-full"/>
</div>
<IconWrapper @click.stop="click" v-else>
<div class="center">
<Icon v-if="step === 0" icon="bx:volume"/>
<Icon v-if="step === 1" icon="bx:volume-low"/>
@@ -49,8 +60,15 @@ function click() {
<style scoped lang="scss">
.center {
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
$w: 26rem;
:deep(svg) {
width: $w;
height: $w;
}
}
</style>

View File

@@ -2,11 +2,11 @@ import {defineStore} from "pinia"
import {Word} from "@/types.ts"
export interface PracticeState {
type: 'word' | 'article',
wrongWords: Word[],
repeatNumber: number,
startDate: number,
total: number,
index: number,
inputWordNumber: number,
wrongWordNumber: number,
correctRate: number,
@@ -15,13 +15,12 @@ export interface PracticeState {
export const usePracticeStore = defineStore('practice', {
state: (): PracticeState => {
return {
// type: 'articles',
type: 'word',
wrongWords: [],
repeatNumber: 0,
startDate: Date.now(),
correctRate: -1,
total: 0,
index: 0,
inputWordNumber: 0,
wrongWordNumber: 0,
}