fix:修复文章界面toolbar有问题
This commit is contained in:
54
index.html
54
index.html
@@ -1,34 +1,34 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<link rel="icon" type="image/svg+xml" href="/logo.jpg"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>TypeWords练习英语</title>
|
||||
<script defer src="./s.js" data-website-id="160308c9-7900-4b1d-a0b1-c3b25a9530f6"></script>
|
||||
<script>
|
||||
;(function () {
|
||||
var src = '//cdn.jsdelivr.net/npm/eruda';
|
||||
if (!/eruda=true/.test(window.location) && localStorage.getItem('active-eruda') != 'true') return;
|
||||
document.write('<scr' + 'ipt src="' + src + '"></scr' + 'ipt>');
|
||||
document.write('<scr' + 'ipt>eruda.init();</scr' + 'ipt>');
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
if (!location.href.includes('localhost')
|
||||
&& !location.href.includes('192.168')
|
||||
&& !location.href.includes('172.16')
|
||||
&& !location.href.includes('10.0')
|
||||
) {
|
||||
var _hmt = _hmt || [];
|
||||
(function () {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "https://hm.baidu.com/hm.js?3dae52fcd5375a19905462e4ad3eb54e";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
<meta charset="UTF-8"/>
|
||||
<link rel="icon" type="image/svg+xml" href="/logo.jpg"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>TypeWords练习英语</title>
|
||||
<script defer src="./s.js" data-website-id="160308c9-7900-4b1d-a0b1-c3b25a9530f6"></script>
|
||||
<script>
|
||||
;(function () {
|
||||
var src = '//cdn.jsdelivr.net/npm/eruda';
|
||||
if (!/eruda=true/.test(window.location) && localStorage.getItem('active-eruda') != 'true') return;
|
||||
document.write('<scr' + 'ipt src="' + src + '"></scr' + 'ipt>');
|
||||
document.write('<scr' + 'ipt>eruda.init();</scr' + 'ipt>');
|
||||
})();
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
<script>
|
||||
if (!location.href.includes('localhost')
|
||||
&& !location.href.includes('192.168')
|
||||
&& !location.href.includes('172.16')
|
||||
&& !location.href.includes('10.0')
|
||||
) {
|
||||
var _hmt = _hmt || [];
|
||||
(function () {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "https://hm.baidu.com/hm.js?3dae52fcd5375a19905462e4ad3eb54e";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -17,10 +17,11 @@
|
||||
|
||||
--practice-wrapper-translateX: 1px;
|
||||
--article-width: 50vw;
|
||||
--article-toolbar-width: 50rem;
|
||||
--toolbar-width: 50rem;
|
||||
--panel-width: 24rem;
|
||||
--space: 1rem;
|
||||
--stat-gap: 2rem;
|
||||
--stat-gap: 1rem;
|
||||
--shadow: rgba(0, 0, 0, 0.08) 0px 4px 12px;
|
||||
--panel-margin-left: calc(50% + var(--toolbar-width) / 2 + 1rem);
|
||||
--article-panel-margin-left: calc(50% + var(--article-width) / 2 + 1rem);
|
||||
@@ -416,7 +417,7 @@ a {
|
||||
|
||||
.book {
|
||||
@extend .anim;
|
||||
@apply p-4 rounded-md relative cursor-pointer bg-third hover:bg-card-active flex flex-col justify-between;
|
||||
@apply p-4 rounded-md relative cursor-pointer bg-third hover:bg-card-active flex flex-col justify-between shrink-0;
|
||||
$w: 6rem;
|
||||
width: $w;
|
||||
height: calc($w * 1.4);
|
||||
|
||||
@@ -126,7 +126,7 @@ async function goBookDetail(val: DictResource) {
|
||||
<div class="color-blue cursor-pointer" @click="nav('dict-detail', { isAdd: true })">创建个人书籍</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-6 gap-4 mt-4">
|
||||
<div class="flex gap-4 flex-wrap mt-4">
|
||||
<Book :is-add="false" quantifier="篇" :item="item" :checked="selectIds.includes(item.id)"
|
||||
@check="() => toggleSelect(item)"
|
||||
:show-checkbox="isMultiple && j >= 1"
|
||||
|
||||
@@ -2,23 +2,45 @@
|
||||
|
||||
import {onMounted, onUnmounted} from "vue";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
|
||||
import Statistics from "@/pages/pc/word/Statistics.vue";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts";
|
||||
import {emitter, EventKey, useEvents} from "@/utils/eventBus.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import PracticeArticle from "@/pages/pc/article/practice-article/index.vue";
|
||||
import {ShortcutKey} from "@/types/types.ts";
|
||||
import {useStartKeyboardEventListener} from "@/hooks/event.ts";
|
||||
import {Article, ArticleItem, ArticleWord, ShortcutKey, Word} from "@/types/types.ts";
|
||||
import {useOnKeyboardEventListener, useStartKeyboardEventListener} from "@/hooks/event.ts";
|
||||
import useTheme from "@/hooks/theme.ts";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {cloneDeep} from "@/utils";
|
||||
import {usePracticeStore} from "@/stores/practice.ts";
|
||||
import {useArticleOptions} from "@/hooks/dict.ts";
|
||||
import {genArticleSectionData, usePlaySentenceAudio} from "@/hooks/article.ts";
|
||||
import router from "@/router.ts";
|
||||
import {getDefaultArticle} from "@/types/func.ts";
|
||||
import TypingArticle from "@/pages/pc/article/components/TypingArticle.vue";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import Panel from "@/pages/pc/components/Panel.vue";
|
||||
import ArticleList from "@/pages/pc/components/list/ArticleList.vue";
|
||||
import EditSingleArticleModal from "@/pages/pc/article/components/EditSingleArticleModal.vue";
|
||||
import Tooltip from "@/pages/pc/components/Tooltip.vue";
|
||||
import {Icon} from "@iconify/vue";
|
||||
|
||||
const store = useBaseStore()
|
||||
const settingStore = useSettingStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const statisticsStore = usePracticeStore()
|
||||
const {toggleTheme} = useTheme()
|
||||
const practiceRef: any = $ref()
|
||||
|
||||
let articleData = $ref({
|
||||
list: [],
|
||||
article: getDefaultArticle(),
|
||||
sectionIndex: 0,
|
||||
sentenceIndex: 0,
|
||||
wordIndex: 0,
|
||||
stringIndex: 0,
|
||||
})
|
||||
let showEditArticle = $ref(false)
|
||||
let typingArticleRef = $ref<any>()
|
||||
let editArticle = $ref<Article>(getDefaultArticle())
|
||||
|
||||
useStartKeyboardEventListener()
|
||||
|
||||
function write() {
|
||||
// console.log('write')
|
||||
@@ -27,81 +49,427 @@ function write() {
|
||||
}
|
||||
|
||||
//TODO 需要判断是否已忽略
|
||||
//todo 使用场景是?
|
||||
function repeat() {
|
||||
// console.log('repeat')
|
||||
emitter.emit(EventKey.resetWord)
|
||||
practiceRef.getCurrentPractice()
|
||||
getCurrentPractice()
|
||||
}
|
||||
|
||||
function prev() {
|
||||
// console.log('next')
|
||||
if (store.currentBook.chapterIndex === 0) {
|
||||
if (store.sbook.lastLearnIndex === 0) {
|
||||
ElMessage.warning('已经在第一章了~')
|
||||
} else {
|
||||
store.currentBook.chapterIndex--
|
||||
repeat()
|
||||
store.sbook.lastLearnIndex--
|
||||
getCurrentPractice()
|
||||
}
|
||||
}
|
||||
|
||||
function toggleShowTranslate() {
|
||||
settingStore.translate = !settingStore.translate
|
||||
}
|
||||
|
||||
function toggleDictation() {
|
||||
settingStore.dictation = !settingStore.dictation
|
||||
}
|
||||
const toggleShowTranslate = () => settingStore.translate = !settingStore.translate
|
||||
const toggleDictation = () => settingStore.dictation = !settingStore.dictation
|
||||
const togglePanel = () => settingStore.showPanel = !settingStore.showPanel
|
||||
const skip = () => typingArticleRef?.nextSentence()
|
||||
const collect = () => toggleArticleCollect(articleData.article)
|
||||
const shortcutKeyEdit = () => edit()
|
||||
|
||||
function toggleConciseMode() {
|
||||
settingStore.showToolbar = !settingStore.showToolbar
|
||||
settingStore.showPanel = settingStore.showToolbar
|
||||
}
|
||||
|
||||
function togglePanel() {
|
||||
settingStore.showPanel = !settingStore.showPanel
|
||||
function next() {
|
||||
if (store.sbook.lastLearnIndex >= articleData.list.length - 1) {
|
||||
store.sbook.lastLearnIndex = 0
|
||||
//todo 这里应该弹窗
|
||||
} else store.sbook.lastLearnIndex++
|
||||
getCurrentPractice()
|
||||
}
|
||||
|
||||
function jumpSpecifiedChapter(val: number) {
|
||||
store.currentBook.chapterIndex = val
|
||||
repeat()
|
||||
function init() {
|
||||
if (!store.sbook?.articles?.length) {
|
||||
router.push('/article')
|
||||
return
|
||||
}
|
||||
articleData.list = cloneDeep(store.sbook.articles)
|
||||
getCurrentPractice()
|
||||
console.log('init', articleData.article)
|
||||
}
|
||||
|
||||
function setArticle(val: Article) {
|
||||
statisticsStore.inputWordNumber = 0
|
||||
statisticsStore.wrong = 0
|
||||
statisticsStore.total = 0
|
||||
statisticsStore.startDate = Date.now()
|
||||
|
||||
articleData.list[store.sbook.lastLearnIndex] = val
|
||||
articleData.article = val
|
||||
articleData.sectionIndex = 0
|
||||
articleData.sentenceIndex = 0
|
||||
articleData.wordIndex = 0
|
||||
articleData.stringIndex = 0
|
||||
articleData.article.sections.map((v, i) => {
|
||||
v.map((w, j) => {
|
||||
w.words.map(s => {
|
||||
if (!store.knownWordsWithSimpleWords.includes(s.word.toLowerCase()) && !s.isSymbol) {
|
||||
statisticsStore.total++
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getCurrentPractice() {
|
||||
emitter.emit(EventKey.resetWord)
|
||||
let currentArticle = articleData.list[store.sbook.lastLearnIndex]
|
||||
let article = getDefaultArticle(currentArticle)
|
||||
// console.log('article', article)
|
||||
if (article.sections.length) {
|
||||
setArticle(article)
|
||||
} else {
|
||||
genArticleSectionData(article)
|
||||
setArticle(article)
|
||||
}
|
||||
}
|
||||
|
||||
function saveArticle(val: Article) {
|
||||
console.log('saveArticle', val, JSON.stringify(val.lrcPosition))
|
||||
console.log('saveArticle', val.textTranslate)
|
||||
showEditArticle = false
|
||||
let rIndex = store.sbook.articles.findIndex(v => v.id === val.id)
|
||||
if (rIndex > -1) {
|
||||
store.sbook.articles[rIndex] = cloneDeep(val)
|
||||
}
|
||||
setArticle(val)
|
||||
}
|
||||
|
||||
function edit(val: Article = articleData.article) {
|
||||
editArticle = val
|
||||
showEditArticle = true
|
||||
}
|
||||
|
||||
function wrong(word: Word) {
|
||||
let lowerName = word.word.toLowerCase();
|
||||
if (!store.wrong.words.find((v: Word) => v.word.toLowerCase() === lowerName)) {
|
||||
store.wrong.words.push(word)
|
||||
}
|
||||
if (!store.knownWordsWithSimpleWords.includes(lowerName)) {
|
||||
//todo
|
||||
}
|
||||
}
|
||||
|
||||
function nextWord(word: ArticleWord) {
|
||||
if (!store.knownWordsWithSimpleWords.includes(word.word.toLowerCase()) && !word.isSymbol) {
|
||||
statisticsStore.inputWordNumber++
|
||||
}
|
||||
}
|
||||
|
||||
function changeArticle(val: ArticleItem) {
|
||||
let rIndex = articleData.list.findIndex(v => v.id === val.item.id)
|
||||
if (rIndex > -1) {
|
||||
store.sbook.lastLearnIndex = rIndex
|
||||
getCurrentPractice()
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
isArticleCollect,
|
||||
toggleArticleCollect
|
||||
} = useArticleOptions()
|
||||
|
||||
function play() {
|
||||
typingArticleRef?.play()
|
||||
}
|
||||
|
||||
function show() {
|
||||
typingArticleRef?.showSentence()
|
||||
}
|
||||
|
||||
|
||||
function onKeyUp() {
|
||||
typingArticleRef.hideSentence()
|
||||
}
|
||||
|
||||
async function onKeyDown(e: KeyboardEvent) {
|
||||
// console.log('e', e)
|
||||
switch (e.key) {
|
||||
case 'Backspace':
|
||||
typingArticleRef.del()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
useOnKeyboardEventListener(onKeyDown, onKeyUp)
|
||||
|
||||
|
||||
onMounted(init)
|
||||
|
||||
useEvents([
|
||||
[EventKey.write, write],
|
||||
[EventKey.repeatStudy, repeat],
|
||||
[EventKey.continueStudy, next],
|
||||
|
||||
[ShortcutKey.PreviousChapter, prev],
|
||||
[ShortcutKey.RepeatChapter, repeat],
|
||||
[ShortcutKey.DictationChapter, write],
|
||||
[ShortcutKey.ToggleShowTranslate, toggleShowTranslate],
|
||||
[ShortcutKey.ToggleDictation, toggleDictation],
|
||||
[ShortcutKey.ToggleTheme, toggleTheme],
|
||||
[ShortcutKey.ToggleConciseMode, toggleConciseMode],
|
||||
[ShortcutKey.TogglePanel, togglePanel],
|
||||
[ShortcutKey.NextChapter, next],
|
||||
[ShortcutKey.PlayWordPronunciation, play],
|
||||
[ShortcutKey.ShowWord, show],
|
||||
[ShortcutKey.Next, skip],
|
||||
[ShortcutKey.ToggleCollect, collect],
|
||||
[ShortcutKey.EditArticle, shortcutKeyEdit],
|
||||
])
|
||||
|
||||
let speedMinute = $ref(0)
|
||||
let timer = $ref(0)
|
||||
onMounted(() => {
|
||||
emitter.on(EventKey.write, write)
|
||||
emitter.on(EventKey.repeatStudy, repeat)
|
||||
emitter.on(EventKey.jumpSpecifiedChapter, jumpSpecifiedChapter)
|
||||
|
||||
emitter.on(ShortcutKey.PreviousChapter, prev)
|
||||
emitter.on(ShortcutKey.RepeatChapter, repeat)
|
||||
emitter.on(ShortcutKey.DictationChapter, write)
|
||||
emitter.on(ShortcutKey.ToggleShowTranslate, toggleShowTranslate)
|
||||
emitter.on(ShortcutKey.ToggleDictation, toggleDictation)
|
||||
emitter.on(ShortcutKey.ToggleTheme, toggleTheme)
|
||||
emitter.on(ShortcutKey.ToggleConciseMode, toggleConciseMode)
|
||||
emitter.on(ShortcutKey.TogglePanel, togglePanel)
|
||||
timer = setInterval(() => {
|
||||
speedMinute = Math.floor((Date.now() - statisticsStore.startDate) / 1000 / 60)
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
emitter.off(EventKey.write, write)
|
||||
emitter.off(EventKey.repeatStudy, repeat)
|
||||
emitter.off(EventKey.jumpSpecifiedChapter, jumpSpecifiedChapter)
|
||||
|
||||
emitter.off(ShortcutKey.PreviousChapter, prev)
|
||||
emitter.off(ShortcutKey.RepeatChapter, repeat)
|
||||
emitter.off(ShortcutKey.DictationChapter, write)
|
||||
emitter.off(ShortcutKey.ToggleShowTranslate, toggleShowTranslate)
|
||||
emitter.off(ShortcutKey.ToggleDictation, toggleDictation)
|
||||
emitter.off(ShortcutKey.ToggleTheme, toggleTheme)
|
||||
emitter.off(ShortcutKey.ToggleConciseMode, toggleConciseMode)
|
||||
emitter.off(ShortcutKey.TogglePanel, togglePanel)
|
||||
timer && clearInterval(timer)
|
||||
})
|
||||
|
||||
useStartKeyboardEventListener()
|
||||
let audioRef = $ref<HTMLAudioElement>()
|
||||
const {playSentenceAudio} = usePlaySentenceAudio()
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<PracticeArticle ref="practiceRef"/>
|
||||
<Statistics/>
|
||||
<div class="practice-wrapper">
|
||||
<div class="practice-article">
|
||||
<TypingArticle
|
||||
ref="typingArticleRef"
|
||||
@edit="edit"
|
||||
@wrong="wrong"
|
||||
@next="next"
|
||||
@nextWord="nextWord"
|
||||
@play="e => playSentenceAudio(e,audioRef,articleData.article)"
|
||||
:article="articleData.article"
|
||||
/>
|
||||
|
||||
<div class="panel-wrapper">
|
||||
<Panel>
|
||||
<template v-slot:title>
|
||||
<span>{{
|
||||
store.sbook.name
|
||||
}} ({{ store.sbook.lastLearnIndex + 1 }} / {{ articleData.list.length }})</span>
|
||||
</template>
|
||||
<div class="panel-page-item pl-4">
|
||||
<ArticleList
|
||||
:isActive="true"
|
||||
:static="false"
|
||||
:show-translate="settingStore.translate"
|
||||
@click="changeArticle"
|
||||
:active-id="articleData.article.id"
|
||||
:list="articleData.list ">
|
||||
<template v-slot:suffix="{item,index}">
|
||||
<BaseIcon
|
||||
v-if="!isArticleCollect(item)"
|
||||
class="collect"
|
||||
@click="toggleArticleCollect(item)"
|
||||
title="收藏" icon="ph:star"/>
|
||||
<BaseIcon
|
||||
v-else
|
||||
class="fill"
|
||||
@click="toggleArticleCollect(item)"
|
||||
title="取消收藏" icon="ph:star-fill"/>
|
||||
</template>
|
||||
</ArticleList>
|
||||
</div>
|
||||
</Panel>
|
||||
</div>
|
||||
|
||||
<EditSingleArticleModal
|
||||
v-model="showEditArticle"
|
||||
:article="editArticle"
|
||||
@save="saveArticle"
|
||||
/>
|
||||
</div>
|
||||
<div class="footer" :class="!settingStore.showToolbar && 'hide'">
|
||||
<Tooltip :title="settingStore.showToolbar?'收起':'展开'">
|
||||
<Icon icon="icon-park-outline:down"
|
||||
@click="settingStore.showToolbar = !settingStore.showToolbar"
|
||||
class="arrow"
|
||||
:class="!settingStore.showToolbar && 'down'"
|
||||
width="24"
|
||||
color="#999"/>
|
||||
</Tooltip>
|
||||
|
||||
<div class="bottom">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="stat">
|
||||
<div class="row">
|
||||
<div class="num">{{ speedMinute }}分钟</div>
|
||||
<div class="line"></div>
|
||||
<div class="name">时间</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="num">{{ statisticsStore.total }}</div>
|
||||
<div class="line"></div>
|
||||
<div class="name">单词总数</div>
|
||||
</div>
|
||||
</div>
|
||||
<audio ref="audioRef" v-if="articleData.article.audioSrc" :src="articleData.article.audioSrc"
|
||||
controls></audio>
|
||||
<div class="flex flex-col items-center justify-center gap-1">
|
||||
<div class="flex gap-2 center">
|
||||
<BaseIcon
|
||||
:title="`下一句(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
|
||||
icon="icon-park-outline:go-ahead"
|
||||
@click="skip"/>
|
||||
<BaseIcon
|
||||
:title="`重听(${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
|
||||
icon="fluent:replay-16-filled"
|
||||
@click="play"/>
|
||||
|
||||
<BaseIcon
|
||||
@click="settingStore.dictation = !settingStore.dictation"
|
||||
:title="`开关默写模式(${settingStore.shortcutKeyMap[ShortcutKey.ToggleDictation]})`"
|
||||
:icon="['majesticons:eye-off-line','mdi:eye-outline'][settingStore.dictation?0:1]"/>
|
||||
|
||||
<BaseIcon :icon="['mdi:translate','mdi:translate-off'][settingStore.translate?0:1]"
|
||||
:title="`开关释义显示(${settingStore.shortcutKeyMap[ShortcutKey.ToggleShowTranslate]})`"
|
||||
@click="settingStore.translate = !settingStore.translate"/>
|
||||
|
||||
<!-- <BaseIcon-->
|
||||
<!-- :title="`编辑(${settingStore.shortcutKeyMap[ShortcutKey.EditArticle]})`"-->
|
||||
<!-- icon="tabler:edit"-->
|
||||
<!-- @click="emitter.emit(ShortcutKey.EditArticle)"-->
|
||||
<!-- />-->
|
||||
<BaseIcon
|
||||
@click="settingStore.showPanel = !settingStore.showPanel"
|
||||
:title="`面板(${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`"
|
||||
icon="tdesign:menu-unfold"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.practice-wrapper {
|
||||
font-size: 0.9rem;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.swiper-wrapper {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
.swiper-list {
|
||||
transition: transform .3s;
|
||||
height: 200%;
|
||||
|
||||
.swiper-item {
|
||||
height: 50%;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.step1 {
|
||||
transform: translate3d(0, -50%, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.practice-article {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
width: var(--article-width);
|
||||
}
|
||||
|
||||
.typing-word-wrapper {
|
||||
width: var(--toolbar-width);
|
||||
}
|
||||
|
||||
.panel-wrapper {
|
||||
position: absolute;
|
||||
left: var(--article-panel-margin-left);
|
||||
//left: 0;
|
||||
top: .8rem;
|
||||
z-index: 1;
|
||||
height: calc(100% - 1.5rem);
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: var(--article-toolbar-width);
|
||||
margin-bottom: .8rem;
|
||||
transition: all var(--anim-time);
|
||||
position: relative;
|
||||
margin-top: 1rem;
|
||||
|
||||
&.hide {
|
||||
margin-bottom: -6rem;
|
||||
margin-top: 3rem;
|
||||
|
||||
}
|
||||
|
||||
.bottom {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border-radius: .6rem;
|
||||
background: var(--color-second);
|
||||
padding: .5rem var(--space);
|
||||
z-index: 2;
|
||||
border: 1px solid var(--color-item-border);
|
||||
box-shadow: var(--shadow);
|
||||
|
||||
.stat {
|
||||
margin-top: .5rem;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
gap: var(--stat-gap);
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: .3rem;
|
||||
width: 5rem;
|
||||
color: gray;
|
||||
|
||||
.line {
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background: var(--color-sub-gray);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.arrow {
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: 50%;
|
||||
cursor: pointer;
|
||||
transition: all .5s;
|
||||
transform: rotate(0);
|
||||
padding: .5rem;
|
||||
|
||||
&.down {
|
||||
top: -90%;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -21,7 +21,6 @@ interface IProps {
|
||||
sentenceIndex?: number,
|
||||
wordIndex?: number,
|
||||
stringIndex?: number,
|
||||
active: boolean,
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
@@ -30,7 +29,6 @@ const props = withDefaults(defineProps<IProps>(), {
|
||||
sentenceIndex: 0,
|
||||
wordIndex: 0,
|
||||
stringIndex: 0,
|
||||
active: true,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -38,7 +36,8 @@ const emit = defineEmits<{
|
||||
wrong: [val: Word],
|
||||
play: [val: Sentence],
|
||||
nextWord: [val: ArticleWord],
|
||||
over: [],
|
||||
complete: [],
|
||||
next: [],
|
||||
edit: [val: Article]
|
||||
}>()
|
||||
|
||||
@@ -175,7 +174,7 @@ function nextSentence() {
|
||||
if (!props.article.sections[sectionIndex]) {
|
||||
console.log('打完了')
|
||||
isEnd = true
|
||||
emit('over')
|
||||
emit('complete')
|
||||
} else {
|
||||
emit('play', props.article.sections[sectionIndex][0])
|
||||
}
|
||||
@@ -186,7 +185,6 @@ function nextSentence() {
|
||||
}
|
||||
|
||||
function onTyping(e: KeyboardEvent) {
|
||||
if (!props.active) return
|
||||
if (!props.article.sections.length) return
|
||||
// console.log('keyDown', e.key, e.code, e.keyCode)
|
||||
wrong = ''
|
||||
@@ -458,7 +456,7 @@ let showQuestions = $ref(false)
|
||||
<div class="options flex justify-center" v-if="isEnd">
|
||||
<BaseButton
|
||||
v-if="store.currentBook.lastLearnIndex < store.currentBook.articles.length - 1"
|
||||
@click="emitter.emit(EventKey.continueStudy)">下一章
|
||||
@click="emit('next')">下一章
|
||||
</BaseButton>
|
||||
</div>
|
||||
|
||||
@@ -1,520 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import TypingArticle from "./TypingArticle.vue";
|
||||
import {Article, ArticleItem, ArticleWord, DisplayStatistics, ShortcutKey, Word} from "@/types/types.ts";
|
||||
import {cloneDeep} from "@/utils";
|
||||
import Panel from "../../components/Panel.vue";
|
||||
import {onMounted, onUnmounted} from "vue";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import EditSingleArticleModal from "@/pages/pc/article/components/EditSingleArticleModal.vue";
|
||||
import {usePracticeStore} from "@/stores/practice.ts";
|
||||
import {emitter, EventKey, useEvents} from "@/utils/eventBus.ts";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import {useArticleOptions} from "@/hooks/dict.ts";
|
||||
import ArticleList from "@/pages/pc/components/list/ArticleList.vue";
|
||||
import {useOnKeyboardEventListener} from "@/hooks/event.ts";
|
||||
import {genArticleSectionData, usePlaySentenceAudio} from "@/hooks/article.ts";
|
||||
import {ElProgress} from 'element-plus';
|
||||
import router from "@/router.ts";
|
||||
import {getDefaultArticle} from "@/types/func.ts";
|
||||
|
||||
const store = useBaseStore()
|
||||
const statisticsStore = usePracticeStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
|
||||
let tabIndex = $ref(0)
|
||||
let wordData = $ref({
|
||||
words: [],
|
||||
index: -1
|
||||
})
|
||||
let articleData = $ref({
|
||||
articles: [],
|
||||
article: getDefaultArticle(),
|
||||
sectionIndex: 0,
|
||||
sentenceIndex: 0,
|
||||
wordIndex: 0,
|
||||
stringIndex: 0,
|
||||
})
|
||||
let showEditArticle = $ref(false)
|
||||
let typingArticleRef = $ref<any>()
|
||||
let editArticle = $ref<Article>(getDefaultArticle())
|
||||
let articleIsActive = $computed(() => tabIndex === 0)
|
||||
|
||||
function next() {
|
||||
if (!articleIsActive) return
|
||||
if (store.currentBook.lastLearnIndex >= articleData.articles.length - 1) {
|
||||
store.currentBook.lastLearnIndex = 0
|
||||
} else store.currentBook.lastLearnIndex++
|
||||
|
||||
emitter.emit(EventKey.resetWord)
|
||||
getCurrentPractice()
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (!store.currentBook?.articles?.length) {
|
||||
router.push('/article')
|
||||
return
|
||||
}
|
||||
articleData.articles = cloneDeep(store.currentBook.articles)
|
||||
getCurrentPractice()
|
||||
console.log('inin', articleData.article)
|
||||
}
|
||||
|
||||
function setArticle(val: Article) {
|
||||
let tempVal = cloneDeep(val)
|
||||
articleData.articles[store.currentBook.lastLearnIndex] = tempVal
|
||||
articleData.article = tempVal
|
||||
statisticsStore.inputWordNumber = 0
|
||||
statisticsStore.wrong = 0
|
||||
statisticsStore.total = 0
|
||||
statisticsStore.startDate = Date.now()
|
||||
articleData.article.sections.map((v, i) => {
|
||||
v.map((w, j) => {
|
||||
w.words.map(s => {
|
||||
if (!store.knownWordsWithSimpleWords.includes(s.word.toLowerCase()) && !s.isSymbol) {
|
||||
statisticsStore.total++
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getCurrentPractice() {
|
||||
// console.log('store.currentBook',store.currentBook)
|
||||
// return
|
||||
tabIndex = 0
|
||||
articleData.article = getDefaultArticle()
|
||||
|
||||
let currentArticle = articleData.articles[store.currentBook.lastLearnIndex]
|
||||
let tempArticle = getDefaultArticle(currentArticle)
|
||||
// console.log('article', tempArticle)
|
||||
if (tempArticle.sections.length) {
|
||||
setArticle(tempArticle)
|
||||
} else {
|
||||
genArticleSectionData(tempArticle)
|
||||
setArticle(tempArticle)
|
||||
}
|
||||
}
|
||||
|
||||
function saveArticle(val: Article) {
|
||||
console.log('saveArticle', val, JSON.stringify(val.lrcPosition))
|
||||
console.log('saveArticle', val.textTranslate)
|
||||
showEditArticle = false
|
||||
let rIndex = store.currentBook.articles.findIndex(v => v.id === val.id)
|
||||
if (rIndex > -1) {
|
||||
store.currentBook.articles[rIndex] = cloneDeep(val)
|
||||
}
|
||||
setArticle(val)
|
||||
}
|
||||
|
||||
function edit(val: Article = articleData.article) {
|
||||
if (!articleIsActive) return
|
||||
// tabIndex = 1
|
||||
// wordData.words = [
|
||||
// {
|
||||
// ...cloneDeep(DefaultWord),
|
||||
// word: 'test'
|
||||
// }
|
||||
// ]
|
||||
// wordData.index = 0
|
||||
// return
|
||||
editArticle = val
|
||||
showEditArticle = true
|
||||
}
|
||||
|
||||
function wrong(word: Word) {
|
||||
let lowerName = word.word.toLowerCase();
|
||||
if (!store.wrong.words.find((v: Word) => v.word.toLowerCase() === lowerName)) {
|
||||
store.wrong.words.push(word)
|
||||
}
|
||||
if (!store.knownWordsWithSimpleWords.includes(lowerName)) {
|
||||
}
|
||||
}
|
||||
|
||||
function over() {
|
||||
if (statisticsStore.wrong === 0) {
|
||||
// if (false) {
|
||||
console.log('这章节完了')
|
||||
let now = Date.now()
|
||||
let stat: DisplayStatistics = {
|
||||
startDate: statisticsStore.startDate,
|
||||
endDate: now,
|
||||
spend: now - statisticsStore.startDate,
|
||||
total: statisticsStore.total,
|
||||
correctRate: -1,
|
||||
wrong: statisticsStore.wrong,
|
||||
}
|
||||
stat.correctRate = 100 - Math.trunc(((stat.wrong) / (stat.total)) * 100)
|
||||
} else {
|
||||
tabIndex = 1
|
||||
wordData.index = 0
|
||||
}
|
||||
}
|
||||
|
||||
function nextWord(word: ArticleWord) {
|
||||
if (!store.knownWordsWithSimpleWords.includes(word.word.toLowerCase()) && !word.isSymbol) {
|
||||
statisticsStore.inputWordNumber++
|
||||
}
|
||||
}
|
||||
|
||||
function handleChangeChapterIndex(val: ArticleItem) {
|
||||
let rIndex = articleData.articles.findIndex(v => v.id === val.item.id)
|
||||
if (rIndex > -1) {
|
||||
store.currentBook.lastLearnIndex = rIndex
|
||||
getCurrentPractice()
|
||||
}
|
||||
}
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
const {
|
||||
isArticleCollect,
|
||||
toggleArticleCollect
|
||||
} = useArticleOptions()
|
||||
|
||||
function sort(list: Word[]) {
|
||||
wordData.words = list
|
||||
wordData.index = 0
|
||||
}
|
||||
|
||||
function play() {
|
||||
if (!articleIsActive) return
|
||||
typingArticleRef?.play()
|
||||
}
|
||||
|
||||
function show() {
|
||||
if (!articleIsActive) return
|
||||
typingArticleRef?.showSentence()
|
||||
}
|
||||
|
||||
|
||||
function onKeyUp(e: KeyboardEvent) {
|
||||
typingArticleRef.hideSentence()
|
||||
}
|
||||
|
||||
async function onKeyDown(e: KeyboardEvent) {
|
||||
// console.log('e', e)
|
||||
switch (e.key) {
|
||||
case 'Backspace':
|
||||
typingArticleRef.del()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
useOnKeyboardEventListener(onKeyDown, onKeyUp)
|
||||
|
||||
function skip() {
|
||||
if (!articleIsActive) return
|
||||
typingArticleRef?.nextSentence()
|
||||
}
|
||||
|
||||
function collect(e: KeyboardEvent) {
|
||||
if (!articleIsActive) return
|
||||
toggleArticleCollect(articleData.article)
|
||||
}
|
||||
|
||||
//包装一遍,因为快捷建的默认参数是Event
|
||||
function shortcutKeyEdit() {
|
||||
edit()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
|
||||
useEvents([
|
||||
[EventKey.changeDict, init],
|
||||
[EventKey.continueStudy, next],
|
||||
|
||||
[ShortcutKey.NextChapter, next],
|
||||
[ShortcutKey.PlayWordPronunciation, play],
|
||||
[ShortcutKey.ShowWord, show],
|
||||
[ShortcutKey.Next, skip],
|
||||
[ShortcutKey.ToggleCollect, collect],
|
||||
[ShortcutKey.EditArticle, shortcutKeyEdit],
|
||||
])
|
||||
|
||||
defineExpose({getCurrentPractice})
|
||||
|
||||
const emit = defineEmits<{
|
||||
ignore: [],
|
||||
wrong: [val: Word],
|
||||
nextWord: [val: ArticleWord],
|
||||
over: [],
|
||||
edit: [val: Article]
|
||||
}>()
|
||||
|
||||
function format(val: number, suffix: string = '', check: number = -1) {
|
||||
return val === check ? '-' : (val + suffix)
|
||||
}
|
||||
|
||||
const progress = $computed(() => {
|
||||
if (!statisticsStore.total) return 0
|
||||
if (statisticsStore.index > statisticsStore.total) return 100
|
||||
return ((statisticsStore.index / statisticsStore.total) * 100)
|
||||
})
|
||||
|
||||
let speedMinute = $ref(0)
|
||||
let timer = $ref(0)
|
||||
onMounted(() => {
|
||||
timer = setInterval(() => {
|
||||
speedMinute = Math.floor((Date.now() - statisticsStore.startDate) / 1000 / 60)
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
timer && clearInterval(timer)
|
||||
})
|
||||
|
||||
let audioRef = $ref<HTMLAudioElement>()
|
||||
const {playSentenceAudio} = usePlaySentenceAudio()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="practice-wrapper">
|
||||
<div class="practice-article">
|
||||
<TypingArticle
|
||||
ref="typingArticleRef"
|
||||
:active="tabIndex === 0"
|
||||
@edit="edit"
|
||||
@wrong="wrong"
|
||||
@over="skip"
|
||||
@nextWord="nextWord"
|
||||
@play="e => playSentenceAudio(e,audioRef,articleData.article)"
|
||||
:article="articleData.article"
|
||||
/>
|
||||
|
||||
<div class="panel-wrapper">
|
||||
<Panel>
|
||||
<template v-slot:title>
|
||||
<span>{{
|
||||
store.currentBook.name
|
||||
}} ({{ store.currentBook.lastLearnIndex + 1 }} / {{ articleData.articles.length }})</span>
|
||||
</template>
|
||||
<div class="panel-page-item pl-4">
|
||||
<ArticleList
|
||||
:isActive="true"
|
||||
:static="false"
|
||||
:show-translate="settingStore.translate"
|
||||
@click="handleChangeChapterIndex"
|
||||
:active-id="articleData.article.id"
|
||||
:list="articleData.articles ">
|
||||
<template v-slot:suffix="{item,index}">
|
||||
<BaseIcon
|
||||
v-if="!isArticleCollect(item)"
|
||||
class="collect"
|
||||
@click="toggleArticleCollect(item)"
|
||||
title="收藏" icon="ph:star"/>
|
||||
<BaseIcon
|
||||
v-else
|
||||
class="fill"
|
||||
@click="toggleArticleCollect(item)"
|
||||
title="取消收藏" icon="ph:star-fill"/>
|
||||
</template>
|
||||
</ArticleList>
|
||||
</div>
|
||||
</Panel>
|
||||
</div>
|
||||
|
||||
<EditSingleArticleModal
|
||||
v-model="showEditArticle"
|
||||
:article="editArticle"
|
||||
@save="saveArticle"
|
||||
/>
|
||||
</div>
|
||||
<div class="footer" :class="!settingStore.showToolbar && 'hide'">
|
||||
<div class="bottom">
|
||||
<ElProgress
|
||||
class="flex-1"
|
||||
:percentage="progress"
|
||||
:stroke-width="8"
|
||||
:show-text="false"/>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="stat">
|
||||
<div class="row">
|
||||
<div class="num">{{ speedMinute }}分钟</div>
|
||||
<div class="line"></div>
|
||||
<div class="name">时间</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="num">{{ statisticsStore.total }}</div>
|
||||
<div class="line"></div>
|
||||
<div class="name">单词总数</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="num">{{ format(statisticsStore.inputWordNumber, '', 0) }}</div>
|
||||
<div class="line"></div>
|
||||
<div class="name">输入数</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="num">{{ format(statisticsStore.wrong, '', 0) }}</div>
|
||||
<div class="line"></div>
|
||||
<div class="name">错误数</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center gap-1">
|
||||
<audio ref="audioRef" v-if="articleData.article.audioSrc" :src="articleData.article.audioSrc"
|
||||
controls></audio>
|
||||
<div class="flex gap-2 center">
|
||||
<BaseIcon
|
||||
:title="`下一句(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
|
||||
icon="icon-park-outline:go-ahead"
|
||||
@click="emit('over')"/>
|
||||
<BaseIcon
|
||||
:title="`重听(${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
|
||||
icon="fluent:replay-16-filled"
|
||||
@click="play"/>
|
||||
|
||||
<BaseIcon
|
||||
@click="settingStore.dictation = !settingStore.dictation"
|
||||
:title="`开关默写模式(${settingStore.shortcutKeyMap[ShortcutKey.ToggleDictation]})`"
|
||||
:icon="['majesticons:eye-off-line','mdi:eye-outline'][settingStore.dictation?0:1]"/>
|
||||
|
||||
<BaseIcon :icon="['mdi:translate','mdi:translate-off'][settingStore.translate?0:1]"
|
||||
:title="`开关释义显示(${settingStore.shortcutKeyMap[ShortcutKey.ToggleShowTranslate]})`"
|
||||
@click="settingStore.translate = !settingStore.translate"/>
|
||||
|
||||
<!-- <BaseIcon-->
|
||||
<!-- :title="`编辑(${settingStore.shortcutKeyMap[ShortcutKey.EditArticle]})`"-->
|
||||
<!-- icon="tabler:edit"-->
|
||||
<!-- @click="emitter.emit(ShortcutKey.EditArticle)"-->
|
||||
<!-- />-->
|
||||
<BaseIcon
|
||||
@click="settingStore.showPanel = !settingStore.showPanel"
|
||||
:title="`面板(${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`"
|
||||
icon="tdesign:menu-unfold"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress">
|
||||
<ElProgress :percentage="progress"
|
||||
:stroke-width="8"
|
||||
:show-text="false"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.practice-wrapper {
|
||||
font-size: 0.9rem;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.swiper-wrapper {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
.swiper-list {
|
||||
transition: transform .3s;
|
||||
height: 200%;
|
||||
|
||||
.swiper-item {
|
||||
height: 50%;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.step1 {
|
||||
transform: translate3d(0, -50%, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.practice-article {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
width: var(--article-width);
|
||||
}
|
||||
|
||||
.typing-word-wrapper {
|
||||
width: var(--toolbar-width);
|
||||
}
|
||||
|
||||
.panel-wrapper {
|
||||
position: absolute;
|
||||
left: var(--article-panel-margin-left);
|
||||
//left: 0;
|
||||
top: .8rem;
|
||||
z-index: 1;
|
||||
height: calc(100% - 1.5rem);
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: var(--article-width);
|
||||
margin-bottom: .8rem;
|
||||
transition: all var(--anim-time);
|
||||
position: relative;
|
||||
margin-top: 1rem;
|
||||
|
||||
&.hide {
|
||||
margin-bottom: -6rem;
|
||||
margin-top: 3rem;
|
||||
|
||||
.progress {
|
||||
bottom: calc(100% + 1.8rem);
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border-radius: .6rem;
|
||||
background: var(--color-second);
|
||||
padding: .5rem var(--space);
|
||||
z-index: 2;
|
||||
border: 1px solid var(--color-item-border);
|
||||
box-shadow: var(--shadow);
|
||||
|
||||
.stat {
|
||||
margin-top: .5rem;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
gap: var(--stat-gap);
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: .3rem;
|
||||
width: 5rem;
|
||||
color: gray;
|
||||
|
||||
.line {
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background: var(--color-sub-gray);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.progress {
|
||||
width: 100%;
|
||||
transition: all .3s;
|
||||
padding: 0 .6rem;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
:deep(.ElProgress-bar__inner) {
|
||||
background: var(--color-scrollbar);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -18,7 +18,7 @@ const emit = defineEmits<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid grid-cols-6 gap-4 ">
|
||||
<div class="flex gap-4 flex-wrap">
|
||||
<Book v-for="(dict,index) in list"
|
||||
:is-add="false"
|
||||
@click="emit('selectDict',{dict,index})"
|
||||
|
||||
@@ -18,7 +18,7 @@ import Panel from "@/pages/pc/components/Panel.vue";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import Tooltip from "@/pages/pc/components/Tooltip.vue";
|
||||
import WordList from "@/pages/pc/components/list/WordList.vue";
|
||||
import Type from "@/pages/pc/word/components/Type.vue";
|
||||
import TypeWord from "@/pages/pc/word/components/TypeWord.vue";
|
||||
import Empty from "@/components/Empty.vue";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {usePracticeStore} from "@/stores/practice.ts";
|
||||
@@ -378,7 +378,7 @@ useEvents([
|
||||
<Icon class="arrow" icon="bi:arrow-right" width="22"/>
|
||||
</div>
|
||||
</div>
|
||||
<Type
|
||||
<TypeWord
|
||||
ref="typingRef"
|
||||
:word="word"
|
||||
@wrong="onTypeWrong"
|
||||
|
||||
@@ -203,7 +203,7 @@ const progressTextRight = $computed(() => {
|
||||
<div class="color-blue cursor-pointer" @click="nav('dict-detail', { isAdd: true })">创建个人词典</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-6 gap-4 mt-4">
|
||||
<div class="flex gap-4 flex-wrap mt-4">
|
||||
<Book :is-add="false" quantifier="个词" :item="item" :checked="selectIds.includes(item.id)"
|
||||
@check="() => toggleSelect(item)" :show-checkbox="isMultiple && j >= 3"
|
||||
v-for="(item, j) in store.word.bookList" @click="goDictDetail(item)"/>
|
||||
|
||||
@@ -29,18 +29,6 @@ function format(val: number, suffix: string = '', check: number = -1) {
|
||||
return val === check ? '-' : (val + suffix)
|
||||
}
|
||||
|
||||
let speedMinute = $ref(0)
|
||||
let timer = $ref(0)
|
||||
onMounted(() => {
|
||||
timer = setInterval(() => {
|
||||
speedMinute = Math.floor((Date.now() - statisticsStore.startDate) / 1000 / 60)
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
timer && clearInterval(timer)
|
||||
})
|
||||
|
||||
let studyData = inject<StudyData>('studyData')
|
||||
|
||||
const status = $computed(() => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {Article, ArticleWord, Dict, DictType, Word} from "@/types/types.ts";
|
||||
import {shallowReactive} from "vue";
|
||||
import {cloneDeep} from "@/utils";
|
||||
|
||||
export function getDefaultWord(val: Partial<Word> = {}): Word {
|
||||
return {
|
||||
@@ -42,7 +43,7 @@ export function getDefaultArticle(val: Partial<Article> = {}): Article {
|
||||
audioSrc: '',
|
||||
lrcPosition: [],
|
||||
questions: [],
|
||||
...val
|
||||
...cloneDeep(val)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +65,7 @@ export function getDefaultDict(val: Partial<Dict> = {}): Dict {
|
||||
complete: false,
|
||||
...val,
|
||||
words: shallowReactive(val.words ?? []),
|
||||
articles: shallowReactive(val.articles ?? []),
|
||||
list: shallowReactive(val.articles ?? []),
|
||||
statistics: shallowReactive(val.statistics ?? [])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ export const EventKey = {
|
||||
write: 'write',
|
||||
editDict: 'editDict',
|
||||
openMyDictDialog: 'openMyDictDialog',
|
||||
jumpSpecifiedChapter: 'jumpSpecifiedChapter',
|
||||
}
|
||||
|
||||
export function useEvent(key: string, func: any) {
|
||||
|
||||
Reference in New Issue
Block a user