fix:Level8luan_2_T/coca20000 cannot be selected

This commit is contained in:
Zyronon
2025-12-11 02:35:23 +08:00
parent 1f4a1ea344
commit 32ded83ee6
7 changed files with 87 additions and 91 deletions

View File

@@ -1,6 +0,0 @@
FROM node
COPY . /root/typing-word
WORKDIR /root/typing-word
EXPOSE 3000
RUN npm install
CMD ["npm", "start"]

View File

@@ -99,7 +99,7 @@
如果您对本项目感兴趣,我们非常欢迎参与到项目的贡献中,我们会尽可能地提供帮助
在贡献前,希望您阅读 Issue #57 了解我们目前的开发计划,我们希望您能参与到"计划中"的工作亦或者 Issue 区 Label 为 "Help Wanted" 的工作,我们也非常欢迎您实现自己的想法。
在贡献前,希望您阅读 [Issue #57](https://github.com/zyronon/TypeWords/issues/57) 了解我们目前的开发计划,我们希望您能参与到"计划中"的工作亦或者 Issue 区 Label 为 "Help Wanted" 的工作,我们也非常欢迎您实现自己的想法。
如果您确定了想要参与的工作,希望在有基本进展后提交 draft pr我们可以在 draft pr 上进行讨论,也有利于听取其他 collaborator 的意见。

View File

@@ -1,20 +0,0 @@
version: "2"
services:
typeword:
image: "node:latest"
#environment: #按需配置, 主要为了科学上网解决依赖安装网络问题
# - HTTP_PROXY=http://127.0.0.1:80
# HTTPS_PROXY=http://127.0.0.1:80
working_dir: /home/node/app
volumes:#将代码目录直接映射到容器,节省打包拷贝时间
- ./:/home/node/app
expose:
- "3000"
ports:
- "3000:3000"
command:
- /bin/bash
- -c
- |
npm install
npm start

View File

@@ -1,9 +1,9 @@
<script setup lang="ts">
import {defineAsyncComponent, onMounted, watch} from "vue";
import {useSettingStore} from "@/stores/setting.ts";
import {jump2Feedback} from "@/utils";
import {useDisableEventListener} from "@/hooks/event.ts";
import { defineAsyncComponent, onMounted, watch } from "vue";
import { useSettingStore } from "@/stores/setting.ts";
import { jump2Feedback } from "@/utils";
import { useDisableEventListener } from "@/hooks/event.ts";
const Dialog = defineAsyncComponent(() => import('@/components/dialog/Dialog.vue'))
@@ -16,7 +16,7 @@ watch(() => settingStore.load, (n) => {
show = true
}, 300)
}
}, {immediate: true})
}, { immediate: true })
useDisableEventListener(() => show)
@@ -24,26 +24,31 @@ useDisableEventListener(() => show)
<template>
<Dialog
v-model="show"
title="重要提示"
footer
:closeOnClickBg="false"
cancel-button-text="不再提醒"
confirm-button-text="关闭"
@cancel="settingStore.conflictNotice = false"
v-model="show"
title="重要提示"
footer
:closeOnClickBg="false"
cancel-button-text="不再提醒"
confirm-button-text="关闭"
@cancel="settingStore.conflictNotice = false"
>
<div class="card w-150 center flex-col color-main py-0 mb-0">
<div class="text">
如果您安装了 <span class="font-bold text-red">调速 Vim</span> 等插件/脚本它们会拦截键盘按下事件<span
class="font-bold text-red">导致在本网站练习时按 'A' 'S' 'D' 等键无反应</span>您可以根据以下步骤解决冲突
class="font-bold text-red">导致在本网站练习时按 'A' 'S' 'D' 等键无反应</span>您可以根据以下步骤解决冲突
</div>
<ul class="m-0">
<li>用浏览器无痕模式打开本网站确认能否正常输入</li>
<li>无痕模式下无法输入请给<span class="color-link mx-1 cp" @click="jump2Feedback">点此</span>反馈</li>
<li>无痕模式下可以输入则是插件/脚本导致的冲突</li>
<li>临时禁用对应插件/脚本或在对应插件/脚本的设置里面排除本网站</li>
<li>可安装 <a
href="https://chromewebstore.google.com/detail/one-click-extensions-mana/pbgjpgbpljobkekbhnnmlikbbfhbhmem" target="_blank">此插件</a> 来快速激活禁用其他插件</li>
<li>可安装
<a href="https://chromewebstore.google.com/detail/one-click-extensions-mana/pbgjpgbpljobkekbhnnmlikbbfhbhmem"
target="_blank">插件(Chrome版本需翻墙)</a>,
<a href="https://microsoftedge.microsoft.com/addons/detail/%E5%BF%AB%E6%8D%B7%E6%89%A9%E5%B1%95%E7%AE%A1%E7%90%86/jdodenbllldnoogfmbmmgpieafbnaogm"
target="_blank">插件(Edge版本无需翻墙)</a>,
来快速激活禁用其他插件
</li>
</ul>
</div>
</Dialog>

