This commit is contained in:
zyronon
2023-12-04 22:59:33 +08:00
parent c368833d10
commit 71b467b814
15 changed files with 475 additions and 451 deletions

View File

@@ -89,7 +89,7 @@ onMounted(() => {
</script>
<template>
<!-- <Backgorund/>-->
<Backgorund/>
<router-view/>
<ArticleContentDialog/>
<SettingDialog v-if="runtimeStore.showSettingModal" @close="runtimeStore.showSettingModal = false"/>

View File

@@ -39,8 +39,12 @@
--color-input-bg: white;
--color-input-icon: #d3d4d7;
--color-textarea-bg: white;
--font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
--word-font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
}
html.dark {
@@ -72,6 +76,9 @@ html.dark {
--color-input-bg: rgba(14, 18, 23, 1);
--color-input-icon: #383737;
--color-textarea-bg: rgb(43, 45, 48);
}
@media (max-width: 1680px) {
@@ -174,7 +181,7 @@ a {
min-height: 20rem;
width: 100%;
box-sizing: border-box;
background: var(--color-item-bg);
background: var(--color-textarea-bg);
&:focus {
border: 1px solid var(--color-main-active);
@@ -429,5 +436,4 @@ footer {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 10rem;
}

View File

@@ -2,7 +2,8 @@
import {$ref} from "vue/macros";
import {Icon} from "@iconify/vue";
import Close from "@/components/icon/Close.vue";
import {useWindowClick} from "@/hooks/event.ts";
import {useDisableEventListener, useWindowClick} from "@/hooks/event.ts";
import {watch} from "vue";
defineProps<{
modelValue: string
@@ -17,6 +18,8 @@ useWindowClick((e: PointerEvent) => {
focus = inputEl.contains(e.target as any);
})
useDisableEventListener(() => focus)
</script>
<template>
@@ -30,7 +33,9 @@ useWindowClick((e: PointerEvent) => {
:value="modelValue"
@input="e=>$emit('update:modelValue',e.target.value)"
>
<Close @click="$emit('update:modelValue','')"/>
<transition name="fade">
<Close v-if="modelValue" @click="$emit('update:modelValue','')"/>
</transition>
</div>
</template>

View File

@@ -448,7 +448,7 @@ defineExpose({save, getEditArticle: () => cloneDeep(editArticle)})
border-radius: 8rem;
.section {
background: var(--color-item-bg);
background: var(--color-textarea-bg);
margin-bottom: 20rem;
padding: var(--space);
border-radius: 8rem;

View File

@@ -4,6 +4,7 @@ import {Article, DefaultArticle} from "@/types.ts";
import {cloneDeep} from "lodash-es";
import Dialog from "@/components/dialog/Dialog.vue";
import EditArticle from "@/components/article/EditArticle.vue";
import {useDisableEventListener} from "@/hooks/event.ts";
interface IProps {
article?: Article
@@ -16,14 +17,18 @@ const props = withDefaults(defineProps<IProps>(), {
})
const emit = defineEmits<{
save: [val: Article]
'update:modelValue': [val: boolean]
}>()
useDisableEventListener(() => props.modelValue)
</script>
<template>
<Dialog
:header="false"
:model-value="props.modelValue"
@close="emit('update:modelValue',false)"
:full-screen="true"
>
<div class="wrapper">

View File

@@ -65,7 +65,7 @@ onUnmounted(() => {
display: flex;
gap: var(--space);
padding: var(--space);
color: var(--color-font-1);
.article-content {
flex: 1;

View File

@@ -108,10 +108,6 @@ function changeDict() {
close()
}
function clickEvent(e) {
console.log('e', e)
}
const dictIsArticle = $computed(() => {
return isArticle(runtimeStore.editDict.type)
})
@@ -128,7 +124,7 @@ function showAllWordModal() {
})
}
function resetChapterList(v) {
function resetChapterList(v:number) {
const temp = () => {
runtimeStore.editDict.chapterWordNumber = v
runtimeStore.editDict.chapterWords = chunk(runtimeStore.editDict.words, runtimeStore.editDict.chapterWordNumber)
@@ -158,7 +154,7 @@ function changeSort(v, notice: boolean = true) {
} else {
runtimeStore.editDict.words = reverse(cloneDeep(runtimeStore.editDict.originWords))
}
resetChapterList()
resetChapterList(runtimeStore.editDict.chapterWordNumber)
notice && ElMessage.success('已重新排序')
}
if (runtimeStore.editDict.isCustom) {
@@ -280,14 +276,16 @@ function handleChangeArticleChapterIndex(val) {
:title="`添加${dictIsArticle?'文章':'单词'}`"
/>
</div>
<div class="text">开始日期-</div>
<div class="text">花费时间-</div>
<div class="text">累积错误-</div>
<div class="text">进度
<el-progress :percentage="0"
:stroke-width="8"
:show-text="false"/>
</div>
<template v-if="false">
<div class="text">开始日期-</div>
<div class="text">花费时间-</div>
<div class="text">累积错误-</div>
<div class="text">进度
<el-progress :percentage="0"
:stroke-width="8"
:show-text="false"/>
</div>
</template>
</div>
<div class="center-column">
<div class="common-title">学习设置</div>
@@ -385,63 +383,52 @@ function handleChangeArticleChapterIndex(val) {
:title="`管理${dictIsArticle?'文章':'章节'}`"
/>
</div>
<template v-if="dictIsArticle">
<ArticleList
v-if="runtimeStore.editDict.articles.length"
:isActive="false"
v-loading="loading"
:show-border="true"
@title="(val:any) => emitter.emit(EventKey.openArticleContentModal,val.item)"
@click="handleChangeArticleChapterIndex"
:active-id="activeId"
:list="runtimeStore.editDict.articles">
<template v-slot:prefix="{item,index}">
<input type="radio" :checked="activeId === item.id">
</template>
</ArticleList>
<Empty v-else/>
</template>
<template v-else>
<BaseList
ref="chapterListRef"
v-if="chapterList2.length"
:list="chapterList2"
:show-border="true"
@click="(val:any) => runtimeStore.editDict.chapterIndex = val.index"
:active-index="runtimeStore.editDict.chapterIndex"
>
<template v-slot:prefix="{ item, index }">
<input type="radio" :checked="runtimeStore.editDict.chapterIndex === item.id">
</template>
<template v-slot="{ item, index }">
<div class="item-title" @click.stop="showWordListModal({item,index})">
<span>{{ item.id + 1 }}</span>&nbsp;&nbsp;&nbsp;
<span>{{ runtimeStore.editDict.chapterWords[item.id]?.length }}</span>
</div>
</template>
</BaseList>
<Empty v-else/>
</template>
<div class="list-content">
<template v-if="dictIsArticle">
<ArticleList
v-if="runtimeStore.editDict.articles.length"
:isActive="false"
v-loading="loading"
:show-border="true"
@title="(val:any) => emitter.emit(EventKey.openArticleContentModal,val.item)"
@click="handleChangeArticleChapterIndex"
:active-id="activeId"
:list="runtimeStore.editDict.articles">
<template v-slot:prefix="{item,index}">
<input type="radio" :checked="activeId === item.id">
</template>
</ArticleList>
<Empty v-else/>
</template>
<template v-else>
<BaseList
ref="chapterListRef"
v-if="chapterList2.length"
:list="chapterList2"
:show-border="true"
@click="(val:any) => runtimeStore.editDict.chapterIndex = val.index"
:active-index="runtimeStore.editDict.chapterIndex"
>
<template v-slot:prefix="{ item, index }">
<input type="radio" :checked="runtimeStore.editDict.chapterIndex === item.id">
</template>
<template v-slot="{ item, index }">
<div class="item-title" @click.stop="showWordListModal({item,index})">
<span>{{ item.id + 1 }}</span>&nbsp;&nbsp;&nbsp;
<span>{{ runtimeStore.editDict.chapterWords[item.id]?.length }}</span>
</div>
</template>
</BaseList>
<Empty v-else/>
</template>
</div>
<div class="footer">
<!-- <BaseButton @click="step = 0">导出</BaseButton>-->
<BaseButton @click="close">关闭</BaseButton>
<BaseButton @click="changeDict">切换</BaseButton>
</div>
</div>
</div>
<div v-if="false" class="activity">
<ActivityCalendar
:data="[{ date: '2023-05-22', count: 5 }]"
:width="40"
:height="7"
:cellLength="16"
:cellInterval="8"
:fontSize="12"
:showLevelFlag="false"
:showWeekDayFlag="false"
:clickEvent="clickEvent"
/>
</div>
<div class="footer">
<!-- <BaseButton @click="step = 0">导出</BaseButton>-->
<BaseButton @click="close">关闭</BaseButton>
<BaseButton @click="changeDict">切换</BaseButton>
</div>
</div>
</div>
</Slide>
@@ -505,17 +492,21 @@ $header-height: 60rem;
display: flex;
position: relative;
.left-column {
overflow: auto;
flex: 6;
.column {
background: var(--color-second-bg);
color: var(--color-font-1);
display: flex;
flex-direction: column;
}
.left-column {
flex: 5;
gap: 10rem;
min-height: 100rem;
position: relative;
color: var(--color-font-1);
font-size: 14rem;
padding-right: var(--space);
@extend .column;
.name {
font-size: 24rem;
@@ -542,10 +533,7 @@ $header-height: 60rem;
.center-column {
overflow: auto;
flex: 7;
background: white;
border-radius: 10rem;
background: var(--color-second-bg);
color: var(--color-font-1);
@extend .column;
.setting {
.row {
@@ -575,58 +563,20 @@ $header-height: 60rem;
font-size: 13rem;
}
}
}
.right-column {
flex: 7;
border-radius: 10rem;
background: var(--color-second-bg);
color: var(--color-font-1);
display: flex;
flex-direction: column;
@extend .column;
.tabs {
.list-content {
flex: 1;
overflow: hidden;
display: flex;
margin-bottom: 10rem;
.tab {
font-size: 20rem;
color: var(--color-font-3);
flex: 1;
display: flex;
justify-content: center;
align-items: center;
span {
cursor: pointer;
border-bottom: 3px solid transparent;
padding-bottom: 10rem;
transition: all .3s;
}
&.active {
color: var(--color-font-1);
span {
border-bottom: 3px solid var(--color-main-active);
}
}
}
}
.scroll {
height: calc(100% - 45rem);
}
}
}
.activity {
display: flex;
justify-content: center;
}
.footer {
box-sizing: content-box;
display: flex;
@@ -634,7 +584,7 @@ $header-height: 60rem;
justify-content: flex-end;
gap: var(--space);
padding-right: var(--space);
margin-bottom: 20rem;
margin: var(--space) 0;
}
}
}

View File

@@ -109,15 +109,15 @@ const emit = defineEmits<{
position: absolute;
bottom: 0;
left: 0;
height: 50rem;
width: 50rem;
height: 55rem;
width: 55rem;
color: white; background-color: skyblue;
clip-path: polygon(0 10%, 0% 100%, 100% 100%);
font-size: 12rem;
display: flex;
justify-content: flex-start;
align-items: flex-end;
padding: 5rem;
padding: 3rem;
box-sizing: border-box;
}
}

View File

@@ -53,6 +53,7 @@ export function useWordOptions() {
if (rIndex > -1) {
store.wrong.originWords.splice(rIndex, 1)
}
store.wrong.length = store.wrong.originWords.length
}
function delSimpleWord(val: Word) {
@@ -60,6 +61,7 @@ export function useWordOptions() {
if (rIndex > -1) {
store.simple.originWords.splice(rIndex, 1)
}
store.simple.length = store.simple.originWords.length
}
return {

View File

@@ -303,7 +303,7 @@ $header-height: 50rem;
font-size: 16rem;
&.active {
color: rgb(36, 127, 255);
color: var(--color-main-active);
font-weight: bold;
}
}

View File

@@ -82,18 +82,6 @@ watch(() => settingStore.dictation, () => {
calcTranslateLocation()
})
onMounted(() => {
emitter.on(EventKey.resetWord, () => {
wrong = input = ''
})
emitter.on(EventKey.onTyping, onTyping)
})
onUnmounted(() => {
emitter.off(EventKey.resetWord,)
emitter.off(EventKey.onTyping, onTyping)
})
function nextSentence() {
// wordData.words = [
// {"name": "pharmacy", "trans": ["药房;配药学,药剂学;制药业;一批备用药品"], "usphone": "'fɑrməsi", "ukphone": "'fɑːməsɪ"},
@@ -218,7 +206,6 @@ function onTyping(e: KeyboardEvent) {
playKeyboardAudio()
}
e.preventDefault()
}
function calcTranslateLocation() {
@@ -247,7 +234,9 @@ function calcTranslateLocation() {
}
function play() {
return playWordAudio('article1')
let currentSection = props.article.sections[sectionIndex]
return playWordAudio(currentSection[sentenceIndex].text)
if (isPlay) {
isPlay = false
return window.speechSynthesis.pause();
@@ -262,55 +251,14 @@ function play() {
window.speechSynthesis.speak(msg);
}
function onKeyDown(e: KeyboardEvent) {
if (!props.active) return
switch (e.key) {
case 'Backspace':
if (wrong) {
wrong = ''
} else {
input = input.slice(0, -1)
}
break
case ShortcutKeyMap.Collect:
break
case ShortcutKeyMap.Remove:
break
case ShortcutKeyMap.Ignore:
nextSentence()
break
case ShortcutKeyMap.Show:
if (settingStore.allowWordTip) {
hoverIndex = {
sectionIndex: sectionIndex,
sentenceIndex: sentenceIndex,
}
}
break
}
// console.log(
// 'sectionIndex', sectionIndex,
// 'sentenceIndex', sentenceIndex,
// 'wordIndex', wordIndex,
// 'stringIndex', stringIndex,
// )
e.preventDefault()
}
function onKeyUp() {
hoverIndex = {
sectionIndex: -1,
sentenceIndex: -1,
function del() {
if (wrong) {
wrong = ''
} else {
input = input.slice(0, -1)
}
}
useOnKeyboardEventListener(onKeyDown, onKeyUp)
// useEventListener('keydown', onKeyDown)
// useEventListener('keyup', onKeyUp)
function playWord(word: ArticleWord) {
playWordAudio(word.name)
}
@@ -366,6 +314,30 @@ const {
toggleArticleCollect
} = useArticleOptions()
function showSentence(i1: number = sectionIndex, i2: number = sentenceIndex) {
hoverIndex = {sectionIndex: i1, sentenceIndex: i2}
}
function hideSentence() {
hoverIndex = {sectionIndex: -1, sentenceIndex: -1}
}
onMounted(() => {
emitter.on(EventKey.resetWord, () => {
wrong = input = ''
})
emitter.on(EventKey.onTyping, onTyping)
})
onUnmounted(() => {
emitter.off(EventKey.resetWord,)
emitter.off(EventKey.onTyping, onTyping)
})
defineExpose({showSentence, play, del,hideSentence,nextSentence})
</script>
<template>
@@ -408,8 +380,8 @@ const {
sectionIndex === indexI && sentenceIndex === indexJ && settingStore.dictation
?'dictation':''
]"
@mouseenter="settingStore.allowWordTip && (hoverIndex = {sectionIndex : indexI,sentenceIndex :indexJ})"
@mouseleave="hoverIndex = {sectionIndex : -1,sentenceIndex :-1}"
@mouseenter="settingStore.allowWordTip && showSentence(indexI,indexJ)"
@mouseleave="hideSentence"
@click="playWordAudio(sentence.text)"
v-for="(sentence,indexJ) in section">
<span

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import {$ref} from "vue/macros";
import {$computed, $ref} from "vue/macros";
import TypingArticle from "./TypingArticle.vue";
import {
Article,
@@ -29,6 +29,7 @@ import {useSettingStore} from "@/stores/setting.ts";
import BaseIcon from "@/components/BaseIcon.vue";
import {syncMyDictList, useArticleOptions} from "@/hooks/dict.ts";
import ArticleList from "@/components/list/ArticleList.vue";
import {useOnKeyboardEventListener} from "@/hooks/event.ts";
const store = useBaseStore()
const practiceStore = usePracticeStore()
@@ -48,9 +49,12 @@ let articleData = $ref({
stringIndex: 0,
})
let showEditArticle = $ref(false)
let typingArticleRef = $ref<any>()
let editArticle = $ref<Article>(cloneDeep(DefaultArticle))
let articleIsActive = $computed(() => tabIndex === 0)
function next() {
if (!articleIsActive) return
if (store.currentDict.chapterIndex >= articleData.articles.length - 1) {
store.currentDict.chapterIndex = 0
} else store.currentDict.chapterIndex++
@@ -59,19 +63,6 @@ function next() {
getCurrentPractice()
}
onMounted(() => {
init()
emitter.on(EventKey.changeDict, init)
emitter.on(EventKey.next, next)
emitter.on(ShortcutKey.NextChapter, next)
})
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)
@@ -180,7 +171,8 @@ function saveArticle(val: Article) {
setArticle(val)
}
function edit(val: Article) {
function edit(val: Article = articleData.article) {
if (!articleIsActive)return
// tabIndex = 1
// wordData.words = [
// {
@@ -256,6 +248,71 @@ function sort(list: Word[]) {
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()
emitter.on(EventKey.changeDict, init)
emitter.on(EventKey.next, next)
emitter.on(ShortcutKey.NextChapter, next)
emitter.on(ShortcutKey.PlayWordPronunciation, play)
emitter.on(ShortcutKey.ShowWord, show)
emitter.on(ShortcutKey.Next, skip)
emitter.on(ShortcutKey.ToggleCollect, collect)
emitter.on(ShortcutKey.EditArticle, shortcutKeyEdit)
})
onUnmounted(() => {
emitter.off(EventKey.changeDict, init)
emitter.off(EventKey.next, next)
emitter.off(ShortcutKey.NextChapter, next)
emitter.off(ShortcutKey.PlayWordPronunciation, play)
emitter.off(ShortcutKey.ShowWord, show)
emitter.off(ShortcutKey.Next, skip)
emitter.off(ShortcutKey.ToggleCollect, collect)
emitter.off(ShortcutKey.EditArticle, shortcutKeyEdit)
})
defineExpose({getCurrentPractice})
</script>
@@ -266,10 +323,11 @@ defineExpose({getCurrentPractice})
<div class="swiper-list" :class="`step${tabIndex}`">
<div class="swiper-item">
<TypingArticle
ref="typingArticleRef"
:active="tabIndex === 0"
@edit="edit"
@wrong="wrong"
@over="over"
@over="skip"
@nextWord="nextWord"
:article="articleData.article"
/>

View File

@@ -147,10 +147,6 @@ function next(isTyping: boolean = true) {
}
}
function onKeyUp(e: KeyboardEvent) {
typingRef.hideWord()
}
function wordWrong() {
if (!store.wrong.originWords.find((v: Word) => v.name.toLowerCase() === word.name.toLowerCase())) {
store.wrong.originWords.push(word)
@@ -161,6 +157,10 @@ function wordWrong() {
}
}
function onKeyUp(e: KeyboardEvent) {
typingRef.hideWord()
}
async function onKeyDown(e: KeyboardEvent) {
// console.log('e', e)
switch (e.key) {

View File

@@ -8,13 +8,13 @@ import {nanoid} from "nanoid";
import {state} from "vue-tsc/out/shared";
export interface BaseState {
myDictList: Dict[],
current: {
index: number,
practiceType: DictType,//练习类型目前仅词典为collect时判断是练单词还是文章使用
},
simpleWords: string[],
load: boolean
myDictList: Dict[],
current: {
index: number,
practiceType: DictType,//练习类型目前仅词典为collect时判断是练单词还是文章使用
},
simpleWords: string[],
load: boolean
}
// words: [
@@ -63,241 +63,267 @@ export interface BaseState {
// ],
export const useBaseStore = defineStore('base', {
state: (): BaseState => {
return {
myDictList: [
{
...cloneDeep(DefaultDict),
id: 'collect',
name: '收藏',
type: DictType.collect,
category: '自带字典',
tags: ['自带'],
},
{
...cloneDeep(DefaultDict),
id: 'skip',
name: '简单词',
type: DictType.simple,
category: '自带字典'
},
{
...cloneDeep(DefaultDict),
id: 'wrong',
name: '错词本',
type: DictType.wrong,
category: '自带字典'
},
{
...cloneDeep(DefaultDict),
id: 'article_nce2',
name: "新概念英语2-课文",
description: '新概念英语2-课文',
category: '英语学习',
tags: ['新概念英语'],
url: 'NCE_2.json',
translateLanguage: 'common',
language: 'en',
type: DictType.article,
resourceId: 'article_nce2',
},
{
...cloneDeep(DefaultDict),
id: 'nce-new-2',
name: '新概念英语(新版)-2',
description: '新概念英语新版第二册',
category: '青少年英语',
tags: ['新概念英语'],
url: 'nce-new-2.json',
translateLanguage: 'common',
language: 'en',
type: DictType.word,
resourceId: 'nce-new-2',
},
],
current: {
index: 4,
// dictType: DictType.article,
// index: 0,
practiceType: DictType.word,
},
simpleWords: [
'a', 'an',
'i', 'my', 'you', 'your', 'me', 'it',
'am', 'is', 'do', 'are', 'did', 'were',
'what', 'who', 'where', 'how', 'no', 'yes',
'not', 'can', 'could',
'the', 'to', 'of', 'for', 'and', 'that', 'this', 'be'
],
load: false
}
},
getters: {
collect() {
return this.myDictList[0] ?? {}
},
simple(): Dict {
return this.myDictList[1]
},
wrong() {
return this.myDictList[2]
},
skipWordNames() {
return this.simple.originWords.map(v => v.name.toLowerCase())
},
skipWordNamesWithSimpleWords() {
return this.simple.originWords.map(v => v.name.toLowerCase()).concat(this.simpleWords)
},
isArticle(state: BaseState): boolean {
//如果是收藏时,特殊判断
if (this.currentDict.type === DictType.collect) {
return state.current.practiceType === DictType.article
}
return [
DictType.article,
].includes(this.currentDict.type)
},
currentDict(): Dict {
return this.myDictList[this.current.index]
},
chapter(state: BaseState): Word[] {
return this.currentDict.chapterWords[this.currentDict.chapterIndex] ?? []
},
chapterName(state: BaseState) {
let title = ''
switch (this.currentDict.type) {
case DictType.collect:
if (state.current.practiceType === DictType.article) {
return `${this.currentDict.chapterIndex + 1}`
}
case DictType.wrong:
case DictType.simple:
return this.currentDict.name
case DictType.word:
return `${this.currentDict.chapterIndex + 1}`
}
return title
}
},
actions: {
setState(obj: any) {
//这样不会丢失watch的值的引用
merge(this, obj)
},
async init() {
return new Promise(async resolve => {
try {
let configStr: string = await localforage.getItem(SaveDict.key)
// console.log(configStr)
// console.log('s', new Blob([configStr]).size)
configStr = ''
if (configStr) {
let data = JSON.parse(configStr)
let state: BaseState = data.val
let version = Number(data.version)
// console.log('state', state)
state.load = false
if (version === SaveDict.version) {
this.setState(state)
} else {
if (version === 2) {
}
// this.setState(state)
}
}
} catch (e) {
console.error('读取本地dict数据失败', e)
state: (): BaseState => {
return {
myDictList: [
{
...cloneDeep(DefaultDict),
id: 'collect',
name: '收藏',
type: DictType.collect,
category: '自带字典',
tags: ['自带'],
},
{
...cloneDeep(DefaultDict),
id: 'skip',
name: '简单词',
type: DictType.simple,
category: '自带字典'
},
{
...cloneDeep(DefaultDict),
id: 'wrong',
name: '错词本',
type: DictType.wrong,
category: '自带字典'
},
{
...cloneDeep(DefaultDict),
id: 'cet4',
name: 'CET-4',
description: '大学英语四级词库',
category: '中国考试',
tags: ['大学英语'],
url: 'CET4_T.json',
length: 2607,
translateLanguage: 'common',
language: 'en',
type: DictType.word
},
{
...cloneDeep(DefaultDict),
id: 'article_nce2',
name: "新概念英语2-课文",
description: '新概念英语2-课文',
category: '英语学习',
tags: ['新概念英语'],
url: 'NCE_2.json',
translateLanguage: 'common',
language: 'en',
type: DictType.article,
resourceId: 'article_nce2',
length: 10
},
{
...cloneDeep(DefaultDict),
id: 'nce-new-2',
name: '新概念英语(新版)-2',
description: '新概念英语新版第二册',
category: '青少年英语',
tags: ['新概念英语'],
url: 'nce-new-2.json',
translateLanguage: 'common',
language: 'en',
type: DictType.word,
resourceId: 'nce-new-2',
length: 862
},
],
current: {
index: 3,
// dictType: DictType.article,
// index: 0,
practiceType: DictType.word,
},
simpleWords: [
'a', 'an',
'i', 'my', 'you', 'your', 'me', 'it',
'am', 'is', 'do', 'are', 'did', 'were',
'what', 'who', 'where', 'how', 'no', 'yes',
'not', 'can', 'could',
'the', 'to', 'of', 'for', 'and', 'that', 'this', 'be'
],
load: false
}
},
getters: {
collect() {
return this.myDictList[0] ?? {}
},
simple(): Dict {
return this.myDictList[1]
},
wrong() {
return this.myDictList[2]
},
skipWordNames() {
return this.simple.originWords.map(v => v.name.toLowerCase())
},
skipWordNamesWithSimpleWords() {
return this.simple.originWords.map(v => v.name.toLowerCase()).concat(this.simpleWords)
},
isArticle(state: BaseState): boolean {
//如果是收藏时,特殊判断
if (this.currentDict.type === DictType.collect) {
return state.current.practiceType === DictType.article
}
return [
DictType.article,
].includes(this.currentDict.type)
},
currentDict(): Dict {
return this.myDictList[this.current.index]
},
chapter(state: BaseState): Word[] {
return this.currentDict.chapterWords[this.currentDict.chapterIndex] ?? []
},
chapterName(state: BaseState) {
let title = ''
switch (this.currentDict.type) {
case DictType.collect:
if (state.current.practiceType === DictType.article) {
return `${this.currentDict.chapterIndex + 1}`
}
case DictType.wrong:
case DictType.simple:
return this.currentDict.name
case DictType.word:
return `${this.currentDict.chapterIndex + 1}`
}
return title
}
},
actions: {
setState(obj: any) {
//这样不会丢失watch的值的引用
merge(this, obj)
},
async init() {
return new Promise(async resolve => {
try {
let configStr: string = await localforage.getItem(SaveDict.key)
// console.log(configStr)
// console.log('s', new Blob([configStr]).size)
configStr = ''
if (configStr) {
let data = JSON.parse(configStr)
let state: BaseState = data.val
let version = Number(data.version)
// console.log('state', state)
state.load = false
if (this.current.index < 3) {
if (version === SaveDict.version) {
this.setState(state)
} else {
if (version === 2) {
} else {
let dictResourceUrl = `./dicts/${this.currentDict.language}/${this.currentDict.type}/${this.currentDict.translateLanguage}/${this.currentDict.url}`;
if ([DictType.word].includes(this.currentDict.type)) {
if (!this.currentDict.originWords.length) {
let r = await fetch(dictResourceUrl)
// let r = await fetch(`.${this.currentDict.url}`)
let v = await r.json()
v.map(s => {
s.id = nanoid(6)
})
if (this.currentDict.translateLanguage === 'common') {
const runtimeStore = useRuntimeStore()
let r2 = await fetch('./translate/en2zh_CN-min.json')
// fetch('http://sc.ttentau.top/en2zh_CN-min.json').then(r2 => {
let list: Word[] = await r2.json()
if (list && list.length) {
runtimeStore.translateWordList = list
}
// this.setState(state)
}
}
} catch (e) {
console.error('读取本地dict数据失败', e)
}
}
this.currentDict.originWords = cloneDeep(v)
this.currentDict.words = cloneDeep(v)
this.currentDict.chapterWords = chunk(this.currentDict.words, this.currentDict.chapterWordNumber)
const runtimeStore = useRuntimeStore()
if (this.current.index < 3) {
} else {
let dictResourceUrl = `./dicts/${this.currentDict.language}/${this.currentDict.type}/${this.currentDict.translateLanguage}/${this.currentDict.url}`;
if ([DictType.word].includes(this.currentDict.type)) {
if (!this.currentDict.originWords.length) {
let r = await fetch(dictResourceUrl)
// let r = await fetch(`.${this.currentDict.url}`)
let v = await r.json()
v.map(s => {
s.id = nanoid(6)
})
if (this.currentDict.translateLanguage === 'common') {
let r2 = await fetch('./translate/en2zh_CN-min.json')
// fetch('http://sc.ttentau.top/en2zh_CN-min.json').then(r2 => {
let list: Word[] = await r2.json()
if (list && list.length) {
runtimeStore.translateWordList = list
}
}
this.currentDict.originWords = cloneDeep(v)
this.currentDict.words = cloneDeep(v)
this.currentDict.chapterWords = chunk(this.currentDict.words, this.currentDict.chapterWordNumber)
}
}
if ([DictType.article].includes(this.currentDict.type)) {
if (!this.currentDict.articles.length) {
let r = await fetch(dictResourceUrl)
let s: any[] = await r.json()
this.currentDict.articles = cloneDeep(s.map(v => {
v.id = nanoid(6)
return v
}))
}
}
}
//TODO 先这样,默认加载
if (!runtimeStore.translateWordList.length) {
setTimeout(async () => {
let r2 = await fetch('./translate/en2zh_CN-min.json')
// fetch('http://sc.ttentau.top/en2zh_CN-min.json').then(r2 => {
let list: Word[] = await r2.json()
if (list && list.length) {
runtimeStore.translateWordList = list
}
})
}
emitter.emit(EventKey.changeDict)
resolve(true)
})
},
saveStatistics(statistics: DisplayStatistics) {
if (statistics.spend > 1000 * 10) {
delete statistics.wrongWords
this.currentDict.statistics.push(statistics)
}
}
if ([DictType.article].includes(this.currentDict.type)) {
if (!this.currentDict.articles.length) {
let r = await fetch(dictResourceUrl)
let s: any[] = await r.json()
this.currentDict.articles = cloneDeep(s.map(v => {
v.id = nanoid(6)
return v
}))
},
async changeDict(dict: Dict, practiceType?: DictType, chapterIndex?: number, wordIndex?: number) {
//TODO 保存统计
// this.saveStatistics()
console.log('changeDict', cloneDeep(dict), chapterIndex, wordIndex)
if (chapterIndex === undefined) chapterIndex = dict.chapterIndex
if (wordIndex === undefined) wordIndex = dict.wordIndex
if (practiceType === undefined) this.current.practiceType = practiceType
if ([DictType.collect,
DictType.simple,
DictType.wrong].includes(dict.type)) {
dict.chapterIndex = 0
dict.wordIndex = wordIndex
dict.chapterWordNumber = dict.words.length
dict.chapterWords = [dict.words]
} else {
if (dict.type === DictType.article) {
if (chapterIndex > dict.articles.length) {
dict.chapterIndex = 0
dict.wordIndex = 0
}
} else {
if (chapterIndex > dict.chapterWords.length) {
dict.chapterIndex = 0
dict.wordIndex = 0
}
}
}
// await checkDictHasTranslate(dict)
let rIndex = this.myDictList.findIndex((v: Dict) => v.id === dict.id)
if (rIndex > -1) {
this.myDictList[rIndex] = dict
this.current.index = rIndex
} else {
this.myDictList.push(cloneDeep(dict))
this.current.index = this.myDictList.length - 1
}
}
}
emitter.emit(EventKey.changeDict)
resolve(true)
})
},
saveStatistics(statistics: DisplayStatistics) {
if (statistics.spend > 1000 * 10) {
delete statistics.wrongWords
this.currentDict.statistics.push(statistics)
}
},
async changeDict(dict: Dict, practiceType?: DictType, chapterIndex?: number, wordIndex?: number) {
//TODO 保存统计
// this.saveStatistics()
console.log('changeDict', cloneDeep(dict), chapterIndex, wordIndex)
if (chapterIndex === undefined) chapterIndex = dict.chapterIndex
if (wordIndex === undefined) wordIndex = dict.wordIndex
if (practiceType === undefined) this.current.practiceType = practiceType
if ([DictType.collect,
DictType.simple,
DictType.wrong].includes(dict.type)) {
dict.chapterIndex = 0
dict.wordIndex = wordIndex
dict.chapterWordNumber = dict.words.length
dict.chapterWords = [dict.words]
} else {
if (dict.type === DictType.article) {
if (chapterIndex > dict.articles.length) {
dict.chapterIndex = 0
dict.wordIndex = 0
}
} else {
if (chapterIndex > dict.chapterWords.length) {
dict.chapterIndex = 0
dict.wordIndex = 0
}
emitter.emit(EventKey.changeDict)
}
}
// await checkDictHasTranslate(dict)
let rIndex = this.myDictList.findIndex((v: Dict) => v.id === dict.id)
if (rIndex > -1) {
this.myDictList[rIndex] = dict
this.current.index = rIndex
} else {
this.myDictList.push(cloneDeep(dict))
this.current.index = this.myDictList.length - 1
}
emitter.emit(EventKey.changeDict)
}
},
},
})