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

@@ -104,7 +104,7 @@
"text": "On Wednesday evening, we went to the Town Hall. It was the last day of the year and a large crowd of people had gathered under the Town Hall clock. It would strike twelve in twenty minutes' time. Fifteen minutes passed and then, at five to twelve, the clock stopped. The big minute hand did not move. We waited and waited, but nothing happened. Suddenly someone shouted. 'It's two minutes past twelve! The clock has stopped!' I looked at my watch. It was true. The big clock refused to welcome the New Year. At that moment, everybody began to laugh and sing.\n",
"textCustomTranslate": "星期三的晚上,我们去了市政厅。\n 那是一年的最后一天,一大群人聚集在市政厅的大钟下面。\n再过20分钟大钟将敲响12下。\n15分钟过去了而就在11点55分时大钟停了。\n那根巨大的分针不动了。\n 我们等啊等啊,可情况没有变化。\n突然有人喊道“已经12点零2分了\n那钟已经停了”\n我看了一下我的手表\n果真如此。\n那座大钟不愿意迎接新年。\n此时大家已经笑了起来同时唱起了歌。",
"textNetworkTranslate": "",
"textCustomTranslateIsFormat": false,
"textCustomTranslateIsFormat": true,
"useTranslateType": "custom",
"newWords": [],
"id": "UydP2M"

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