View File

@@ -1,17 +1,18 @@
<script setup lang="ts">
import {ShortcutKey, Word, WordPracticeType} from "@/types/types.ts";
import { ShortcutKey, Word, WordPracticeType } from "@/types/types.ts";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import {useSettingStore} from "@/stores/setting.ts";
import {usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePlayWordAudio} from "@/hooks/sound.ts";
import {emitter, EventKey, useEvents} from "@/utils/eventBus.ts";
import {onMounted, onUnmounted, watch} from "vue";
import { useSettingStore } from "@/stores/setting.ts";
import { usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePlayWordAudio } from "@/hooks/sound.ts";
import { emitter, EventKey, useEvents } from "@/utils/eventBus.ts";
import { onMounted, onUnmounted, watch } from "vue";
import SentenceHightLightWord from "@/pages/word/components/SentenceHightLightWord.vue";
import {usePracticeStore} from "@/stores/practice.ts";
import {getDefaultWord} from "@/types/func.ts";
import {_nextTick, last} from "@/utils";
import { usePracticeStore } from "@/stores/practice.ts";
import { getDefaultWord } from "@/types/func.ts";
import { _nextTick, last } from "@/utils";
import BaseButton from "@/components/BaseButton.vue";
import Space from "@/pages/article/components/Space.vue";
import Toast from "@/components/base/toast/Toast.ts";
import Tooltip from "@/components/base/Tooltip.vue";
interface IProps {
word: Word,
@@ -33,6 +34,9 @@ let showFullWord = $ref(false)
//输入锁定因为跳转到下一个单词有延时如果重复在延时期间内重复输入导致会跳转N次
let inputLock = false
let wordRepeatCount = 0
// 记录单词完成的时间戳,用于防止同时按下最后一个字母和空格键时跳过单词
let wordCompletedTime = 0
let jumpTimer = -1
let cursor = $ref({
top: 0,
left: 0,
@@ -63,12 +67,13 @@ function updateCurrentWordInfo() {
};
}
watch(() => props.word, reset, {deep: true})
watch(() => props.word, reset, { deep: true })
function reset() {
wrong = input = ''
wordRepeatCount = 0
showWordResult = inputLock = false
wordCompletedTime = 0 // 重置时间戳
if (settingStore.wordSound) {
if (settingStore.wordPracticeType !== WordPracticeType.Dictation) {
volumeIconRef?.play(400, true)
@@ -127,7 +132,7 @@ function know(e) {
input = props.word.word
emit('know')
if (!showNotice) {
Toast.info('若误选“我认识”,可按删除键重新选择!', {duration: 5000})
Toast.info('若误选“我认识”,可按删除键重新选择!', { duration: 5000 })
showNotice = true
}
return
@@ -149,6 +154,7 @@ function unknown(e) {
}
async function onTyping(e: KeyboardEvent) {
debugger
let word = props.word.word
// 输入完成会锁死不能再输入
if (inputLock) {
@@ -156,6 +162,11 @@ async function onTyping(e: KeyboardEvent) {
if (e.code === 'Space') {
//正确时就切换到下一个单词
if (right) {
clearInterval(jumpTimer)
// 如果单词刚完成300ms内忽略空格键避免同时按下最后一个字母和空格键时跳过单词
if (wordCompletedTime && Date.now() - wordCompletedTime < 300) {
return
}
showWordResult = inputLock = false
emit('complete')
} else {
@@ -163,7 +174,7 @@ async function onTyping(e: KeyboardEvent) {
// 错误时,提示用户按删除键,仅默写需要提示
pressNumber++
if (pressNumber >= 3) {
Toast.info('请按删除键重新输入', {duration: 2000})
Toast.info('请按删除键重新输入', { duration: 2000 })
pressNumber = 0
}
}
@@ -173,7 +184,7 @@ async function onTyping(e: KeyboardEvent) {
if (right) {
pressNumber++
if (pressNumber >= 3) {
Toast.info('请按空格键继续', {duration: 2000})
Toast.info('请按空格键继续', { duration: 2000 })
pressNumber = 0
}
} else {
@@ -183,7 +194,6 @@ async function onTyping(e: KeyboardEvent) {
onTyping(e)
}
}
return
}
inputLock = true
@@ -253,6 +263,7 @@ async function onTyping(e: KeyboardEvent) {
updateCurrentWordInfo();
//不需要把inputLock设为false输入完成不能再输入了只能删除删除会打开锁
if (input.toLowerCase() === word.toLowerCase()) {
wordCompletedTime = Date.now() // 记录单词完成的时间戳
playCorrect()
if ([WordPracticeType.Listen, WordPracticeType.Identify].includes(settingStore.wordPracticeType) && !showWordResult) {
showWordResult = true
@@ -261,13 +272,13 @@ async function onTyping(e: KeyboardEvent) {
if (settingStore.autoNextWord) {
if (settingStore.repeatCount == 100) {
if (settingStore.repeatCustomCount <= wordRepeatCount + 1) {
setTimeout(() => emit('complete'), settingStore.waitTimeForChangeWord)
jumpTimer = setTimeout(() => emit('complete'), settingStore.waitTimeForChangeWord)
} else {
repeat()
}
} else {
if (settingStore.repeatCount <= wordRepeatCount + 1) {
setTimeout(() => emit('complete'), settingStore.waitTimeForChangeWord)
jumpTimer = setTimeout(() => emit('complete'), settingStore.waitTimeForChangeWord)
} else {
repeat()
}
@@ -329,7 +340,7 @@ function play() {
volumeIconRef?.play()
}
defineExpose({del, showWord, hideWord, play})
defineExpose({ del, showWord, hideWord, play })
function mouseleave() {
setTimeout(() => {
@@ -419,50 +430,56 @@ useEvents([
v-if="settingStore.soundType === 'us' && word.phonetic1">[{{ word.phonetic1 }}]
</div>
<VolumeIcon
:title="`发音(${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
ref="volumeIconRef" :simple="true" :cb="() => playWordAudio(word.word)"/>
:title="`发音(${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
ref="volumeIconRef" :simple="true" :cb="() => playWordAudio(word.word)"/>
</div>
<div id="word" class="word my-1"
:class="wrong && 'is-wrong'"
:style="{fontSize: settingStore.fontSize.wordForeignFontSize +'px'}"
@mouseenter="showWord"
@mouseleave="mouseleave"
>
<div v-if="settingStore.wordPracticeType === WordPracticeType.Dictation">
<div class="letter text-align-center w-full inline-block"
v-opacity="!settingStore.dictation || showWordResult || showFullWord">
{{ word.word }}
<Tooltip
:title="([WordPracticeType.FollowWrite,WordPracticeType.Identify].includes(settingStore.wordPracticeType) || !settingStore.dictation)
? ''
: `可以按快捷键 ${settingStore.shortcutKeyMap[ShortcutKey.ShowWord]} 显示正确答案`
">
<div id="word" class="word my-1"
:class="wrong && 'is-wrong'"
:style="{fontSize: settingStore.fontSize.wordForeignFontSize +'px'}"
@mouseenter="showWord"
@mouseleave="mouseleave"
>
<div v-if="settingStore.wordPracticeType === WordPracticeType.Dictation">
<div class="letter text-align-center w-full inline-block"
v-opacity="!settingStore.dictation || showWordResult || showFullWord">
{{ word.word }}
</div>
<div
class="mt-2 w-120 dictation"
:style="{minHeight: settingStore.fontSize.wordForeignFontSize +'px'}"
:class="showWordResult ? (right ? 'right' : 'wrong') : ''">
<template v-for="i in input">
<span class="l" v-if="i !== ' '">{{ i }}</span>
<Space class="l" v-else :is-wrong="showWordResult ? (!right) : false" :is-wait="!showWordResult"/>
</template>
</div>
</div>
<div
class="mt-2 w-120 dictation"
:style="{minHeight: settingStore.fontSize.wordForeignFontSize +'px'}"
:class="showWordResult ? (right ? 'right' : 'wrong') : ''">
<template v-for="i in input">
<span class="l" v-if="i !== ' '">{{ i }}</span>
<Space class="l" v-else :is-wrong="showWordResult ? (!right) : false" :is-wait="!showWordResult"/>
</template>
</div>
</div>
<template v-else>
<span class="input" v-if="input">{{ input }}</span>
<span class="wrong" v-if="wrong">{{ wrong }}</span>
<span class="letter" v-if="settingStore.dictation && !showFullWord">
<template v-else>
<span class="input" v-if="input">{{ input }}</span>
<span class="wrong" v-if="wrong">{{ wrong }}</span>
<span class="letter" v-if="settingStore.dictation && !showFullWord">
{{ displayWord.split('').map((v) => (v === ' ' ? '&nbsp;' : '_')).join('') }}
</span>
<span class="letter" v-else>{{ displayWord }}</span>
</template>
</div>
<span class="letter" v-else>{{ displayWord }}</span>
</template>
</div>
</Tooltip>
<div class="mt-4 flex gap-4"
v-if="settingStore.wordPracticeType === WordPracticeType.Identify && !showWordResult">
<BaseButton
:keyboard="`快捷键(${settingStore.shortcutKeyMap[ShortcutKey.KnowWord]})`"
size="large" @click="know">我认识
:keyboard="`快捷键(${settingStore.shortcutKeyMap[ShortcutKey.KnowWord]})`"
size="large" @click="know">我认识
</BaseButton>
<BaseButton
:keyboard="`快捷键(${settingStore.shortcutKeyMap[ShortcutKey.UnknownWord]})`"
size="large" @click="unknown">不认识
:keyboard="`快捷键(${settingStore.shortcutKeyMap[ShortcutKey.UnknownWord]})`"
size="large" @click="unknown">不认识
</BaseButton>
</div>