save
This commit is contained in:
@@ -16,6 +16,7 @@ import BackIcon from "@/components/BackIcon.vue";
|
||||
import MiniDialog from "@/components/dialog/MiniDialog.vue";
|
||||
import {onMounted} from "vue";
|
||||
import {Origin} from "@/config/ENV.ts";
|
||||
import {syncBookInMyStudyList} from "@/hooks/article.ts";
|
||||
|
||||
const base = useBaseStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
@@ -106,25 +107,6 @@ function saveArticle(val: Article): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
//todo 考虑与syncDictInMyStudyList、changeDict方法合并
|
||||
function syncBookInMyStudyList(study = false) {
|
||||
_nextTick(() => {
|
||||
let rIndex = base.article.bookList.findIndex(v => v.id === runtimeStore.editDict.id)
|
||||
let temp = cloneDeep(runtimeStore.editDict);
|
||||
if (!temp.custom && temp.id !== DictId.articleCollect) {
|
||||
temp.custom = true
|
||||
}
|
||||
temp.length = temp.articles.length
|
||||
if (rIndex > -1) {
|
||||
base.article.bookList[rIndex] = temp
|
||||
if (study) base.article.studyIndex = rIndex
|
||||
} else {
|
||||
base.article.bookList.push(temp)
|
||||
if (study) base.article.studyIndex = base.article.bookList.length - 1
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
|
||||
function saveAndNext(val: Article) {
|
||||
if (saveArticle(val)) {
|
||||
add()
|
||||
|
||||
@@ -11,7 +11,7 @@ import Toast from '@/components/base/toast/Toast.ts'
|
||||
import {_getDictDataByUrl, cloneDeep, msToHourMinute, msToMinute, total} from "@/utils";
|
||||
import {usePracticeStore} from "@/stores/practice.ts";
|
||||
import {useArticleOptions} from "@/hooks/dict.ts";
|
||||
import {genArticleSectionData, usePlaySentenceAudio} from "@/hooks/article.ts";
|
||||
import {genArticleSectionData, syncBookInMyStudyList, usePlaySentenceAudio} from "@/hooks/article.ts";
|
||||
import {getDefaultArticle, getDefaultDict, getDefaultWord} from "@/types/func.ts";
|
||||
import TypingArticle from "@/pages/article/components/TypingArticle.vue";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
@@ -204,6 +204,7 @@ function saveArticle(val: Article) {
|
||||
store.sbook.articles[rIndex] = cloneDeep(val)
|
||||
}
|
||||
setArticle(val)
|
||||
store.sbook.custom = true
|
||||
}
|
||||
|
||||
function edit(val: Article = articleData.article) {
|
||||
@@ -376,7 +377,7 @@ const currentPractice = $computed(() => {
|
||||
<div class="flex justify-between items-center gap-2">
|
||||
<div class="stat">
|
||||
<div class="row">
|
||||
<div class="num">{{ currentPractice.length }}次/{{ msToMinute(total(currentPractice, 'spend'))}}</div>
|
||||
<div class="num">{{ currentPractice.length }}次/{{ msToMinute(total(currentPractice, 'spend')) }}</div>
|
||||
<div class="line"></div>
|
||||
<div class="name">记录</div>
|
||||
</div>
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import {onMounted, onUnmounted, watch} from "vue"
|
||||
import {Article, ArticleWord, Sentence, Word} from "@/types/types.ts";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePlayWordAudio} from "@/hooks/sound.ts";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts";
|
||||
import {_nextTick} from "@/utils";
|
||||
import { onMounted, onUnmounted, watch } from "vue"
|
||||
import { Article, ArticleWord, Sentence, Word } from "@/types/types.ts";
|
||||
import { useBaseStore } from "@/stores/base.ts";
|
||||
import { useSettingStore } from "@/stores/setting.ts";
|
||||
import { usePlayBeep, usePlayCorrect, usePlayKeyboardAudio } from "@/hooks/sound.ts";
|
||||
import { emitter, EventKey } from "@/utils/eventBus.ts";
|
||||
import { _nextTick } from "@/utils";
|
||||
import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css'
|
||||
import ContextMenu from '@imengyu/vue3-context-menu'
|
||||
import {getTranslateText} from "@/hooks/article.ts";
|
||||
import { getTranslateText } from "@/hooks/article.ts";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import QuestionForm from "@/pages/article/components/QuestionForm.vue";
|
||||
import {getDefaultArticle, getDefaultWord} from "@/types/func.ts";
|
||||
import { getDefaultArticle, getDefaultWord } from "@/types/func.ts";
|
||||
import Toast from '@/components/base/toast/Toast.ts'
|
||||
import TypingWord from "@/pages/article/components/TypingWord.vue";
|
||||
import Space from "@/pages/article/components/Space.vue";
|
||||
import {useWordOptions} from "@/hooks/dict.ts";
|
||||
import { useWordOptions } from "@/hooks/dict.ts";
|
||||
import nlp from "compromise/three";
|
||||
import {nanoid} from "nanoid";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
interface IProps {
|
||||
article: Article,
|
||||
@@ -76,7 +76,6 @@ const currentIndex = $computed(() => {
|
||||
const playBeep = usePlayBeep()
|
||||
const playCorrect = usePlayCorrect()
|
||||
const playKeyboardAudio = usePlayKeyboardAudio()
|
||||
const playWordAudio = usePlayWordAudio()
|
||||
const {
|
||||
toggleWordCollect,
|
||||
} = useWordOptions()
|
||||
@@ -855,22 +854,4 @@ $article-lh: 2.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cursor {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 1.8rem;
|
||||
animation: underline 1s infinite steps(1, start);
|
||||
}
|
||||
|
||||
@keyframes underline {
|
||||
0%, 100% {
|
||||
border-left: .1rem solid var(--color-article);
|
||||
}
|
||||
50% {
|
||||
border-left: .1rem solid transparent;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -139,7 +139,6 @@ function getShortcutKeyName(key: string): string {
|
||||
'ShowWord': '显示单词',
|
||||
'EditArticle': '编辑文章',
|
||||
'Next': '下一个',
|
||||
'Replay': '重播',
|
||||
'Previous': '上一个',
|
||||
'ToggleSimple': '切换已掌握状态',
|
||||
'ToggleCollect': '切换收藏状态',
|
||||
@@ -350,14 +349,14 @@ function importOldData() {
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem title="允许默写模式下显示提示"
|
||||
:desc="`开启后,可以通过鼠标 hover 单词或者按快捷键 ${settingStore.shortcutKeyMap[ShortcutKey.ShowWord]} 显示正确答案`"
|
||||
:desc="`开启后,可以通过将鼠标移动到单词上或者按快捷键 ${settingStore.shortcutKeyMap[ShortcutKey.ShowWord]} 显示正确答案`"
|
||||
>
|
||||
<Switch v-model="settingStore.allowWordTip"/>
|
||||
</SettingItem>
|
||||
|
||||
<div class="line"></div>
|
||||
<SettingItem title="简单词过滤"
|
||||
desc="开启后,练习单词中不会包含简单词;文章统计的总词数中不会包含简单词"
|
||||
desc="开启后,练习的单词中不会包含简单词;文章统计的总词数中不会包含简单词"
|
||||
>
|
||||
<Switch v-model="settingStore.ignoreSimpleWord"/>
|
||||
</SettingItem>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {onMounted, provide, watch} from "vue";
|
||||
import { onMounted, provide, 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, ShortcutKey, StudyData, 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, ShortcutKey, StudyData, 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, shuffle} from "@/utils";
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
import { getCurrentStudyWord, useWordOptions } from "@/hooks/dict.ts";
|
||||
import { _getDictDataByUrl, cloneDeep, shuffle } 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,13 +19,14 @@ 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 dict_list from "@/assets/dict-list.json";
|
||||
import PracticeLayout from "@/components/PracticeLayout.vue";
|
||||
import { PracticeSaveKey } from "@/utils/const.ts";
|
||||
|
||||
interface IProps {
|
||||
new: Word[],
|
||||
@@ -92,9 +93,28 @@ watch(() => store.load, (n) => {
|
||||
if (n && loading) init()
|
||||
}, {immediate: true})
|
||||
|
||||
function checkSaveData() {
|
||||
let d = localStorage.getItem(PracticeSaveKey.Word)
|
||||
if (d) {
|
||||
try {
|
||||
let obj = JSON.parse(d)
|
||||
console.log('obj', obj)
|
||||
studyData = obj.studyData
|
||||
data = obj.practiceData
|
||||
return true
|
||||
} catch (e) {
|
||||
localStorage.removeItem(PracticeSaveKey.Word)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
//如果是从单词学习主页过来的,就直接使用;否则等待加载
|
||||
if (runtimeStore.routeData) {
|
||||
studyData = runtimeStore.routeData
|
||||
if (!checkSaveData()) {
|
||||
studyData = runtimeStore.routeData
|
||||
}
|
||||
} else {
|
||||
loading = true
|
||||
}
|
||||
@@ -168,6 +188,8 @@ function next(isTyping: boolean = true) {
|
||||
statStore.spend = Date.now() - statStore.startDate
|
||||
console.log('全完学完了')
|
||||
showStatDialog = true
|
||||
localStorage.removeItem(PracticeSaveKey.Word)
|
||||
return;
|
||||
// emit('complete', {})
|
||||
}
|
||||
|
||||
@@ -180,8 +202,8 @@ function next(isTyping: boolean = true) {
|
||||
data.words = shuffle(studyData.write)
|
||||
data.index = 0
|
||||
} else {
|
||||
console.log('开始默认所有单词-无单词路过')
|
||||
next()
|
||||
console.log('开始默认所有单词-无单词略过')
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,8 +216,8 @@ function next(isTyping: boolean = true) {
|
||||
data.words = shuffle(studyData.review)
|
||||
data.index = 0
|
||||
} else {
|
||||
console.log('开始默写昨日-无单词路过')
|
||||
next()
|
||||
console.log('开始默写昨日-无单词略过')
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,8 +230,8 @@ function next(isTyping: boolean = true) {
|
||||
data.words = shuffle(studyData.review)
|
||||
data.index = 0
|
||||
} else {
|
||||
console.log('开始复习昨日-无单词路过')
|
||||
next()
|
||||
console.log('开始复习昨日-无单词略过')
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,8 +252,15 @@ function next(isTyping: boolean = true) {
|
||||
} else {
|
||||
data.index++
|
||||
isTyping && statStore.inputWordNumber++
|
||||
console.log('这个词完了')
|
||||
// console.log('这个词完了')
|
||||
}
|
||||
|
||||
localStorage.setItem(PracticeSaveKey.Word, JSON.stringify({
|
||||
studyData,
|
||||
practiceData: data,
|
||||
statStoreData: statStore.$state,
|
||||
}))
|
||||
console.log('wordPracticeData',)
|
||||
}
|
||||
|
||||
function onTypeWrong() {
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {useRouter} from "vue-router";
|
||||
import { useBaseStore } from "@/stores/base.ts";
|
||||
import { useRouter } from "vue-router";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import {_getAccomplishDate, _getDictDataByUrl, useNav} from "@/utils";
|
||||
import { _getAccomplishDate, _getDictDataByUrl, useNav } from "@/utils";
|
||||
import BasePage from "@/components/BasePage.vue";
|
||||
import {DictResource} from "@/types/types.ts";
|
||||
import {watch} from "vue";
|
||||
import {getCurrentStudyWord} from "@/hooks/dict.ts";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import { DictResource } from "@/types/types.ts";
|
||||
import { watch } from "vue";
|
||||
import { getCurrentStudyWord } from "@/hooks/dict.ts";
|
||||
import { useRuntimeStore } from "@/stores/runtime.ts";
|
||||
import Book from "@/components/Book.vue";
|
||||
import PopConfirm from "@/components/PopConfirm.vue";
|
||||
import Progress from '@/components/base/Progress.vue';
|
||||
import Toast from '@/components/base/toast/Toast.ts';
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import {getDefaultDict} from "@/types/func.ts";
|
||||
import { getDefaultDict } from "@/types/func.ts";
|
||||
import DeleteIcon from "@/components/icon/DeleteIcon.vue";
|
||||
import PracticeSettingDialog from "@/pages/word/components/PracticeSettingDialog.vue";
|
||||
import ChangeLastPracticeIndexDialog from "@/pages/word/components/ChangeLastPracticeIndexDialog.vue";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import { useSettingStore } from "@/stores/setting.ts";
|
||||
import recommendDictList from "@/assets/recommend-dict-list.json";
|
||||
import CollectNotice from "@/components/CollectNotice.vue";
|
||||
import { PracticeSaveKey } from "@/utils/const.ts";
|
||||
|
||||
|
||||
const store = useBaseStore()
|
||||
@@ -28,6 +29,7 @@ const router = useRouter()
|
||||
const {nav} = useNav()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
let loading = $ref(true)
|
||||
let isSaveData = $ref(false)
|
||||
let currentStudy = $ref({
|
||||
new: [],
|
||||
review: [],
|
||||
@@ -46,7 +48,19 @@ async function init() {
|
||||
}
|
||||
// console.log(store.sdict)
|
||||
if (!currentStudy.new.length && store.sdict.words.length) {
|
||||
currentStudy = getCurrentStudyWord()
|
||||
let d = localStorage.getItem(PracticeSaveKey.Word)
|
||||
if (d) {
|
||||
try {
|
||||
let data = JSON.parse(d)
|
||||
currentStudy = data.studyData
|
||||
isSaveData = true
|
||||
} catch (e) {
|
||||
localStorage.removeItem(PracticeSaveKey.Word)
|
||||
currentStudy = getCurrentStudyWord()
|
||||
}
|
||||
} else {
|
||||
currentStudy = getCurrentStudyWord()
|
||||
}
|
||||
}
|
||||
loading = false
|
||||
}
|
||||
@@ -71,7 +85,6 @@ function startPractice() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let showPracticeSettingDialog = $ref(false)
|
||||
let showChangeLastPracticeIndexDialog = $ref(false)
|
||||
|
||||
@@ -162,7 +175,7 @@ function check(cb: Function) {
|
||||
</div>
|
||||
|
||||
<div class="w-3/10 flex flex-col justify-evenly">
|
||||
<div class="center text-xl">今日任务</div>
|
||||
<div class="center text-xl">{{ isSaveData ? '上次学习任务' : '今日任务' }}</div>
|
||||
<div class="flex">
|
||||
<div class="flex-1 flex flex-col items-center">
|
||||
<div class="text-4xl font-bold">{{ currentStudy.new.length }}</div>
|
||||
@@ -196,7 +209,7 @@ function check(cb: Function) {
|
||||
:loading="loading"
|
||||
@click="startPractice">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="line-height-[2]">开始学习</span>
|
||||
<span class="line-height-[2]">{{ isSaveData ? '继续上次学习' : '开始学习' }}</span>
|
||||
<IconFluentArrowCircleRight16Regular class="text-xl"/>
|
||||
</div>
|
||||
</BaseButton>
|
||||
@@ -246,7 +259,15 @@ function check(cb: Function) {
|
||||
|
||||
<PracticeSettingDialog
|
||||
:show-left-option="false"
|
||||
v-model="showPracticeSettingDialog" @ok="currentStudy = getCurrentStudyWord()"/>
|
||||
v-model="showPracticeSettingDialog"
|
||||
@ok="()=>{
|
||||
if(isSaveData) {
|
||||
Toast.success('修改成功,完成当前任务后生效')
|
||||
}else {
|
||||
Toast.success('修改成功')
|
||||
currentStudy = getCurrentStudyWord()
|
||||
}
|
||||
}"/>
|
||||
|
||||
<ChangeLastPracticeIndexDialog
|
||||
v-model="showChangeLastPracticeIndexDialog"
|
||||
|
||||
@@ -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 {sleep} from "@/utils";
|
||||
import { usePracticeStore } from "@/stores/practice.ts";
|
||||
import { getDefaultWord } from "@/types/func.ts";
|
||||
import { _nextTick, sleep } from "@/utils";
|
||||
|
||||
interface IProps {
|
||||
word: Word,
|
||||
@@ -30,7 +30,12 @@ let showFullWord = $ref(false)
|
||||
//输入锁定,因为跳转到下一个单词有延时,如果重复在延时期间内重复输入,导致会跳转N次
|
||||
let inputLock = false
|
||||
let wordRepeatCount = 0
|
||||
let cursor = $ref({
|
||||
top: 0,
|
||||
left: 0,
|
||||
})
|
||||
const settingStore = useSettingStore()
|
||||
const statStore = usePracticeStore()
|
||||
|
||||
const playBeep = usePlayBeep()
|
||||
const playCorrect = usePlayCorrect()
|
||||
@@ -38,6 +43,7 @@ const playKeyboardAudio = usePlayKeyboardAudio()
|
||||
const playWordAudio = usePlayWordAudio()
|
||||
// const ttsPlayAudio = useTTsPlayAudio()
|
||||
const volumeIconRef: any = $ref()
|
||||
const typingWordRef = $ref<HTMLDivElement>()
|
||||
// const volumeTranslateIconRef: any = $ref()
|
||||
|
||||
let displayWord = $computed(() => {
|
||||
@@ -63,6 +69,7 @@ watch(() => props.word, () => {
|
||||
}
|
||||
// 更新当前单词信息
|
||||
updateCurrentWordInfo();
|
||||
checkCursorPosition()
|
||||
}, {deep: true})
|
||||
|
||||
// 监听输入变化,更新当前单词信息
|
||||
@@ -73,7 +80,7 @@ watch(() => input, () => {
|
||||
onMounted(() => {
|
||||
// 初始化当前单词信息
|
||||
updateCurrentWordInfo();
|
||||
|
||||
|
||||
emitter.on(EventKey.resetWord, () => {
|
||||
wrong = input = ''
|
||||
updateCurrentWordInfo();
|
||||
@@ -110,22 +117,22 @@ async function onTyping(e: KeyboardEvent) {
|
||||
}
|
||||
let letter = e.key
|
||||
inputLock = true
|
||||
|
||||
|
||||
// 检查当前单词是否包含空格
|
||||
const wordContainsSpace = props.word.word.includes(' ')
|
||||
|
||||
|
||||
// 如果是空格键,需要判断是作为输入还是切换单词
|
||||
if (letter === ' ' || e.code === 'Space') {
|
||||
// 如果当前单词包含空格
|
||||
if (wordContainsSpace && props.word.word[input.length] === ' ') {
|
||||
letter = ' '
|
||||
}
|
||||
}
|
||||
// 如果当前单词不包含空格,且已经输入完成,则视为切换单词的信号
|
||||
else if (!wordContainsSpace && input.toLowerCase() === props.word.word.toLowerCase()) {
|
||||
return emit('complete')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let isTypingRight = false
|
||||
if (settingStore.ignoreCase) {
|
||||
isTypingRight = letter.toLowerCase() === props.word.word[input.length].toLowerCase()
|
||||
@@ -171,6 +178,7 @@ async function onTyping(e: KeyboardEvent) {
|
||||
} else {
|
||||
inputLock = false
|
||||
}
|
||||
checkCursorPosition()
|
||||
}
|
||||
|
||||
function del() {
|
||||
@@ -182,12 +190,11 @@ function del() {
|
||||
} else {
|
||||
input = input.slice(0, -1)
|
||||
}
|
||||
|
||||
|
||||
// 更新当前单词信息
|
||||
updateCurrentWordInfo();
|
||||
}
|
||||
|
||||
const statStore = usePracticeStore()
|
||||
|
||||
function showWord() {
|
||||
if (settingStore.allowWordTip) {
|
||||
@@ -226,7 +233,7 @@ function hideWordInTranslation(text: string, word: string): string {
|
||||
if (!text || !word) {
|
||||
return text
|
||||
}
|
||||
|
||||
|
||||
// 创建正则表达式,匹配单词本身及其常见变形(如复数、过去式等)
|
||||
const wordBase = word.toLowerCase()
|
||||
const patterns = [
|
||||
@@ -236,13 +243,13 @@ function hideWordInTranslation(text: string, word: string): string {
|
||||
`\\b${escapeRegExp(wordBase)}ed\\b`, // 过去式
|
||||
`\\b${escapeRegExp(wordBase)}ing\\b`, // 进行时
|
||||
]
|
||||
|
||||
|
||||
let result = text
|
||||
patterns.forEach(pattern => {
|
||||
const regex = new RegExp(pattern, 'gi')
|
||||
result = result.replace(regex, match => `<span class="word-shadow">${match}</span>`)
|
||||
})
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -251,10 +258,37 @@ function escapeRegExp(string: string): string {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
}
|
||||
|
||||
watch(() => input, checkCursorPosition)
|
||||
|
||||
//检测光标位置
|
||||
function checkCursorPosition() {
|
||||
_nextTick(() => {
|
||||
// 选中目标元素
|
||||
const cursorEl = document.querySelector(`.cursor`);
|
||||
const input = document.querySelector(`.input`);
|
||||
const typingWordRect = typingWordRef.getBoundingClientRect();
|
||||
|
||||
if (input) {
|
||||
let inputRect = input.getBoundingClientRect();
|
||||
cursor = {
|
||||
top: inputRect.top + inputRect.height - cursorEl.clientHeight - typingWordRect.top,
|
||||
left: inputRect.right - typingWordRect.left - 3,
|
||||
};
|
||||
} else {
|
||||
const letter = document.querySelector(`.letter`);
|
||||
let letterRect = letter.getBoundingClientRect();
|
||||
cursor = {
|
||||
top: letterRect.top + letterRect.height - cursorEl.clientHeight - typingWordRect.top,
|
||||
left: letterRect.left - typingWordRect.left - 3,
|
||||
};
|
||||
}
|
||||
},)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="typing-word">
|
||||
<div class="typing-word" ref="typingWordRef">
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="flex gap-1 mt-26">
|
||||
<div class="phonetic" v-if="settingStore.soundType === 'us' && word.phonetic0">[{{
|
||||
@@ -265,12 +299,9 @@ function escapeRegExp(string: string): string {
|
||||
(settingStore.dictation && !showFullWord) ? '_'.repeat(word.phonetic1.length) : word.phonetic1
|
||||
}}]
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
<VolumeIcon
|
||||
:title="`发音(${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
|
||||
>
|
||||
<VolumeIcon ref="volumeIconRef" :simple="true" :cb="() => playWordAudio(word.word)"/>
|
||||
</Tooltip>
|
||||
ref="volumeIconRef" :simple="true" :cb="() => playWordAudio(word.word)"/>
|
||||
</div>
|
||||
|
||||
<div class="word my-1"
|
||||
@@ -318,7 +349,7 @@ function escapeRegExp(string: string): string {
|
||||
<div class="tabs">
|
||||
<div @click="tab = 0" class="tab" :class="tab === 0 && 'active'">短语</div>
|
||||
<div @click="tab = 1" class="tab" :class="tab === 1 && 'active'">同近义词</div>
|
||||
<!-- <div @click="tab = 2" class="tab" :class="tab === 2 && 'active'">同根词</div>-->
|
||||
<!-- <div @click="tab = 2" class="tab" :class="tab === 2 && 'active'">同根词</div>-->
|
||||
<div @click="tab = 3" class="tab" :class="tab === 3 && 'active'">词源</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -361,6 +392,8 @@ function escapeRegExp(string: string): string {
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="cursor"
|
||||
:style="{top:cursor.top+'px',left:cursor.left+'px',height: settingStore.fontSize.wordForeignFontSize +'px'}"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user