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

@@ -44,16 +44,31 @@ watch(settingStore.$state, (n) => {
//检测几个特定词典
watch(store.collect.originWords, (n) => {
store.collect.words = cloneDeep(n)
store.collect.chapterWords = [store.collect.words]
if (n.length === 0) {
store.collect.words = []
store.collect.chapterWords = []
} else {
store.collect.words = cloneDeep(n)
store.collect.chapterWords = [store.collect.words]
}
})
watch(store.simple.originWords, (n) => {
store.simple.words = cloneDeep(n)
store.simple.chapterWords = [store.simple.words]
if (n.length === 0) {
store.simple.words = []
store.simple.chapterWords = []
} else {
store.simple.words = cloneDeep(n)
store.simple.chapterWords = [store.simple.words]
}
})
watch(store.wrong.originWords, (n) => {
store.wrong.words = cloneDeep(n)
store.wrong.chapterWords = [store.wrong.words]
if (n.length === 0) {
store.wrong.words = []
store.wrong.chapterWords = []
} else {
store.wrong.words = cloneDeep(n)
store.wrong.chapterWords = [store.wrong.words]
}
})
async function init() {
@@ -74,7 +89,7 @@ onMounted(() => {
</script>
<template>
<Backgorund/>
<!-- <Backgorund/>-->
<router-view/>
<ArticleContentDialog/>
<SettingDialog v-if="runtimeStore.showSettingModal" @close="runtimeStore.showSettingModal = false"/>

View File

@@ -273,7 +273,6 @@ export function getSplitTranslateText(article: string) {
export function isArticle(type: DictType): boolean {
return [
DictType.article,
DictType.customArticle
].includes(type)
}

View File

@@ -98,7 +98,6 @@ export async function checkDictHasTranslate(dict: Dict) {
let dictResourceUrl = `./dicts/${dict.language}/${dict.type}/${dict.translateLanguage}/${dict.url}`;
if ([
DictType.word,
DictType.customWord,
].includes(dict.type)) {
if (!dict.originWords.length) {
let r = await fetch(dictResourceUrl)
@@ -129,7 +128,6 @@ export async function checkDictHasTranslate(dict: Dict) {
if ([
DictType.article,
DictType.customArticle,
].includes(dict.type)) {
if (!dict.articles.length) {
let r = await fetch(dictResourceUrl)

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})

View File

@@ -163,20 +163,17 @@ export const useBaseStore = defineStore('base', {
chapter(state: BaseState): Word[] {
return this.currentDict.chapterWords[this.currentDict.chapterIndex] ?? []
},
dictTitle(state: BaseState) {
let title = this.currentDict.name
return title + this.chapterName
},
chapterName(state: BaseState) {
let title = ''
switch (this.currentDict.type) {
case DictType.collect:
if (state.current.practiceType === DictType.article || state.current.practiceType === DictType.customArticle) {
if (state.current.practiceType === DictType.article) {
return `${this.currentDict.chapterIndex + 1}`
}
return ''
case DictType.wrong:
case DictType.simple:
return this.currentDict.name
case DictType.word:
case DictType.customWord:
return `${this.currentDict.chapterIndex + 1}`
}
return title
@@ -252,6 +249,8 @@ export const useBaseStore = defineStore('base', {
}
}
}
emitter.emit(EventKey.changeDict)
resolve(true)
})
},
@@ -296,7 +295,6 @@ export const useBaseStore = defineStore('base', {
this.current.index = this.myDictList.length - 1
}
emitter.emit(EventKey.resetWord)
emitter.emit(EventKey.changeDict)
}
},

View File

@@ -46,9 +46,7 @@ export enum DictType {
simple = 'simple',
wrong = 'wrong',
word = 'word',
customWord = 'customWord',
article = 'article',
customArticle = 'customArticle'
}
export const DefaultArticleWord: ArticleWord = {
@@ -227,7 +225,7 @@ export const DefaultDict: Dict = {
wordIndex: 0,//单词下标
articles: [],
statistics: [],
isCustom: false,
isCustom: true,
length: 0,
/*资源属性*/
resourceId: '',
@@ -235,7 +233,7 @@ export const DefaultDict: Dict = {
category: '',
tags: [],
translateLanguage: 'common',
type: DictType.customWord,
type: DictType.word,
language: 'en',
}