Allow yourself to add favorite words and articles for practice

This commit is contained in:
zyronon
2023-12-04 16:02:32 +08:00
parent d0e74ebaff
commit 747a0dcea4
11 changed files with 241 additions and 237 deletions

View File

@@ -30,7 +30,7 @@ const DefaultDictForm = {
tags: [],
translateLanguage: 'zh-CN',
language: 'en',
type: DictType.customWord
type: DictType.word
}
let dictForm: any = $ref(cloneDeep(DefaultDictForm))
const dictFormRef = $ref<FormInstance>()
@@ -156,9 +156,9 @@ onMounted(() => {
</el-select>
</el-form-item>
<el-form-item label="类型">
<el-select v-model="dictForm.type" placeholder="请选择选项">
<el-option label="单词" :value="DictType.customWord"/>
<el-option label="文章" :value="DictType.customArticle"/>
<el-select v-model="dictForm.type" placeholder="请选择选项" :disabled="dictForm.id">
<el-option label="单词" :value="DictType.word"/>
<el-option label="文章" :value="DictType.article"/>
</el-select>
</el-form-item>
<div class="flex-center">

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts"
import {$ref} from "vue/macros"
import {computed, onMounted, provide, watch} from "vue"
import {$computed, $ref} from "vue/macros"
import {computed, onMounted, onUnmounted, provide, watch} from "vue"
import {Dict, DictType, ShortcutKey} from "@/types.ts"
import PopConfirm from "@/components/PopConfirm.vue"
import BaseButton from "@/components/BaseButton.vue";
@@ -20,6 +20,7 @@ import {useRuntimeStore} from "@/stores/runtime.ts";
import {cloneDeep} from "lodash-es";
import WordList from "@/components/list/WordList.vue";
import ArticleList from "@/components/list/ArticleList.vue";
import Slide from "@/components/Slide.vue";
const router = useRouter()
const store = useBaseStore()
@@ -36,8 +37,8 @@ watch(() => settingStore.showPanel, n => {
let practiceType = $ref(DictType.word)
function changeIndex(i: number, dict: Dict) {
store.changeDict(dict, dict.chapterIndex, i, practiceType)
function changeIndex(dict: Dict) {
store.changeDict(dict, dict.chapterIndex, dict.wordIndex, practiceType)
}
onMounted(() => {
@@ -46,6 +47,10 @@ onMounted(() => {
})
})
onUnmounted(() => {
emitter.off(EventKey.changeDict)
})
const {
delWrongWord,
delSimpleWord,
@@ -53,7 +58,6 @@ const {
} = useWordOptions()
const {
isArticleCollect,
toggleArticleCollect
} = useArticleOptions()
@@ -67,6 +71,19 @@ function addSimple() {
router.push({path: '/dict', query: {type: 'addWordOrArticle'}})
}
const showCollectToggleButton = $computed(() => {
if (store.currentDict.type === DictType.collect) {
if (store.current.practiceType !== practiceType) {
return (practiceType === DictType.word && store.collect.words.length) ||
(practiceType === DictType.article && store.collect.articles.length)
}
} else {
return (practiceType === DictType.word && store.collect.words.length) ||
(practiceType === DictType.article && store.collect.articles.length)
}
return false
})
</script>
<template>
<Transition name="fade">
@@ -87,140 +104,126 @@ function addSimple() {
<div class="tab" :class="tabIndex === 3 && 'active'" @click="tabIndex = 3">{{ store.wrong.name }}</div>
</div>
</header>
<div class="slide">
<div class="slide-list" :class="`step${tabIndex}`">
<div class="slide-item">
<slot :active="tabIndex === 0 && settingStore.showPanel"></slot>
</div>
<div class="slide-item">
<div class="panel-page-item">
<div class="list-header">
<div class="left">
<el-radio-group v-model="practiceType">
<el-radio-button border :label="DictType.word">单词</el-radio-button>
<el-radio-button border :label="DictType.article">文章</el-radio-button>
</el-radio-group>
<div class="dict-name" v-if="practiceType === DictType.word && store.collect.words.length">
{{ store.collect.words.length }}个单词
</div>
<div class="dict-name" v-if="practiceType === DictType.article && store.collect.articles.length">
{{ store.collect.articles.length }}篇文章
</div>
<Tooltip title="添加">
<IconWrapper>
<Icon icon="fluent:add-12-regular" @click="addCollect"/>
</IconWrapper>
</Tooltip>
<Slide :slide-count="4" :step="tabIndex">
<div class="slide-item">
<slot :active="tabIndex === 0 && settingStore.showPanel"></slot>
</div>
<div class="slide-item">
<div class="panel-page-item">
<div class="list-header">
<div class="left">
<el-radio-group v-model="practiceType">
<el-radio-button border :label="DictType.word">单词</el-radio-button>
<el-radio-button border :label="DictType.article">文章</el-radio-button>
</el-radio-group>
<div class="dict-name" v-if="practiceType === DictType.word && store.collect.words.length">
{{ store.collect.words.length }}个单词
</div>
<template v-if="store.currentDict.type !== DictType.collect &&
(
( practiceType === DictType.word && store.collect.words.length) ||
( practiceType === DictType.article && store.collect.articles.length)
)">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex(0,store.collect)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
<div class="dict-name" v-if="practiceType === DictType.article && store.collect.articles.length">
{{ store.collect.articles.length }}篇文章
</div>
<BaseIcon icon="fluent:add-12-regular" title="添加" @click="addCollect"/>
</div>
<template v-if="practiceType === DictType.word">
<WordList
v-if="store.collect.words.length"
class="word-list"
:list="store.collect.words">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click="toggleWordCollect(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList>
<Empty v-else/>
</template>
<template v-else>
<ArticleList
v-if="store.collect.articles.length"
v-model:list="store.collect.articles">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click="toggleArticleCollect(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</ArticleList>
<Empty v-else/>
<template v-if="showCollectToggleButton">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex( store.collect)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
</div>
</div>
<div class="slide-item">
<div class="panel-page-item">
<div class="list-header">
<div class="left">
<div class="dict-name">总词数:{{ store.simple.words.length }}</div>
<Tooltip title="添加">
<IconWrapper>
<Icon icon="fluent:add-12-regular" @click="addSimple"/>
</IconWrapper>
</Tooltip>
</div>
<template v-if="store.currentDict.type !== DictType.simple && store.simple.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex(0,store.simple)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
</div>
<template v-if="practiceType === DictType.word">
<WordList
v-if="store.simple.words.length"
v-if="store.collect.words.length"
class="word-list"
:list="store.simple.words">
:list="store.collect.words">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click="delSimpleWord(item)"
@click="toggleWordCollect(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList>
<Empty v-else/>
</div>
</div>
<div class="slide-item">
<div class="panel-page-item" v-if="store.wrong.words.length">
<div class="list-header">
<div class="dict-name">总词数:{{ store.wrong.words.length }}</div>
<template
v-if="store.currentDict.type !== DictType.wrong && store.wrong.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex(0,store.wrong)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
</div>
<WordList
class="word-list"
:list="store.wrong.words">
<template v-slot="{item,index}">
</template>
<template v-else>
<ArticleList
v-if="store.collect.articles.length"
:list="store.collect.articles">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click="delWrongWord(item)"
@click="toggleArticleCollect(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList>
</ArticleList>
<Empty v-else/>
</template>
</div>
</div>
<div class="slide-item">
<div class="panel-page-item">
<div class="list-header">
<div class="left">
<div class="dict-name">总词数{{ store.simple.words.length }}</div>
<BaseIcon icon="fluent:add-12-regular" title="添加" @click="addSimple"/>
</div>
<template v-if="store.currentDict.type !== DictType.simple && store.simple.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex( store.simple)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
</div>
<WordList
v-if="store.simple.words.length"
class="word-list"
:list="store.simple.words">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click="delSimpleWord(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList>
<Empty v-else/>
</div>
</div>
</div>
<div class="slide-item">
<div class="panel-page-item" v-if="store.wrong.words.length">
<div class="list-header">
<div class="dict-name">总词数{{ store.wrong.words.length }}</div>
<template
v-if="store.currentDict.type !== DictType.wrong && store.wrong.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex( store.wrong)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
</div>
<WordList
class="word-list"
:list="store.wrong.words">
<template v-slot="{item,index}">
<BaseIcon
class="del"
@click="delWrongWord(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList>
</div>
<Empty v-else/>
</div>
</Slide>
</div>
</Transition>
</template>
@@ -228,60 +231,34 @@ function addSimple() {
@import "@/assets/css/variable";
$header-height: 50rem;
.slide-item {
width: var(--panel-width);
height: 100%;
display: flex;
flex-direction: column;
.slide {
width: 100%;
flex: 1;
overflow: hidden;
.slide-list {
width: 400%;
height: 100%;
> header {
padding: 0 var(--space);
height: $header-height;
position: relative;
display: flex;
transition: all .5s;
.slide-item {
width: var(--panel-width);
height: 100%;
display: flex;
flex-direction: column;
> header {
padding: 0 var(--space);
height: $header-height;
position: relative;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10rem;
font-size: 16rem;
color: black;
}
.content {
flex: 1;
overflow: auto;
padding-bottom: var(--space);
}
footer {
padding-right: var(--space);
margin-bottom: 10rem;
align-items: center;
}
}
align-items: center;
justify-content: flex-end;
gap: 10rem;
font-size: 16rem;
color: black;
}
.step1 {
transform: translate3d(-25%, 0, 0);
.content {
flex: 1;
overflow: auto;
padding-bottom: var(--space);
}
.step2 {
transform: translate3d(-50%, 0, 0);
}
.step3 {
transform: translate3d(-75%, 0, 0);
footer {
padding-right: var(--space);
margin-bottom: 10rem;
align-items: center;
}
}
@@ -331,7 +308,6 @@ $header-height: 50rem;
}
}
}
}
}

View File

@@ -62,21 +62,6 @@ function repeat() {
practiceRef.getCurrentPractice()
}
function next() {
// console.log('next')
if (store.isArticle) {
if (store.currentDict.chapterIndex >= store.currentDict.articles.length - 1) {
store.currentDict.chapterIndex = 0
} else store.currentDict.chapterIndex++
} else {
if (store.currentDict.chapterIndex >= store.currentDict.chapterWords.length - 1) {
store.currentDict.chapterIndex = 0
} else store.currentDict.chapterIndex++
}
repeat()
}
function prev() {
// console.log('next')
if (store.currentDict.chapterIndex === 0) {
@@ -118,12 +103,10 @@ function jumpSpecifiedChapter(val: number) {
}
onMounted(() => {
emitter.on(EventKey.next, next)
emitter.on(EventKey.write, write)
emitter.on(EventKey.repeat, repeat)
emitter.on(EventKey.jumpSpecifiedChapter, jumpSpecifiedChapter)
emitter.on(ShortcutKey.NextChapter, next)
emitter.on(ShortcutKey.PreviousChapter, prev)
emitter.on(ShortcutKey.RepeatChapter, repeat)
emitter.on(ShortcutKey.DictationChapter, write)
@@ -137,12 +120,10 @@ onMounted(() => {
})
onUnmounted(() => {
emitter.off(EventKey.next, next)
emitter.off(EventKey.write, write)
emitter.off(EventKey.repeat, repeat)
emitter.off(EventKey.jumpSpecifiedChapter, jumpSpecifiedChapter)
emitter.off(ShortcutKey.NextChapter, next)
emitter.off(ShortcutKey.PreviousChapter, prev)
emitter.off(ShortcutKey.RepeatChapter, repeat)
emitter.off(ShortcutKey.DictationChapter, write)

View File

@@ -14,7 +14,7 @@ import {
import {cloneDeep} from "lodash-es";
import TypingWord from "@/pages/practice/practice-word/TypingWord.vue";
import Panel from "../Panel.vue";
import {onMounted, watch} from "vue";
import {onMounted, onUnmounted, watch} from "vue";
import {renewSectionTexts, renewSectionTranslates} from "@/hooks/translate.ts";
import {MessageBox} from "@/utils/MessageBox.tsx";
import {useBaseStore} from "@/stores/base.ts";
@@ -40,6 +40,7 @@ let wordData = $ref({
index: -1
})
let articleData = $ref({
articles: [],
article: cloneDeep(DefaultArticle),
sectionIndex: 0,
sentenceIndex: 0,
@@ -49,18 +50,38 @@ let articleData = $ref({
let showEditArticle = $ref(false)
let editArticle = $ref<Article>(cloneDeep(DefaultArticle))
watch([
// () => store.load,
() => store.currentDict.articles,
], n => {
function next() {
if (store.currentDict.chapterIndex >= articleData.articles.length - 1) {
store.currentDict.chapterIndex = 0
} else store.currentDict.chapterIndex++
emitter.emit(EventKey.resetWord)
getCurrentPractice()
}
onMounted(() => {
init()
emitter.on(EventKey.changeDict, init)
emitter.on(EventKey.next, next)
emitter.on(ShortcutKey.NextChapter, next)
})
onMounted(getCurrentPractice)
onUnmounted(() => {
emitter.off(EventKey.changeDict, init)
emitter.off(EventKey.next, next)
emitter.off(ShortcutKey.NextChapter, next)
})
function init() {
if (!store.currentDict.articles.length) return
articleData.articles = cloneDeep(store.currentDict.articles)
getCurrentPractice()
}
function setArticle(val: Article) {
store.currentDict.articles[store.currentDict.chapterIndex] = cloneDeep(val)
articleData.article = cloneDeep(val)
let tempVal = cloneDeep(val)
articleData.articles[store.currentDict.chapterIndex] = tempVal
articleData.article = tempVal
practiceStore.inputWordNumber = 0
practiceStore.wrongWordNumber = 0
practiceStore.repeatNumber = 0
@@ -81,11 +102,10 @@ function setArticle(val: Article) {
function getCurrentPractice() {
// console.log('store.currentDict',store.currentDict)
// return
if (!store.currentDict.articles.length) return
tabIndex = 0
articleData.article = cloneDeep(DefaultArticle)
let currentArticle = store.currentDict.articles[store.currentDict.chapterIndex]
let currentArticle = articleData.articles [store.currentDict.chapterIndex]
let tempArticle = {...DefaultArticle, ...currentArticle}
// console.log('article', tempArticle)
if (tempArticle.sections.length) {
@@ -153,7 +173,10 @@ function getCurrentPractice() {
function saveArticle(val: Article) {
console.log('saveArticle', val)
showEditArticle = false
// articleData.article = cloneDeep(store.currentDict.articles[store.currentDict.chapterIndex])
let rIndex = store.currentDict.articles.findIndex(v => v.id === val.id)
if (rIndex > -1) {
store.currentDict.articles[rIndex] = cloneDeep(val)
}
setArticle(val)
}
@@ -213,8 +236,8 @@ function nextWord(word: ArticleWord) {
}
}
function changePracticeArticle(val: ArticleItem) {
let rIndex = store.currentDict.articles.findIndex(v => v.id === val.item.id)
function handleChangeChapterIndex(val: ArticleItem) {
let rIndex = articleData.articles.findIndex(v => v.id === val.item.id)
if (rIndex > -1) {
store.currentDict.chapterIndex = rIndex
getCurrentPractice()
@@ -274,18 +297,18 @@ defineExpose({getCurrentPractice})
@click="emitter.emit(EventKey.openDictModal,'list')"
icon="carbon:change-catalog"/>
<div class="title">
{{ store.dictTitle }}
{{ store.currentDict.name }}
</div>
<Tooltip
:title="`下一章(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`"
v-if="store.currentDict.chapterIndex < store.currentDict.articles.length - 1">
v-if="store.currentDict.chapterIndex < articleData.articles .length - 1">
<IconWrapper>
<Icon @click="emitter.emit(EventKey.next)" icon="octicon:arrow-right-24"/>
</IconWrapper>
</Tooltip>
</div>
<div class="right">
{{ store.currentDict.articles.length }}篇文章
{{ articleData.articles.length }}篇文章
</div>
</div>
@@ -295,9 +318,9 @@ defineExpose({getCurrentPractice})
:show-border="true"
:show-translate="settingStore.translate"
@title="e => emitter.emit(EventKey.openArticleContentModal,e.item)"
@click="changePracticeArticle"
@click="handleChangeChapterIndex"
:active-id="articleData.article.id"
:list="store.currentDict.articles">
:list="articleData.articles ">
<template v-slot:suffix="{item,index}">
<BaseIcon
v-if="!isArticleCollect(item)"

View File

@@ -140,7 +140,7 @@ function next(isTyping: boolean = true) {
data.index++
isTyping && practiceStore.inputWordNumber++
console.log('这个词完了')
if ([DictType.customWord, DictType.word].includes(store.currentDict.type)
if ([DictType.word].includes(store.currentDict.type)
&& store.skipWordNames.includes(word.name.toLowerCase())) {
next()
}

View File

@@ -6,7 +6,7 @@ import {chunk, cloneDeep} from "lodash-es";
import {useBaseStore} from "@/stores/base.ts";
import {onMounted, onUnmounted, watch} from "vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {Word} from "@/types.ts";
import {ShortcutKey, Word} from "@/types.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {syncMyDictList} from "@/hooks/dict.ts";
@@ -20,12 +20,6 @@ let wordData = $ref({
index: -1
})
watch([
() => store.currentDict.words,
], n => {
getCurrentPractice()
})
function getCurrentPractice() {
if (store.chapter.length) {
wordData.words = store.chapter
@@ -37,6 +31,9 @@ function getCurrentPractice() {
if (res) w = Object.assign(w, res)
}
})
wordData.words = cloneDeep(store.chapter)
emitter.emit(EventKey.resetWord)
}
}
@@ -46,7 +43,26 @@ function sort(list: Word[]) {
syncMyDictList(store.currentDict)
}
onMounted(getCurrentPractice)
function next() {
if (store.currentDict.chapterIndex >= store.currentDict.chapterWords.length - 1) {
store.currentDict.chapterIndex = 0
} else store.currentDict.chapterIndex++
getCurrentPractice()
}
onMounted(() => {
getCurrentPractice()
emitter.on(EventKey.changeDict, getCurrentPractice)
emitter.on(EventKey.next, next)
emitter.on(ShortcutKey.NextChapter, next)
})
onUnmounted(() => {
emitter.off(EventKey.changeDict, getCurrentPractice)
emitter.off(EventKey.next, next)
emitter.off(ShortcutKey.NextChapter, next)
})
defineExpose({getCurrentPractice})