454 lines
12 KiB
Vue
454 lines
12 KiB
Vue
<template>
|
|
<div>
|
|
<el-page-header class="recite-view-header recite-view-animation-el" @back="$router.push('/')">
|
|
<template #content>
|
|
<span class="text-large font-600 mr-3"> 背诵 </span>
|
|
</template>
|
|
</el-page-header>
|
|
<div id="test" class="colbox">
|
|
<div style="flex-grow: 1;" class="card recite-view-animation-el">
|
|
<div id="status">背诵进度: {{ current + 1 }}/{{ total }}</div>
|
|
<div>
|
|
<textarea autofocus id="input" ref="inputAreaRef" v-if="current === answered" @input="change"
|
|
class="answer" spellcheck="false">
|
|
</textarea>
|
|
<div v-if="current < answered" class="answer">{{ word.word }}</div>
|
|
</div>
|
|
<div id="explain">
|
|
<el-button v-if="current > 0" @click="prev">上一个</el-button>
|
|
<el-button v-if="current < answered" @click="next">下一个</el-button>
|
|
<el-button v-if="current === answered" @click="showAnswer">显示答案</el-button>
|
|
<el-button v-if="current === answered" type="warning" @click="skip">跳过</el-button>
|
|
<el-button @click="terminate" type="danger">停止背诵</el-button>
|
|
<v-icon style="margin-left: 10px;translate: 0 4px;"
|
|
@click="audio_play">mdi-volume-high</v-icon>
|
|
</div>
|
|
<div id="trans">{{ word.type }} {{ word.trans }}</div>
|
|
</div>
|
|
<div id="add-to-box" class="card recite-view-animation-el">
|
|
<el-text class="mx-1 title">加入至</el-text>
|
|
<div class="colbox para">
|
|
<div class="mid-text">分组:</div>
|
|
<el-select v-model="set_class" class="m-2" placeholder="Select">
|
|
<el-option v-for="item in wordsets._allclass" :key="item" :label="item" :value="item" />
|
|
</el-select>
|
|
</div>
|
|
<div class="colbox para">
|
|
<div class="mid-text">单词本:</div>
|
|
<el-select v-model="set_id" class="m-2" placeholder="Select">
|
|
<el-option v-for="item in wordsets.getClass(set_class)" :key="item.id" :label="item.name"
|
|
:value="item.id" />
|
|
</el-select>
|
|
</div>
|
|
<el-button @click="add_to" type="primary">添加</el-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
|
import { useRouter, useRoute } from 'vue-router';
|
|
import { ElMessage, ElNotification, ElMessageBox } from 'element-plus';
|
|
import { useMainStore } from '@/store';
|
|
import { WordItem } from '@/types';
|
|
import anime from 'animejs';
|
|
|
|
const router = useRouter();
|
|
const route = useRoute();
|
|
const store = useMainStore();
|
|
|
|
interface Settings {
|
|
direct_answer: boolean;
|
|
}
|
|
|
|
const wordsets = ref(store.wordsets)
|
|
const testWords = ref<WordItem[]>([]);
|
|
const seq = ref<number[]>([]);
|
|
const total = ref(0);
|
|
const current = ref(0);
|
|
const answered = ref(0);
|
|
const word = ref<WordItem>({} as WordItem);
|
|
const set_class = ref("");
|
|
const set_id = ref("");
|
|
const settings = ref<Settings>({
|
|
direct_answer: false,
|
|
});
|
|
const inputAreaRef = ref<HTMLTextAreaElement | null>(null)
|
|
|
|
const key_listener = (event: KeyboardEvent) => {
|
|
let ctrlKey = event.ctrlKey || event.metaKey;
|
|
if (ctrlKey) {
|
|
switch (event.key) {
|
|
case 'b':
|
|
showAnswer();
|
|
break;
|
|
case 'm':
|
|
skip();
|
|
break;
|
|
case 'i':
|
|
add_to();
|
|
break;
|
|
case 'ArrowLeft':
|
|
prev();
|
|
break;
|
|
case 'ArrowRight':
|
|
next();
|
|
break;
|
|
case 'y':
|
|
audio_play();
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
event.preventDefault();
|
|
};
|
|
|
|
const next = () => {
|
|
if (current.value < answered.value) {
|
|
current.value++;
|
|
if (current.value === total.value) {
|
|
ElNotification({
|
|
message: "您已完成所有单词的背诵",
|
|
title: "Congratulations!",
|
|
type: "success"
|
|
});
|
|
router.push('/');
|
|
return;
|
|
}
|
|
nextTick(() => {
|
|
show();
|
|
});
|
|
} else {
|
|
ElMessage({
|
|
type: "error",
|
|
message: "您还未答过该词,跳过请使用ctrl+M",
|
|
});
|
|
}
|
|
};
|
|
|
|
const skip = () => {
|
|
ElMessage({
|
|
message: "已经跳过该词",
|
|
type: "info"
|
|
});
|
|
answered.value++;
|
|
store.history.count(answered.value);
|
|
next();
|
|
};
|
|
|
|
const prev = () => {
|
|
if (current.value > 0) {
|
|
current.value--;
|
|
show();
|
|
} else {
|
|
ElMessage({
|
|
type: 'error',
|
|
message: "已经是第一个单词"
|
|
});
|
|
}
|
|
};
|
|
|
|
const showAnswer = () => {
|
|
if (!settings.value.direct_answer) {
|
|
if (inputAreaRef.value) {
|
|
const inputArea = inputAreaRef.value;
|
|
if (inputArea.value === "_".repeat(word.value.word.length)) {
|
|
inputArea.value = word.value.word[0] + "_".repeat(word.value.word.length - 1);
|
|
inputArea.setSelectionRange(1, 1);
|
|
ElMessage(`首字母为:${word.value.word[0]}`);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
ElMessage(`答案:${word.value.word}`);
|
|
};
|
|
|
|
const show = () => {
|
|
word.value = testWords.value[seq.value[current.value]];
|
|
if (current.value === answered.value) {
|
|
if (inputAreaRef.value) {
|
|
const inputArea = inputAreaRef.value;
|
|
inputArea.value = "_".repeat(word.value.word.length);
|
|
inputArea.setSelectionRange(0, 0);
|
|
inputArea.style.height = "0";
|
|
setTimeout(() => {
|
|
inputArea.style.height = `${inputArea.scrollHeight}px`;
|
|
}, 200);
|
|
}
|
|
}
|
|
};
|
|
|
|
const change = () => {
|
|
if (inputAreaRef.value) {
|
|
const inputArea = inputAreaRef.value;
|
|
const cur = inputArea.selectionStart;
|
|
const value = inputArea.value;
|
|
const n = value.indexOf("_");
|
|
let content: string;
|
|
|
|
if (n == word.value.word.length) {
|
|
if (value.substring(0, value.length - 1).toLowerCase() == word.value.word.toLowerCase()) {
|
|
ElMessage.success("正确");
|
|
answered.value++;
|
|
store.history.count(answered.value);
|
|
next();
|
|
return;
|
|
}
|
|
ElMessage.error("错误");
|
|
content = "_".repeat(word.value.word.length);
|
|
inputArea.value = content;
|
|
inputArea.setSelectionRange(0, 0);
|
|
inputArea.style.height = "0";
|
|
setTimeout(() => {
|
|
inputArea.style.height = `${inputArea.scrollHeight}px`;
|
|
}, 200);
|
|
return;
|
|
} else {
|
|
n == -1 ? content = "_".repeat(word.value.word.length) : (content = value.substring(0, n),
|
|
content += "_".repeat(word.value.word.length - n));
|
|
inputArea.value = content;
|
|
if (cur && cur > n) {
|
|
inputArea.setSelectionRange(n, n);
|
|
} else if (cur !== null) {
|
|
inputArea.setSelectionRange(cur, cur);
|
|
}
|
|
}
|
|
setTimeout(() => {
|
|
const height = parseInt(inputArea.style.height || '0');
|
|
if (height < inputArea.scrollHeight) {
|
|
inputArea.style.height = `${inputArea.scrollHeight}px`;
|
|
}
|
|
}, 0);
|
|
inputArea.focus();
|
|
}
|
|
|
|
};
|
|
|
|
const audio_play = () => {
|
|
const t = new Audio("https://dict.youdao.com/dictvoice?audio=" + word.value.word);
|
|
t.play();
|
|
};
|
|
|
|
const terminate = () => {
|
|
ElMessageBox.confirm("确定要终止吗?")
|
|
.then(() => {
|
|
router.push('/');
|
|
ElNotification({
|
|
message: "背诵已终止",
|
|
type: "info",
|
|
title: "Terminate"
|
|
});
|
|
});
|
|
};
|
|
|
|
const add_to = () => {
|
|
if (set_id.value) {
|
|
const data = JSON.parse(localStorage.getItem(set_id.value) || '[]');
|
|
for (let i of data) {
|
|
if (i.word === word.value.word) {
|
|
ElMessage({
|
|
message: '已经添加过该词',
|
|
type: 'error',
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
data.push(word.value);
|
|
localStorage.setItem(set_id.value, JSON.stringify(data));
|
|
ElMessage({
|
|
message: `已添加 ${word.value.word} (${word.value.type})`,
|
|
type: 'success',
|
|
});
|
|
} else {
|
|
ElMessage({
|
|
message: '请先选择单词本',
|
|
type: 'error',
|
|
});
|
|
}
|
|
};
|
|
|
|
onMounted(() => {
|
|
document.addEventListener('keyup', key_listener);
|
|
const index = route.query.index;
|
|
if (index) {
|
|
store.history.use(Number(index));
|
|
}
|
|
|
|
store.history.init_recite((words: WordItem[], currentVal: number, seqVal: number[], settingsVal: Settings) => {
|
|
testWords.value = words;
|
|
current.value = currentVal;
|
|
total.value = words.length;
|
|
answered.value = currentVal;
|
|
settings.value = settingsVal;
|
|
seq.value = seqVal;
|
|
|
|
if (answered.value >= total.value) {
|
|
answered.value = 0;
|
|
current.value = 0;
|
|
ElMessage({
|
|
type: 'warning',
|
|
message: "进度已经重置"
|
|
});
|
|
store.history.count(answered.value);
|
|
}
|
|
|
|
nextTick(() => {
|
|
show();
|
|
});
|
|
}, (msg: string) => {
|
|
ElMessage({
|
|
type: 'error',
|
|
message: msg
|
|
});
|
|
router.push('/select');
|
|
});
|
|
anime({
|
|
targets: '.recite-view-animation-el',
|
|
translateY: [-50, 0],
|
|
opacity: [0, 1],
|
|
delay: anime.stagger(50),
|
|
});
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
document.removeEventListener("keyup", key_listener);
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
.recite-view-header {
|
|
margin-top:20px;
|
|
margin-left:20px
|
|
}
|
|
</style>
|
|
|
|
<style scoped>
|
|
@media screen and (max-width: 500px) {
|
|
|
|
.title {
|
|
font-size: 30px;
|
|
font-weight: 800;
|
|
color: var(--text-color);
|
|
}
|
|
|
|
#test {
|
|
flex-direction: column;
|
|
animation: enter .5s ease-out;
|
|
}
|
|
|
|
#trans {
|
|
font-size: 25px;
|
|
transition: .5s;
|
|
margin-top: 15px;
|
|
}
|
|
|
|
#explain {
|
|
font-size: 18px;
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.el-button {
|
|
width: 55px;
|
|
height: 28px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.tbtn {
|
|
width: 100%;
|
|
}
|
|
|
|
.answer {
|
|
font-size: 55px;
|
|
letter-spacing: 15px;
|
|
min-height: 50px;
|
|
}
|
|
|
|
.card {
|
|
box-shadow: none;
|
|
}
|
|
|
|
.checkbox {
|
|
font-size: 30px;
|
|
margin-bottom: 10px;
|
|
margin-right: 15px;
|
|
}
|
|
}
|
|
|
|
@media screen and (min-width: 500px) {
|
|
.item {
|
|
margin-bottom: 20px;
|
|
/* background-color: var(--bg-color); */
|
|
width: 50%;
|
|
}
|
|
|
|
.tbtn {
|
|
width: 50%;
|
|
}
|
|
|
|
.title {
|
|
font-size: 35px;
|
|
font-weight: 800;
|
|
color: var(--text-color);
|
|
}
|
|
|
|
#test {
|
|
padding: 30px;
|
|
animation: enter .5s ease-out;
|
|
}
|
|
|
|
#trans {
|
|
font-size: 43px;
|
|
transition: .5s;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
#add-to-box {
|
|
width: 30%;
|
|
min-width: 200px;
|
|
}
|
|
|
|
#explain {
|
|
font-size: 25px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.answer {
|
|
font-size: 70px;
|
|
letter-spacing: 25px;
|
|
min-height: 100px;
|
|
}
|
|
|
|
.checkbox {
|
|
font-size: 30px;
|
|
margin-bottom: 10px;
|
|
}
|
|
}
|
|
|
|
.wrapper {
|
|
height: calc(100% - 10px);
|
|
}
|
|
|
|
.container {
|
|
padding: 10px;
|
|
height: calc(100% - 50px);
|
|
}
|
|
|
|
#status {
|
|
font-size: 20px;
|
|
}
|
|
|
|
.answer {
|
|
margin-top: 10px;
|
|
border-radius: 8px;
|
|
background-color: var(--bg-color);
|
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
|
border: none;
|
|
outline-style: none;
|
|
padding: 0;
|
|
transition: .5s;
|
|
resize: vertical;
|
|
width: 100%;
|
|
}
|
|
</style> |