feat:重构代码

This commit is contained in:
zyronon
2025-07-13 02:24:08 +08:00
parent cd4a779e6e
commit 97c0c47746
46 changed files with 119360 additions and 1184 deletions

View File

@@ -35,7 +35,7 @@
--toolbar-width: 45rem;
--toolbar-height: 3.2rem;
--panel-width: 24rem;
--space: 1.2rem;
--space: 1rem;
--radius: .5rem;
--shadow: rgba(0, 0, 0, 0.08) 0px 4px 12px;
--panel-margin-left: calc(50% - var(--practice-wrapper-translateX) / 2 + var(--toolbar-width) / 2 + 2rem);

View File

@@ -23,6 +23,7 @@ defineEmits<{
.empty {
width: 100%;
height: 100%;
min-height: 18rem;
display: flex;
justify-content: center;
align-items: center;
@@ -39,4 +40,4 @@ defineEmits<{
width: 9rem;
}
}
</style>
</style>

View File

@@ -34,16 +34,8 @@ export const EnKeyboardMap: KeyboardMap = {
//生成文章段落数据
export function genArticleSectionData(article: Article): number {
let text = article.text.trim()
if (!text) {
// text = "Last week I went to the theatre. I had a very good seat. The play was very interesting. I did not enjoy it. A young man and a young woman were sitting behind me. They were talking loudly. I got very angry. I could not hear the actors. I turned round. I looked at the man and the woman angrily. They did not pay any attention. In the end, I could not bear it. I turned round again. 'I can't hear a word!' I said angrily.\n\n 'It's none of your business,' the young man said rudely. 'This is a private conversation!'"
// text = `While it is yet to be seen what direction the second Trump administration will take globally in its China policy, VOA traveled to the main island of Mahe in Seychelles to look at how China and the U.S. have impacted the country, and how each is fairing in that competition for influence there.`
// text = "It was Sunday. I never get up early on Sundays. I sometimes stay in bed until lunchtime. Last Sunday I got up very late. I looked out of the window. It was dark outside. 'What a day!' I thought. 'It's raining again.' Just then, the telephone rang. It was my aunt Lucy. 'I've just arrived by train,' she said. 'I'm coming to see you.'\n\n 'But I'm still having breakfast,' I said.\n\n 'What are you doing?' she asked.\n\n 'I'm having breakfast,' I repeated.\n\n 'Dear me,' she said. 'Do you always get up so late? It's one o'clock!'"
article.sections = []
ElMessage.error('请填写原文!')
return
}
console.log('genArticleSectionData',text)
// console.log('genArticleSectionData',text)
let keyboardMap = EnKeyboardMap
let sections: Sentence[][] = []
@@ -237,7 +229,7 @@ export function genArticleSectionData(article: Article): number {
article.sections = sections
let failCount = 0
let translateList = article.textTranslate.split('\n\n')
let translateList = article.textTranslate?.split('\n\n') || []
for (let i = 0; i < article.sections.length; i++) {
let v = article.sections[i]
let sList = []
@@ -270,7 +262,7 @@ export function genArticleSectionData(article: Article): number {
article.textTranslate = translate
let count = 0
if (article.lrcPosition.length) {
if (article?.lrcPosition?.length) {
article.sections.map((v, i) => {
v.map((w, j) => {
w.audioPosition = article.lrcPosition[count]

View File

@@ -130,7 +130,7 @@ export function getCurrentStudyWord() {
//取上一次学习的单词用于复习
let list = getList(s, e)
list.map(item => {
if (!store.master.words.map(v => v.word.toLowerCase()).includes(item.word.toLowerCase())) {
if (!store.known.words.map(v => v.word.toLowerCase()).includes(item.word.toLowerCase())) {
data.review.push(item)
}
})
@@ -148,7 +148,7 @@ export function getCurrentStudyWord() {
if (d.length >= Math.floor(dict.perDayStudyNumber / 4)) break
}
let item = list[i]
if (!store.master.words.map(v => v.word.toLowerCase()).includes(item.word.toLowerCase())) {
if (!store.known.words.map(v => v.word.toLowerCase()).includes(item.word.toLowerCase())) {
d.push(item)
}
}

View File

@@ -35,6 +35,10 @@ async function getBookDetail(val: DictResource) {
}
async function getBookDetail2(val: Dict) {
if (!val.name) {
showSearchDialog = true
return
}
runtimeStore.editDict = cloneDeep(val)
nav('book-detail')
}
@@ -51,6 +55,14 @@ function addBook() {
runtimeStore.editDict = getDefaultDict()
nav('book-detail', {isAdd: true})
}
function startStudy() {
if (!base.currentBook.name) {
showSearchDialog = true
return
}
router.push('/learn-article')
}
</script>
<template>
@@ -58,16 +70,21 @@ function addBook() {
<div class="card ">
<div class="flex justify-between items-center">
<div class="bg-slate-200 p-3 gap-4 rounded-md cursor-pointer flex items-center">
<span class="text-lg font-bold">{{ base.currentArticleDict.name }}</span>
<BaseIcon @click="showSearchDialog = true" icon="gg:arrows-exchange"/>
<span class="text-lg font-bold"
@click="getBookDetail2(base.currentBook)">{{
base.currentBook.name ?? '请选择书籍开始学习'
}}</span>
<BaseIcon @click="showSearchDialog = true"
:icon="base.currentBook.name?'gg:arrows-exchange':'fluent:add-20-filled'"/>
</div>
<div class="rounded-xl bg-slate-800 flex items-center py-3 px-5 text-white cursor-pointer"
@click="router.push('/learn-article')">
:class="base.currentBook.name??'opacity-70 cursor-not-allowed'"
@click="startStudy">
开始学习
</div>
</div>
<div class="mt-5 text-sm">已学习5555个单词的1%</div>
<el-progress class="mt-1" :percentage="80" :show-text="false"></el-progress>
<div class="mt-5 text-sm">已学习{{ base.currentBook.lastLearnIndex }}篇文章</div>
<el-progress class="mt-1" :percentage="base.currentBookProgress" :show-text="false"></el-progress>
</div>
<div class="card flex flex-col">

View File

@@ -1,12 +1,11 @@
<script setup lang="ts">
import {onMounted, onUnmounted} from "vue";
import {Article, DefaultArticle} from "@/types.ts";
import {Article, getDefaultArticle} from "@/types.ts";
import BaseButton from "@/components/BaseButton.vue";
import {cloneDeep} from "lodash-es";
import {useBaseStore} from "@/stores/base.ts";
import List from "@/pages/pc/components/list/List.vue";
import Dialog from "@/pages/pc/components/dialog/Dialog.vue";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import {useDisableEventListener, useWindowClick} from "@/hooks/event.ts";
import {MessageBox} from "@/utils/MessageBox.tsx";
@@ -24,7 +23,7 @@ const emit = defineEmits<{
const base = useBaseStore()
const runtimeStore = useRuntimeStore()
let article = $ref<Article>(cloneDeep(DefaultArticle))
let article = $ref<Article>(getDefaultArticle())
let show = $ref(false)
let editArticleRef: any = $ref()
let listEl: any = $ref()
@@ -98,7 +97,7 @@ function checkDataChange() {
async function add() {
let r = await checkDataChange()
if (r) {
article = cloneDeep(DefaultArticle)
article = getDefaultArticle()
}
}
@@ -153,7 +152,7 @@ useWindowClick(() => showExport = false)
ref="listEl"
v-model:list="runtimeStore.editDict.articles"
:select-item="article"
@del-select-item="article = cloneDeep(DefaultArticle)"
@del-select-item="article = getDefaultArticle()"
@select-item="selectArticle"
>
<template v-slot="{item,index}">

View File

@@ -5,8 +5,7 @@ import BackIcon from "@/components/BackIcon.vue";
import Empty from "@/components/Empty.vue";
import ArticleList from "@/pages/pc/components/list/ArticleList.vue";
import {useBaseStore} from "@/stores/base.ts";
import {Article, DefaultArticle} from "@/types.ts";
import {cloneDeep} from "lodash-es";
import {Article, getDefaultArticle} from "@/types.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import BaseButton from "@/components/BaseButton.vue";
import {useRoute, useRouter} from "vue-router";
@@ -21,7 +20,7 @@ const route = useRoute()
let isEdit = $ref(false)
let isAdd = $ref(false)
let article: Article = $ref(cloneDeep(DefaultArticle))
let article: Article = $ref(getDefaultArticle())
let chapterIndex = $ref(-1)
function handleCheckedChange(val) {
@@ -89,7 +88,7 @@ function formClose() {
</ArticleList>
<Empty v-else/>
</div>
<div class="right flex-[3] shrink-0 pl-4 overflow-auto">
<div class="right flex-[4] shrink-0 pl-4 overflow-auto">
<div v-if="chapterIndex>-1">
<div class="en-article-family title text-xl">
<div class="text-center text-2xl">{{ article.title }}</div>

View File

@@ -9,7 +9,7 @@ import {emitter, EventKey} from "@/utils/eventBus.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {MessageBox} from "@/utils/MessageBox.tsx";
import PracticeArticle from "@/pages/pc/practice/practice-article/index.vue";
import PracticeArticle from "@/pages/pc/article/practice-article/index.vue";
import {ShortcutKey} from "@/types.ts";
import DictModal from "@/pages/pc/components/dialog/DictDiglog.vue";
import {useStartKeyboardEventListener} from "@/hooks/event.ts";
@@ -142,4 +142,4 @@ useStartKeyboardEventListener()
</template>
<style scoped lang="scss">
</style>
</style>

View File

@@ -30,7 +30,7 @@ interface IProps {
}
const props = withDefaults(defineProps<IProps>(), {
article: () => cloneDeep(DefaultArticle),
article: () => getDefaultArticle(),
type: 'single'
})
@@ -48,7 +48,7 @@ const TranslateEngineOptions = [
{value: 'youdao', label: '有道'},
]
let editArticle = $ref<Article>(cloneDeep(DefaultArticle))
let editArticle = $ref<Article>(getDefaultArticle())
watch(() => props.article, val => {
editArticle = cloneDeep(val)

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import {Article, DefaultArticle, Sentence, TranslateEngine} from "@/types.ts";
import {Article, getDefaultArticle, Sentence, TranslateEngine} from "@/types.ts";
import BaseButton from "@/components/BaseButton.vue";
import EditAbleText from "@/pages/pc/components/EditAbleText.vue";
import {Icon} from "@iconify/vue";
@@ -21,7 +21,7 @@ interface IProps {
}
const props = withDefaults(defineProps<IProps>(), {
article: () => cloneDeep(DefaultArticle),
article: () => getDefaultArticle(),
type: 'single'
})
@@ -39,13 +39,13 @@ const TranslateEngineOptions = [
{value: 'youdao', label: '有道'},
]
let editArticle = $ref<Article>(cloneDeep(DefaultArticle))
let editArticle = $ref<Article>(getDefaultArticle())
watch(() => props.article, val => {
editArticle = cloneDeep(val)
progress = 0
failCount = 0
apply()
apply(false)
}, {immediate: true})
watch(() => editArticle.text, (s) => {
@@ -54,7 +54,16 @@ watch(() => editArticle.text, (s) => {
}
})
function apply() {
function apply(isHandle: boolean = true) {
let text = editArticle.text.trim()
if (!text && isHandle) {
// text = "Last week I went to the theatre. I had a very good seat. The play was very interesting. I did not enjoy it. A young man and a young woman were sitting behind me. They were talking loudly. I got very angry. I could not hear the actors. I turned round. I looked at the man and the woman angrily. They did not pay any attention. In the end, I could not bear it. I turned round again. 'I can't hear a word!' I said angrily.\n\n 'It's none of your business,' the young man said rudely. 'This is a private conversation!'"
// text = `While it is yet to be seen what direction the second Trump administration will take globally in its China policy, VOA traveled to the main island of Mahe in Seychelles to look at how China and the U.S. have impacted the country, and how each is fairing in that competition for influence there.`
// text = "It was Sunday. I never get up early on Sundays. I sometimes stay in bed until lunchtime. Last Sunday I got up very late. I looked out of the window. It was dark outside. 'What a day!' I thought. 'It's raining again.' Just then, the telephone rang. It was my aunt Lucy. 'I've just arrived by train,' she said. 'I'm coming to see you.'\n\n 'But I'm still having breakfast,' I said.\n\n 'What are you doing?' she asked.\n\n 'I'm having breakfast,' I repeated.\n\n 'Dear me,' she said. 'Do you always get up so late? It's one o'clock!'"
editArticle.sections = []
ElMessage.error('请填写原文!')
return
}
failCount = genArticleSectionData(editArticle)
}
@@ -365,13 +374,13 @@ function setStartTime(val: Sentence, i: number, j: number) {
</template>
</el-popover>
<el-button type="primary" @click="splitTranslateText">分句</el-button>
<el-button type="primary" @click="apply">应用</el-button>
<el-button type="primary" @click="apply(true)">应用</el-button>
</div>
</div>
</div>
<div class="row flex flex-col gap-2">
<div class="title">结果</div>
<div class="center">正文译文与结果均可编辑修改一处另外两处会自动同步变动</div>
<div class="center">正文译文与结果均可编辑编辑后点击应用按钮会自动同步</div>
<div class="flex gap-2">
<BaseButton>添加音频</BaseButton>
<el-upload

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import {onMounted, onUnmounted} from "vue";
import {Article, DefaultArticle} from "@/types.ts";
import {Article, getDefaultArticle} from "@/types.ts";
import BaseButton from "@/components/BaseButton.vue";
import {cloneDeep} from "lodash-es";
import {useBaseStore} from "@/stores/base.ts";
@@ -23,7 +23,7 @@ const emit = defineEmits<{
const base = useBaseStore()
const runtimeStore = useRuntimeStore()
let article = $ref<Article>(cloneDeep(DefaultArticle))
let article = $ref<Article>(getDefaultArticle())
let show = $ref(false)
let editArticleRef: any = $ref()
let listEl: any = $ref()
@@ -97,7 +97,7 @@ function checkDataChange() {
async function add() {
let r = await checkDataChange()
if (r) {
article = cloneDeep(DefaultArticle)
article = getDefaultArticle()
}
}
@@ -153,7 +153,7 @@ useWindowClick(() => showExport = false)
ref="listEl"
v-model:list="runtimeStore.editDict.articles"
:select-item="article"
@del-select-item="article = cloneDeep(DefaultArticle)"
@del-select-item="article = getDefaultArticle()"
@select-item="selectArticle"
>
<template v-slot="{item,index}">

View File

@@ -1,7 +1,6 @@
<script setup lang="ts">
import {Article, DefaultArticle} from "@/types.ts";
import {cloneDeep} from "lodash-es";
import {Article, getDefaultArticle} from "@/types.ts";
import Dialog from "@/pages/pc/components/dialog/Dialog.vue";
import {useDisableEventListener} from "@/hooks/event.ts";
import EditArticle2 from "@/pages/pc/article/components/EditArticle2.vue";
@@ -12,7 +11,7 @@ interface IProps {
}
const props = withDefaults(defineProps<IProps>(), {
article: () => cloneDeep(DefaultArticle),
article: () => getDefaultArticle(),
modelValue: false
})
const emit = defineEmits<{

View File

@@ -1,11 +1,10 @@
<script setup lang="ts">
import {computed, nextTick, onMounted, onUnmounted, watch} from "vue"
import {Article, ArticleWord, DefaultArticle, Sentence, Word} from "@/types.ts";
import {computed, onMounted, onUnmounted, watch} from "vue"
import {Article, ArticleWord, getDefaultArticle, Sentence, Word} from "@/types.ts";
import {useBaseStore} from "@/stores/base.ts";
import {usePracticeStore} from "@/stores/practice.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePlayWordAudio} from "@/hooks/sound.ts";
import {cloneDeep} from "lodash-es";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import jq from 'jquery'
import {_nextTick} from "@/utils";
@@ -25,7 +24,7 @@ interface IProps {
}
const props = withDefaults(defineProps<IProps>(), {
article: () => cloneDeep(DefaultArticle),
article: () => getDefaultArticle(),
sectionIndex: 0,
sentenceIndex: 0,
wordIndex: 0,
@@ -455,7 +454,7 @@ let showQuestions = $ref(false)
<div class="options flex justify-center" v-if="isEnd">
<BaseButton
v-if="store.currentArticleDict.lastLearnIndex < store.currentArticleDict.articles.length - 1"
v-if="store.currentBook.lastLearnIndex < store.currentBook.articles.length - 1"
@click="emitter.emit(EventKey.next)">下一章
</BaseButton>
</div>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import TypingArticle from "./TypingArticle.vue";
import {Article, ArticleItem, ArticleWord, DefaultArticle, DisplayStatistics, ShortcutKey, Word} from "@/types.ts";
import {Article, ArticleItem, ArticleWord, DisplayStatistics, getDefaultArticle, ShortcutKey, Word} from "@/types.ts";
import {cloneDeep} from "lodash-es";
import TypingWord from "@/pages/pc/components/TypingWord.vue";
import Panel from "../../components/Panel.vue";
@@ -33,7 +33,7 @@ let wordData = $ref({
})
let articleData = $ref({
articles: [],
article: cloneDeep(DefaultArticle),
article: getDefaultArticle(),
sectionIndex: 0,
sentenceIndex: 0,
wordIndex: 0,
@@ -41,22 +41,22 @@ let articleData = $ref({
})
let showEditArticle = $ref(false)
let typingArticleRef = $ref<any>()
let editArticle = $ref<Article>(cloneDeep(DefaultArticle))
let editArticle = $ref<Article>(getDefaultArticle())
let articleIsActive = $computed(() => tabIndex === 0)
function next() {
if (!articleIsActive) return
if (store.currentArticleDict.lastLearnIndex >= articleData.articles.length - 1) {
store.currentArticleDict.lastLearnIndex = 0
} else store.currentArticleDict.lastLearnIndex++
if (store.currentBook.lastLearnIndex >= articleData.articles.length - 1) {
store.currentBook.lastLearnIndex = 0
} else store.currentBook.lastLearnIndex++
emitter.emit(EventKey.resetWord)
getCurrentPractice()
}
function init() {
if (!store.currentArticleDict.articles.length) return
articleData.articles = cloneDeep(store.currentArticleDict.articles)
if (!store.currentBook.articles.length) return
articleData.articles = cloneDeep(store.currentBook.articles)
getCurrentPractice()
console.log('inin', articleData.article)
@@ -64,7 +64,7 @@ function init() {
function setArticle(val: Article) {
let tempVal = cloneDeep(val)
articleData.articles[store.currentArticleDict.lastLearnIndex] = tempVal
articleData.articles[store.currentBook.lastLearnIndex] = tempVal
articleData.article = tempVal
statisticsStore.inputWordNumber = 0
statisticsStore.wrong = 0
@@ -82,13 +82,13 @@ function setArticle(val: Article) {
}
function getCurrentPractice() {
// console.log('store.currentArticleDict',store.currentArticleDict)
// console.log('store.currentBook',store.currentBook)
// return
tabIndex = 0
articleData.article = cloneDeep(DefaultArticle)
articleData.article = getDefaultArticle()
let currentArticle = articleData.articles[store.currentArticleDict.lastLearnIndex]
let tempArticle = {...DefaultArticle, ...currentArticle}
let currentArticle = articleData.articles[store.currentBook.lastLearnIndex]
let tempArticle = getDefaultArticle(currentArticle)
// console.log('article', tempArticle)
if (tempArticle.sections.length) {
setArticle(tempArticle)
@@ -102,9 +102,9 @@ function saveArticle(val: Article) {
console.log('saveArticle', val, JSON.stringify(val.lrcPosition))
console.log('saveArticle', val.textTranslate)
showEditArticle = false
let rIndex = store.currentArticleDict.articles.findIndex(v => v.id === val.id)
let rIndex = store.currentBook.articles.findIndex(v => v.id === val.id)
if (rIndex > -1) {
store.currentArticleDict.articles[rIndex] = cloneDeep(val)
store.currentBook.articles[rIndex] = cloneDeep(val)
}
setArticle(val)
}
@@ -163,7 +163,7 @@ function nextWord(word: ArticleWord) {
function handleChangeChapterIndex(val: ArticleItem) {
let rIndex = articleData.articles.findIndex(v => v.id === val.item.id)
if (rIndex > -1) {
store.currentArticleDict.lastLearnIndex = rIndex
store.currentBook.lastLearnIndex = rIndex
getCurrentPractice()
}
}
@@ -315,11 +315,11 @@ const {playSentenceAudio} = usePlaySentenceAudio()
@click="emitter.emit(EventKey.openDictModal,'list')"
icon="carbon:change-catalog"/>
<div class="title">
{{ store.currentArticleDict.name }}
{{ store.currentBook.name }}
</div>
<Tooltip
:title="`下一章(${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`"
v-if="store.currentArticleDict.lastLearnIndex < articleData.articles.length - 1">
v-if="store.currentBook.lastLearnIndex < articleData.articles.length - 1">
<IconWrapper>
<Icon @click="emitter.emit(EventKey.next)" icon="octicon:arrow-right-24"/>
</IconWrapper>
@@ -434,9 +434,6 @@ const {playSentenceAudio} = usePlaySentenceAudio()
icon="tabler:edit"
@click="emitter.emit(ShortcutKey.EditArticle)"
/>
<BaseIcon
@click="settingStore.showPanel = !settingStore.showPanel"
:title="`面板(${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`"
@@ -457,7 +454,6 @@ const {playSentenceAudio} = usePlaySentenceAudio()
<style scoped lang="scss">
.practice-wrapper {
font-size: 0.9rem;
width: 100%;

View File

@@ -1,222 +0,0 @@
<script setup lang="ts">
import { onMounted, onUnmounted } from "vue"
import { usePracticeStore } from "@/stores/practice.ts";
import { useSettingStore } from "@/stores/setting.ts";
import { Article, ArticleWord, ShortcutKey, Word } from "@/types.ts";
import { Icon } from "@iconify/vue";
import VolumeSetting from "@/pages/pc/components/toolbar/VolumeSetting.vue";
import TranslateSetting from "@/pages/pc/components/toolbar/TranslateSetting.vue";
import Tooltip from "@/pages/pc/components/Tooltip.vue";
import BaseIcon from "@/components/BaseIcon.vue";
import IconWrapper from "@/pages/pc/components/IconWrapper.vue";
import { useArticleOptions } from "@/hooks/dict.ts";
const statisticsStore = usePracticeStore()
const settingStore = useSettingStore()
const {
isArticleCollect,
toggleArticleCollect
} = useArticleOptions()
const emit = defineEmits<{
ignore: [],
wrong: [val: Word],
nextWord: [val: ArticleWord],
over: [],
edit: [val: Article]
}>()
function format(val: number, suffix: string = '', check: number = -1) {
return val === check ? '-' : (val + suffix)
}
const progress = $computed(() => {
if (!statisticsStore.total) return 0
if (statisticsStore.index > statisticsStore.total) return 100
return ((statisticsStore.index / statisticsStore.total) * 100)
})
let speedMinute = $ref(0)
let timer = $ref(0)
onMounted(() => {
timer = setInterval(() => {
speedMinute = Math.floor((Date.now() - statisticsStore.startDate) / 1000 / 60)
}, 1000)
})
onUnmounted(() => {
timer && clearInterval(timer)
})
</script>
<template>
<div class="footer " :class="!settingStore.showToolbar && 'hide'">
<div class="bottom ">
<div class="flex gap-2">
<el-progress
class="flex-1"
:percentage="progress"
:stroke-width="8"
:show-text="false" />
<el-progress
class="flex-1"
:percentage="progress"
:stroke-width="8"
:show-text="false" />
</div>
<div class="stat">
<div class="row">
<div class="num">{{ speedMinute }}分钟</div>
<div class="line"></div>
<div class="name">时间</div>
</div>
<div class="row">
<div class="num">{{ statisticsStore.total }}</div>
<div class="line"></div>
<div class="name">单词总数</div>
</div>
<div class="row">
<div class="num">{{ format(statisticsStore.inputWordNumber, '', 0) }}</div>
<div class="line"></div>
<div class="name">输入数</div>
</div>
<div class="row">
<div class="num">{{ format(statisticsStore.wrong, '', 0) }}</div>
<div class="line"></div>
<div class="name">错误数</div>
</div>
<div class="row">
<div class="num">{{ format(statisticsStore.correctRate, '%') }}</div>
<div class="line"></div>
<div class="name">正确率</div>
</div>
<div class="center flex-col">
<div class="text-xl">A private conversation!</div>
<div class="options-wrapper">
<div class="flex gap-1">
<Tooltip
:title="`开关默写模式(${settingStore.shortcutKeyMap[ShortcutKey.ToggleDictation]})`"
>
<IconWrapper>
<Icon icon="majesticons:eye-off-line"
v-if="settingStore.dictation"
@click="settingStore.dictation = false" />
<Icon icon="mdi:eye-outline"
v-else
@click="settingStore.dictation = true" />
</IconWrapper>
</Tooltip>
<TranslateSetting />
<VolumeSetting />
<BaseIcon
:title="`编辑(${settingStore.shortcutKeyMap[ShortcutKey.EditArticle]})`"
icon="tabler:edit"
@click="emit('edit',)"
/>
<BaseIcon
v-if="!isArticleCollect()"
class="collect"
@click="toggleArticleCollect()"
:title="`收藏(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
icon="ph:star" />
<BaseIcon
v-else
class="fill"
@click="toggleArticleCollect()"
:title="`取消收藏(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
icon="ph:star-fill" />
<BaseIcon
:title="`下一句(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
icon="icon-park-outline:go-ahead"
@click="emit('over')" />
<BaseIcon
@click="settingStore.showPanel = !settingStore.showPanel"
:title="`面板(${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`"
icon="tdesign:menu-unfold" />
</div>
</div>
</div>
</div>
</div>
<div class="progress">
<el-progress :percentage="progress"
:stroke-width="8"
:show-text="false" />
</div>
</div>
</template>
<style scoped lang="scss">
.footer {
width: var(--article-width);
margin-bottom: .8rem;
transition: all var(--anim-time);
position: relative;
margin-top: 1rem;
&.hide {
margin-bottom: -6rem;
margin-top: 3rem;
.progress {
bottom: calc(100% + 1.8rem);
}
}
.bottom {
position: relative;
width: 100%;
box-sizing: border-box;
border-radius: .6rem;
background: var(--color-second-bg);
padding: .2rem var(--space) .4rem var(--space);
z-index: 2;
border: 1px solid var(--color-item-border);
box-shadow: var(--shadow);
.stat {
margin-top: .5rem;
display: flex;
justify-content: space-around;
.row {
display: flex;
flex-direction: column;
align-items: center;
gap: .3rem;
width: 5rem;
color: gray;
.line {
height: 1px;
width: 100%;
background: var(--color-sub-gray);
}
}
}
}
.progress {
width: 100%;
transition: all .3s;
padding: 0 .6rem;
box-sizing: border-box;
position: absolute;
bottom: 0;
}
:deep(.el-progress-bar__inner) {
background: var(--color-scrollbar);
}
}
</style>

View File

@@ -1,38 +0,0 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts";
import "vue-activity-calendar/style.css";
import {useRouter} from "vue-router";
import BasePage from "@/pages/pc/components/BasePage.vue";
const base = useBaseStore()
const router = useRouter()
function clickEvent(e) {
console.log('e', e)
}
</script>
<template>
<BasePage>
<article class="">
<div class="text-align-center text-xl font-bold">One good turn deserves another</div>
<div>
I was having dinner at a restaurant when Tony Steele came in. Tony worked in a lawyer's office years ago, but he is now working at a bank. He gets a good salary, but he always borrows money from his friends and never pays it back. Tony saw me and came and sat at the same table. He has never borrowed money from me. While he was eating, I asked him to lend me twenty pounds. To my surprise, he gave me the money immediately. I have never borrowed any money from you, Tony said, so now you can pay for my dinner!
</div>
</article>
</BasePage>
</template>
<style scoped lang="scss">
.card {
@apply rounded-xl bg-white p-4 mt-5;
}
.center {
@apply flex justify-center items-center;
}
.title {
@apply text-lg font-medium;
}
</style>

View File

@@ -1,164 +0,0 @@
<script setup lang="ts">
import Toolbar from "@/pages/pc/components/toolbar/index.vue"
import { onMounted, onUnmounted, watch } from "vue";
import { usePracticeStore } from "@/stores/practice.ts";
import Footer from "@/pages/pc/word/Footer.vue";
import { useBaseStore } from "@/stores/base.ts";
import Statistics from "@/pages/pc/word/Statistics.vue";
import { emitter, EventKey } from "@/utils/eventBus.ts";
import { useSettingStore } from "@/stores/setting.ts";
import { useRuntimeStore } from "@/stores/runtime.ts";
import { MessageBox } from "@/utils/MessageBox.tsx";
import PracticeArticle from "@/pages/pc/practice/practice-article/index.vue";
import { ShortcutKey } from "@/types.ts";
import DictModal from "@/pages/pc/components/dialog/DictDiglog.vue";
import { useStartKeyboardEventListener } from "@/hooks/event.ts";
import useTheme from "@/hooks/theme.ts";
const statisticsStore = usePracticeStore()
const store = useBaseStore()
const settingStore = useSettingStore()
const runtimeStore = useRuntimeStore()
const { toggleTheme } = useTheme()
const practiceRef: any = $ref()
watch(statisticsStore, () => {
if (statisticsStore.inputWordNumber < 1) {
return statisticsStore.correctRate = -1
}
if (statisticsStore.wrong > statisticsStore.inputWordNumber) {
return statisticsStore.correctRate = 0
}
statisticsStore.correctRate = 100 - Math.trunc(((statisticsStore.wrong) / (statisticsStore.inputWordNumber)) * 100)
})
function test() {
MessageBox.confirm(
'2您选择了“本地翻译”但译文内容却为空白是否修改为“不需要翻译”并保存?',
'1提示',
() => {
console.log('ok')
},
() => {
console.log('cencal')
})
}
function write() {
// console.log('write')
settingStore.dictation = true
repeat()
}
//TODO 需要判断是否已忽略
function repeat() {
// console.log('repeat')
emitter.emit(EventKey.resetWord)
practiceRef.getCurrentPractice()
}
function prev() {
// console.log('next')
if (store.currentDict.chapterIndex === 0) {
ElMessage.warning('已经在第一章了~')
} else {
store.currentDict.chapterIndex--
repeat()
}
}
function toggleShowTranslate() {
settingStore.translate = !settingStore.translate
}
function toggleDictation() {
settingStore.dictation = !settingStore.dictation
}
function openSetting() {
runtimeStore.showSettingModal = true
}
function openDictDetail() {
emitter.emit(EventKey.openDictModal, 'detail')
}
function toggleConciseMode() {
settingStore.showToolbar = !settingStore.showToolbar
settingStore.showPanel = settingStore.showToolbar
}
function togglePanel() {
settingStore.showPanel = !settingStore.showPanel
}
function jumpSpecifiedChapter(val: number) {
store.currentDict.chapterIndex = val
repeat()
}
onMounted(() => {
emitter.on(EventKey.write, write)
emitter.on(EventKey.repeat, repeat)
emitter.on(EventKey.jumpSpecifiedChapter, jumpSpecifiedChapter)
emitter.on(ShortcutKey.PreviousChapter, prev)
emitter.on(ShortcutKey.RepeatChapter, repeat)
emitter.on(ShortcutKey.DictationChapter, write)
emitter.on(ShortcutKey.ToggleShowTranslate, toggleShowTranslate)
emitter.on(ShortcutKey.ToggleDictation, toggleDictation)
emitter.on(ShortcutKey.OpenSetting, openSetting)
emitter.on(ShortcutKey.OpenDictDetail, openDictDetail)
emitter.on(ShortcutKey.ToggleTheme, toggleTheme)
emitter.on(ShortcutKey.ToggleConciseMode, toggleConciseMode)
emitter.on(ShortcutKey.TogglePanel, togglePanel)
})
onUnmounted(() => {
emitter.off(EventKey.write, write)
emitter.off(EventKey.repeat, repeat)
emitter.off(EventKey.jumpSpecifiedChapter, jumpSpecifiedChapter)
emitter.off(ShortcutKey.PreviousChapter, prev)
emitter.off(ShortcutKey.RepeatChapter, repeat)
emitter.off(ShortcutKey.DictationChapter, write)
emitter.off(ShortcutKey.ToggleShowTranslate, toggleShowTranslate)
emitter.off(ShortcutKey.ToggleDictation, toggleDictation)
emitter.off(ShortcutKey.OpenSetting, openSetting)
emitter.off(ShortcutKey.OpenDictDetail, openDictDetail)
emitter.off(ShortcutKey.ToggleTheme, toggleTheme)
emitter.off(ShortcutKey.ToggleConciseMode, toggleConciseMode)
emitter.off(ShortcutKey.TogglePanel, togglePanel)
})
useStartKeyboardEventListener()
</script>
<template>
<div class="practice-wrapper">
<Toolbar />
<PracticeArticle ref="practiceRef" />
<Footer />
</div>
<DictModal />
<Statistics />
</template>
<style scoped lang="scss">
.practice-wrapper {
font-size: 0.9rem;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
//padding-right: var(--practice-wrapper-padding-right);
transform: translateX(var(--practice-wrapper-translateX));
}
</style>

View File

@@ -1,163 +0,0 @@
<script setup lang="ts">
import { onMounted, onUnmounted, watch } from "vue";
import { usePracticeStore } from "@/stores/practice.ts";
import { useBaseStore } from "@/stores/base.ts";
import Statistics from "@/pages/pc/word/Statistics.vue";
import { emitter, EventKey } from "@/utils/eventBus.ts";
import { useSettingStore } from "@/stores/setting.ts";
import { useRuntimeStore } from "@/stores/runtime.ts";
import { MessageBox } from "@/utils/MessageBox.tsx";
import PracticeArticle from "@/pages/pc/practice/practice-article/index.vue";
import { ShortcutKey } from "@/types.ts";
import DictModal from "@/pages/pc/components/dialog/DictDiglog.vue";
import { useStartKeyboardEventListener } from "@/hooks/event.ts";
import useTheme from "@/hooks/theme.ts";
import ArticleFooter from "@/pages/pc/article/ArticleFooter.vue";
const statisticsStore = usePracticeStore()
const store = useBaseStore()
const settingStore = useSettingStore()
const runtimeStore = useRuntimeStore()
const { toggleTheme } = useTheme()
const practiceRef: any = $ref()
watch(statisticsStore, () => {
if (statisticsStore.inputWordNumber < 1) {
return statisticsStore.correctRate = -1
}
if (statisticsStore.wrong > statisticsStore.inputWordNumber) {
return statisticsStore.correctRate = 0
}
statisticsStore.correctRate = 100 - Math.trunc(((statisticsStore.wrong) / (statisticsStore.inputWordNumber)) * 100)
})
function test() {
MessageBox.confirm(
'2您选择了“本地翻译”但译文内容却为空白是否修改为“不需要翻译”并保存?',
'1提示',
() => {
console.log('ok')
},
() => {
console.log('cencal')
})
}
function write() {
// console.log('write')
settingStore.dictation = true
repeat()
}
//TODO 需要判断是否已忽略
function repeat() {
// console.log('repeat')
emitter.emit(EventKey.resetWord)
practiceRef.getCurrentPractice()
}
function prev() {
// console.log('next')
if (store.currentDict.chapterIndex === 0) {
ElMessage.warning('已经在第一章了~')
} else {
store.currentDict.chapterIndex--
repeat()
}
}
function toggleShowTranslate() {
settingStore.translate = !settingStore.translate
}
function toggleDictation() {
settingStore.dictation = !settingStore.dictation
}
function openSetting() {
runtimeStore.showSettingModal = true
}
function openDictDetail() {
emitter.emit(EventKey.openDictModal, 'detail')
}
function toggleConciseMode() {
settingStore.showToolbar = !settingStore.showToolbar
settingStore.showPanel = settingStore.showToolbar
}
function togglePanel() {
settingStore.showPanel = !settingStore.showPanel
}
function jumpSpecifiedChapter(val: number) {
store.currentDict.chapterIndex = val
repeat()
}
onMounted(() => {
emitter.on(EventKey.write, write)
emitter.on(EventKey.repeat, repeat)
emitter.on(EventKey.jumpSpecifiedChapter, jumpSpecifiedChapter)
emitter.on(ShortcutKey.PreviousChapter, prev)
emitter.on(ShortcutKey.RepeatChapter, repeat)
emitter.on(ShortcutKey.DictationChapter, write)
emitter.on(ShortcutKey.ToggleShowTranslate, toggleShowTranslate)
emitter.on(ShortcutKey.ToggleDictation, toggleDictation)
emitter.on(ShortcutKey.OpenSetting, openSetting)
emitter.on(ShortcutKey.OpenDictDetail, openDictDetail)
emitter.on(ShortcutKey.ToggleTheme, toggleTheme)
emitter.on(ShortcutKey.ToggleConciseMode, toggleConciseMode)
emitter.on(ShortcutKey.TogglePanel, togglePanel)
})
onUnmounted(() => {
emitter.off(EventKey.write, write)
emitter.off(EventKey.repeat, repeat)
emitter.off(EventKey.jumpSpecifiedChapter, jumpSpecifiedChapter)
emitter.off(ShortcutKey.PreviousChapter, prev)
emitter.off(ShortcutKey.RepeatChapter, repeat)
emitter.off(ShortcutKey.DictationChapter, write)
emitter.off(ShortcutKey.ToggleShowTranslate, toggleShowTranslate)
emitter.off(ShortcutKey.ToggleDictation, toggleDictation)
emitter.off(ShortcutKey.OpenSetting, openSetting)
emitter.off(ShortcutKey.OpenDictDetail, openDictDetail)
emitter.off(ShortcutKey.ToggleTheme, toggleTheme)
emitter.off(ShortcutKey.ToggleConciseMode, toggleConciseMode)
emitter.off(ShortcutKey.TogglePanel, togglePanel)
})
useStartKeyboardEventListener()
</script>
<template>
<div class="practice-wrapper">
<PracticeArticle ref="practiceRef" />
<ArticleFooter />
</div>
<DictModal />
<Statistics />
</template>
<style scoped lang="scss">
.practice-wrapper {
font-size: 0.9rem;
width: 100%;
height: 100vh;
display: flex;
overflow: hidden;
flex-direction: column;
justify-content: space-between;
align-items: center;
//padding-right: var(--practice-wrapper-padding-right);
transform: translateX(var(--practice-wrapper-translateX));
}
</style>

View File

@@ -46,7 +46,7 @@ watch(() => settingStore.load, (n) => {
v-if="show">
<div class="notice">
坚持练习提高外语能力
<span class="active">Typing Word</span>
<span class="active">Type Words</span>
保存为书签永不迷失
</div>
<div class="wrapper">
@@ -190,4 +190,4 @@ watch(() => settingStore.load, (n) => {
}
}
</style>
</style>

View File

@@ -47,7 +47,7 @@ function toggle() {
/>
<div class="options">
<BaseButton @click="toggle">取消</BaseButton>
<BaseButton @click="save">保存</BaseButton>
<BaseButton @click="save">应用</BaseButton>
</div>
</div>
<div
@@ -76,4 +76,4 @@ function toggle() {
font-size: 1.2rem;
min-height: 1.1rem;
}
</style>
</style>

View File

@@ -85,7 +85,7 @@ function changeCollect() {
<div class="tab" :class="tabIndex === 1 && 'active'" @click="tabIndex = 1">收藏</div>
<div class="tab" :class="tabIndex === 2 && 'active'" @click="tabIndex = 2">{{ store.simple.name }}</div>
<div class="tab" :class="tabIndex === 3 && 'active'" @click="tabIndex = 3">{{ store.wrong.name }}</div>
<div class="tab" :class="tabIndex === 4 && 'active'" @click="tabIndex = 4">{{ store.master.name }}</div>
<div class="tab" :class="tabIndex === 4 && 'active'" @click="tabIndex = 4">{{ store.known.name }}</div>
</div>
<Tooltip
:title="`关闭(${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`"

View File

@@ -447,7 +447,7 @@ function importData(e) {
</div>
</div>
<div v-if="tabIndex === 5" class="about">
<h1>Typing Word</h1>
<h1>Type Words</h1>
<p>
本项目完全开源好用请大家多多点Star
</p>

View File

@@ -84,11 +84,11 @@ defineExpose({scrollToBottom, scrollToItem})
.search {
box-sizing: border-box;
width: 100%;
padding: 0 var(--space);
padding-right: var(--space);
}
.translate {
font-size: 1rem;
}
}
</style>
</style>

View File

@@ -5,7 +5,7 @@ import BaseButton from "@/components/BaseButton.vue";
import Empty from "@/components/Empty.vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {cloneDeep} from "lodash-es";
import {Article, DefaultArticle, Dict, DictResource, DictType, getDefaultDict, Sort, TranslateType} from "@/types.ts";
import {Article, Dict, DictResource, DictType, getDefaultArticle, getDefaultDict, Sort} from "@/types.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import EditBatchArticleModal from "@/pages/pc/article/components/EditBatchArticleModal.vue";
import {Icon} from "@iconify/vue";
@@ -19,7 +19,6 @@ import {MessageBox} from "@/utils/MessageBox.tsx";
import {syncMyDictList} from "@/hooks/dict.ts";
import {useWindowClick} from "@/hooks/event.ts";
import ArticleList from "@/pages/pc/components/list/ArticleList.vue";
import * as copy from "copy-to-clipboard";
import {getTranslateText} from "@/hooks/article.ts";
import {_copy} from "@/utils";
@@ -31,7 +30,7 @@ const store = useBaseStore()
const settingStore = useSettingStore()
const runtimeStore = useRuntimeStore()
let chapterIndex = $ref(-1)
let article: Article = $ref(cloneDeep(DefaultArticle))
let article: Article = $ref(getDefaultArticle())
let isEditDict = $ref(false)
let showExport = $ref(false)
let loading = $ref(false)
@@ -104,7 +103,7 @@ function delArticle(index: number) {
article = runtimeStore.editDict.articles[chapterIndex]
}
} else {
article = cloneDeep(DefaultArticle)
article = getDefaultArticle()
chapterIndex = -1
}
syncMyDictList(runtimeStore.editDict)
@@ -117,7 +116,7 @@ async function resetDict() {
'提示',
async () => {
chapterIndex = -1
article = cloneDeep(DefaultArticle)
article = getDefaultArticle()
if (runtimeStore.editDict.url) {
runtimeStore.editDict.sort = Sort.normal
runtimeStore.editDict.chapterWordNumber = settingStore.chapterWordNumber
@@ -150,15 +149,14 @@ function importData(e: any) {
if (res.length) {
let articles = res.map(v => {
if (v['原文标题'] && v['原文正文']) {
let article: Article = {
...DefaultArticle,
let article: Article = getDefaultArticle({
id: nanoid(6),
checked: false,
title: String(v['原文标题']),
text: String(v['原文正文']),
titleTranslate: String(v['译文标题']),
textTranslate: String(v['译文正文']),
}
})
return article
}
}).filter(v => v)
@@ -276,7 +274,7 @@ defineExpose({getDictDetail, add, editDict})
</div>
<template v-if="!isPinDict">
<BaseIcon icon="tabler:edit" @click='editDict'/>
<!-- <BaseIcon icon="ph:star" @click='no'/>-->
<!-- <BaseIcon icon="ph:star" @click='no'/>-->
<BaseButton size="small" v-if="runtimeStore.editDict.isCustom" @click="resetDict">恢复默认</BaseButton>
</template>
<div class="import hvr-grow">

View File

@@ -40,14 +40,14 @@ const {toggleTheme} = useTheme()
<Icon icon="ph:article-ny-times"/>
<span v-if="settingStore.sideExpand">文章</span>
</div>
<div class="row" @click="router.push('/article2')">
<Icon icon="healthicons:i-exam-multiple-choice-outline"/>
<span v-if="settingStore.sideExpand">试卷</span>
</div>
<div class="row">
<Icon icon="mdi-light:forum"/>
<span v-if="settingStore.sideExpand">社区</span>
</div>
<!-- <div class="row" @click="router.push('/article2')">-->
<!-- <Icon icon="healthicons:i-exam-multiple-choice-outline"/>-->
<!-- <span v-if="settingStore.sideExpand">试卷</span>-->
<!-- </div>-->
<!-- <div class="row">-->
<!-- <Icon icon="mdi-light:forum"/>-->
<!-- <span v-if="settingStore.sideExpand">社区</span>-->
<!-- </div>-->
</div>
<div class="bottom flex justify-evenly ">
<BaseIcon

View File

@@ -162,21 +162,9 @@ function changePerDayStudyNumber() {
我的词典
</div>
<div class="grid grid-cols-6 gap-4 mt-4">
<div class="book" @click="nav('edit-word-dict',{type:0})">
<span>收藏</span>
<div class="absolute bottom-4 right-4">{{ store.collectWord.words.length }}个词</div>
</div>
<div class="book" @click="nav('edit-word-dict',{type:1})">
<span>错词本</span>
<div class="absolute bottom-4 right-4">{{ store.wrong.words.length }}个词</div>
</div>
<div class="book" @click="nav('edit-word-dict',{type:2})">
<span>简单词</span>
<div class="absolute bottom-4 right-4">{{ store.simple.words.length }}个词</div>
</div>
<div class="book" @click="nav('edit-word-dict',{type:3})">
<span>已掌握</span>
<div class="absolute bottom-4 right-4">{{ store.master.words.length }}个词</div>
<div class="book" v-for="item in store.word.bookList" @click="nav('edit-word-dict',{type:item.type})">
<span>{{ item.name }}</span>
<div class="absolute bottom-4 right-4">{{ item.words.length }}个词</div>
</div>
</div>
</div>
@@ -238,7 +226,7 @@ function changePerDayStudyNumber() {
</template>
<style scoped lang="scss">
.target-modal {
.target-modal {
width: 30rem;
padding: var(--space);
padding-top: 0;

View File

@@ -1,25 +1,11 @@
import * as VueRouter from 'vue-router'
import {RouteRecordRaw} from 'vue-router'
import Mobile from '@/pages/mobile/index.vue'
import MobilePractice from '@/pages/mobile/practice/index.vue'
import Test from "@/pages/test/test.vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
import DictDetail from "@/pages/mobile/DictDetail.vue";
import SetDictPlan from "@/pages/mobile/SetDictPlan.vue";
import Setting from "@/pages/mobile/my/setting/Setting.vue";
import DataManage from "@/pages/mobile/my/DataManage.vue";
import CollectPage from "@/pages/mobile/my/CollectPage.vue";
import WrongPage from "@/pages/mobile/my/WrongPage.vue";
import SimplePage from "@/pages/mobile/my/SimplePage.vue";
import About from "@/pages/mobile/my/About.vue";
import Feedback from "@/pages/mobile/my/Feedback.vue";
import MusicSetting from "@/pages/mobile/my/setting/MusicSetting.vue";
import OtherSetting from "@/pages/mobile/my/setting/OtherSetting.vue";
import WordHome from "@/pages/pc/word/WordHome.vue";
import PC from "@/pages/pc/index.vue";
import Dict2 from '@/pages/pc/dict2/index.vue'
import ArticleIndex from "@/pages/pc/article/ArticleIndex.vue";
import Article2Index from "@/pages/pc/article2/ArticleIndex.vue";
import HomeIndex from "@/pages/pc/home/HomeIndex.vue";
import LearnArticle from "@/pages/pc/article/LearnArticle.vue";
import EditWordDict from "@/pages/pc/word/EditWordDict.vue";
@@ -39,7 +25,6 @@ export const routes: RouteRecordRaw[] = [
{path: 'edit-word-dict', component: EditWordDict},
{path: 'dict', component: Dict2},
{path: 'article', component: ArticleIndex},
{path: 'article2', component: Article2Index},
{path: 'edit-article', component: EditArticlePage},
{path: 'batch-edit-article', component: BatchEditArticlePage},
{path: 'learn-article', component: LearnArticle},

View File

@@ -16,22 +16,17 @@ export interface BaseState {
simpleWords: string[],
load: boolean
articleDictList?: Dict[]
commonDictList: any[],
wordDictList?: Dict[],
currentStudy?: {
word: {
dictIndex: number,
},
article: {
dictIndex: number,
}
},
// word: {
// studyIndex: number,
// dictList: [],
// },
word: {
studyIndex: number,
bookList: Dict[],
},
article: {
bookList: Dict[],
studyIndex: number,
@@ -39,194 +34,14 @@ export interface BaseState {
}
export const DefaultBaseState = (): BaseState => ({
commonDictList: [
getDefaultDict(),
{
...getDefaultDict(),
index: 1,
name: '收藏', type: DictType.collectWord, words: [
{
"id": "pharmacy",
"word": "pharmacy",
"trans": [
{
"cn": "n.药房,配药学,药学,制药业,一批备用药品"
}
],
"phonetic0": "ˈfɑ:məsi",
"phonetic1": "ˈfɑ:rməsi"
},
{
"id": "foregone",
"word": "foregone",
"trans": [
{
"cn": "过去的;先前的;预知的;预先决定的"
},
{
"cn": "发生在…之前forego的过去分词"
}
],
"phonetic0": "fɔː'gɒn",
"phonetic1": "'fɔrɡɔn"
},
{
"id": "calculate",
"word": "calculate",
"trans": [
{
"cn": "vt.& vi.计算,估计,打算,计划,旨在"
},
{
"cn": "vt.预测,推测"
}
],
"phonetic0": "ˈkælkjuleɪt",
"phonetic1": "ˈkælkjəˌlet"
},
{
"id": "compete",
"word": "compete",
"trans": [
{
"cn": "vi.竞赛,竞争,比得上,参加比赛(或竞赛)"
}
],
"phonetic0": "kəmˈpi:t",
"phonetic1": "kəmˈpit"
},
{
"id": "furnish",
"word": "furnish",
"trans": [
{
"cn": "vt.陈设,布置,提供,供应,装修(房屋)"
}
],
"phonetic0": "ˈfɜ:nɪʃ",
"phonetic1": "ˈfɜ:rnɪʃ"
},
], statistics: []
},
{
...getDefaultDict(),
index: 2, name: '收藏', type: DictType.collectArticle, articles: [], statistics: []
},
{
...getDefaultDict(),
index: 3, name: '简单词', type: DictType.simple, words: [], statistics: []
},
{
...getDefaultDict(),
index: 4, name: '错词', type: DictType.wrong, words: [], statistics: []
},
{
...getDefaultDict(),
index: 5, name: '已掌握', type: DictType.master, words: [], statistics: []
},
],
articleDictList: [
{
...getDefaultDict(),
id: 'article_nce2',
name: "新概念英语2-课文",
description: '新概念英语2-课文',
category: '英语学习',
tags: ['新概念英语'],
url: 'NCE_2.json',
translateLanguage: 'common',
language: 'en',
type: DictType.article,
resourceId: 'article_nce2',
length: 96,
lastLearnIndex: 1
},
],
wordDictList: [
{
...getDefaultDict(),
"id": 137,
"name": "新概念英语(新版)-2",
"description": "新概念英语新版第二册",
"length": 862,
"version": 1,
"fileName": "nce-new-2",
"category": "青少年英语",
"langType": 0,
"tranType": 1,
"userId": null,
"tags": [
"新概念英语"
],
"langTypeStr": "en",
"tranTypeStr": "zh",
"dictType": DictType.word,
statistics: []
},
],
commonDictList: [],
wordDictList: [],
currentStudy: {
word: {
dictIndex: 0,
},
article: {
dictIndex: 0,
},
},
myDictList: [
{
...getDefaultDict(),
id: 'collect',
name: '收藏',
type: DictType.collect,
category: '自带字典',
tags: ['自带'],
isCustom: true,
},
{
...getDefaultDict(),
id: 'skip',
name: '简单词',
type: DictType.simple,
category: '自带字典',
isCustom: true,
},
{
...getDefaultDict(),
id: 'wrong',
name: '错词本',
type: DictType.wrong,
category: '自带字典',
isCustom: true,
},
{
...getDefaultDict(),
id: 'cet4',
name: 'CET-4',
description: '大学英语四级词库',
category: '中国考试',
tags: ['大学英语'],
url: 'CET4_T.json',
length: 2607,
translateLanguage: 'common',
language: 'en',
type: DictType.word
},
{
...getDefaultDict(),
id: 'article_nce2',
name: "新概念英语2-课文",
description: '新概念英语2-课文',
category: '英语学习',
tags: ['新概念英语'],
url: 'NCE_2.json',
translateLanguage: 'common',
language: 'en',
type: DictType.article,
resourceId: 'article_nce2',
length: 96
},
],
myDictList: [],
current: {
index: 4,
practiceType: DictType.article,
@@ -239,6 +54,96 @@ export const DefaultBaseState = (): BaseState => ({
'the', 'that', 'this', 'to', 'of', 'for', 'and', 'at', 'not', 'no', 'yes',
],
load: false,
word: {
bookList: [
getDefaultDict({
index: 1,
name: '收藏', type: DictType.collectWord, words: [
{
"id": "pharmacy",
"word": "pharmacy",
"trans": [
{
"cn": "n.药房,配药学,药学,制药业,一批备用药品"
}
],
"phonetic0": "ˈfɑ:məsi",
"phonetic1": "ˈfɑ:rməsi"
},
{
"id": "foregone",
"word": "foregone",
"trans": [
{
"cn": "过去的;先前的;预知的;预先决定的"
},
{
"cn": "发生在…之前forego的过去分词"
}
],
"phonetic0": "fɔː'gɒn",
"phonetic1": "'fɔrɡɔn"
},
{
"id": "calculate",
"word": "calculate",
"trans": [
{
"cn": "vt.& vi.计算,估计,打算,计划,旨在"
},
{
"cn": "vt.预测,推测"
}
],
"phonetic0": "ˈkælkjuleɪt",
"phonetic1": "ˈkælkjəˌlet"
},
{
"id": "compete",
"word": "compete",
"trans": [
{
"cn": "vi.竞赛,竞争,比得上,参加比赛(或竞赛)"
}
],
"phonetic0": "kəmˈpi:t",
"phonetic1": "kəmˈpit"
},
{
"id": "furnish",
"word": "furnish",
"trans": [
{
"cn": "vt.陈设,布置,提供,供应,装修(房屋)"
}
],
"phonetic0": "ˈfɜ:nɪʃ",
"phonetic1": "ˈfɜ:rnɪʃ"
},
], statistics: []
}),
getDefaultDict({
index: 2, name: '错词', type: DictType.wrong, words: [], statistics: []
}),
getDefaultDict({
index: 3, name: '已掌握', type: DictType.known, words: [], statistics: []
}),
getDefaultDict({
id: 'nce-new-2',
name: '新概念英语(新版)-2',
description: '新概念英语新版第二册',
category: '青少年英语',
tags: ['新概念英语'],
url: 'nce-new-2.json',
length: 862,
translateLanguage: 'common',
language: 'en',
type: DictType.word
}),
],
studyIndex: 3,
},
article: {
bookList: [
getDefaultDict({name: '收藏'})
@@ -253,22 +158,22 @@ export const useBaseStore = defineStore('base', {
},
getters: {
collect(): Dict {
return this.myDictList[0]
return this.word.bookList[0]
},
collectWord(): Dict {
return this.commonDictList[1]
return this.word.bookList[1]
},
collectArticle(): Dict {
return this.commonDictList[2]
return this.word.bookList[2]
},
simple(): Dict {
return this.commonDictList[3]
return this.word.bookList[2]
},
wrong(): Dict {
return this.commonDictList[4]
return this.word.bookList[1]
},
master(): Dict {
return this.commonDictList[5]
known(): Dict {
return this.word.bookList[2]
},
skipWordNames() {
return this.simple.words.map(v => v.word.toLowerCase())
@@ -289,10 +194,10 @@ export const useBaseStore = defineStore('base', {
return this.myDictList[this.current.index] ?? {}
},
currentStudyWordDict(): Dict {
if (this.sword.dictIndex >= 0) {
return this.wordDictList[this.currentStudy.word.dictIndex] ?? getDefaultDict()
if (this.word.bookList.studyIndex >= 0) {
return this.word.bookList[this.word.bookList.studyIndex] ?? getDefaultDict()
}
return this.commonDictList[Math.abs(this.currentStudy.word.dictIndex) - 1] ?? getDefaultDict()
return getDefaultDict()
},
sdict(): Dict {
return this.currentStudyWordDict
@@ -310,16 +215,20 @@ export const useBaseStore = defineStore('base', {
currentArticleCollectDict(): Dict {
return this.article.bookList[0]
},
currentArticleDict(): Dict {
return this.article.bookList[this.article.studyIndex] ?? {}
},
chapter(state: BaseState): Word[] {
return this.currentDict.chapterWords[this.currentDict.chapterIndex] ?? []
},
currentBook(): Dict {
return this.article.bookList[this.article.studyIndex] ?? {}
},
currentBookProgress(): number {
if (this.currentBook.name) return Number(Number(this.currentBook.lastLearnIndex / this.currentBook.length).toFixed(2))
return 0
},
},
actions: {
setState(obj: any) {
return
//这样不会丢失watch的值的引用
merge(this, obj)
},
@@ -338,12 +247,16 @@ export const useBaseStore = defineStore('base', {
console.error('读取本地dict数据失败', e)
}
if (this.currentStudy.word.dictIndex >= 0) {
if (this.word.studyIndex >= 0) {
// await _checkDictWords(this.currentStudyWordDict)
// console.log('this.wordDictList', this.wordDictList[0].words[0])
let current = this.word.bookList[this.word.studyIndex]
let dictResourceUrl = `./dicts/${current.language}/${current.type}/${current.translateLanguage}/${current.url}`;
current.words = await getDictFile(dictResourceUrl)
console.log('this.current', current)
}
if (this.currentStudy.article.dictIndex >= 0) {
let current = this.articleDictList[this.currentStudy.article.dictIndex]
if (this.article.studyIndex >= 0) {
let current = this.article.bookList[this.article.studyIndex]
let dictResourceUrl = `./dicts/${current.language}/${current.type}/${current.translateLanguage}/${current.url}`;
if (!current.articles.length) {
let s = await getDictFile(dictResourceUrl)
@@ -352,7 +265,7 @@ export const useBaseStore = defineStore('base', {
return v
}))
}
console.log('this.currentArticleDict', this.currentArticleDict.articles[0])
// console.log('this.currentBook', this.currentBook.articles[0])
}
emitter.emit(EventKey.changeDict)
resolve(true)
@@ -371,7 +284,7 @@ export const useBaseStore = defineStore('base', {
this.wordDictList.push(getDefaultDict(dict))
this.currentStudy.word.dictIndex = this.wordDictList.length - 1
}
await _checkDictWords(this.currentStudyWordDict)
// await _checkDictWords(this.currentStudyWordDict)
console.log(' store.currentStudyWordDict', this.currentStudyWordDict)
emitter.emit(EventKey.changeDict)
@@ -444,6 +357,5 @@ export const useBaseStore = defineStore('base', {
this.currentStudy.word.dictIndex = rIndex
}
},
},
})

View File

@@ -23,7 +23,6 @@ export type Word = {
memory?: string,
}
export function getDefaultWord(val?: any): Word {
return {
id: '',
@@ -68,9 +67,8 @@ export enum DictType {
collect = 'collect',
simple = 'simple',
wrong = 'wrong',
master = 'master',
known = 'known',
collectWord = 'collect-word',
collectArticle = 'collect-article',
word = 'word',
article = 'article',
}
@@ -98,12 +96,6 @@ export interface Sentence {
audioPosition: number[]
}
export enum TranslateType {
custom = 'custom',
network = 'network',
none = 'none'
}
export interface Article {
id: string,
title: string,
@@ -123,19 +115,21 @@ export interface Article {
}[]
}
export const DefaultArticle: Article = {
// id: nanoid(6),
id: '',
title: '',
titleTranslate: '',
text: '',
textTranslate: '',
newWords: [],
textAllWords: [],
sections: [],
audioSrc: '',
lrcPosition: [],
questions: [],
export function getDefaultArticle(val: Partial<Article> = {}): Article {
return {
id: '',
title: '',
titleTranslate: '',
text: '',
textTranslate: '',
newWords: [],
textAllWords: [],
sections: [],
audioSrc: '',
lrcPosition: [],
questions: [],
...val
}
}
export interface Statistics {

View File

@@ -6,7 +6,7 @@ export const SoundFileOptions = [
{value: '笔记本键盘', label: '笔记本键盘'},
]
export const APP_NAME = 'Typing Word'
export const APP_NAME = 'Type Words'
export const SAVE_DICT_KEY = {
key: 'typing-word-dict',
@@ -20,4 +20,4 @@ export const SAVE_SETTING_KEY = {
export const EXPORT_DATA_KEY = {
key: 'typing-word-export',
version: 1
}
}

View File

@@ -120,9 +120,6 @@ export function shakeCommonDict(n: BaseState): BaseState {
data.wordDictList.map((v: Dict) => {
if (!v.isCustom) v.words = []
})
data.articleDictList.map((v: Dict) => {
if (!v.isCustom) v.articles = []
})
return data
}
@@ -133,6 +130,7 @@ export function isMobile(): boolean {
export function getDictFile(url: string) {
return new Promise<any[]>(async resolve => {
let r = await fetch(url).catch(r => {
console.log('getDictFile_error',r)
})
let v = await r.json()
resolve(v)
@@ -177,10 +175,9 @@ export function _fetch(url: string) {
export async function _checkDictWords(dict: Dict) {
console.log('_checkDictWords', dict)
if ([DictType.collect,
DictType.simple,
DictType.known,
DictType.wrong].includes(dict.dictType)) {
} else {
//TODO 需要和其他需要下载的地方统一
//如果不是自定义词典并且有url地址才去下载
if (!dict.isCustom && dict.fileName) {
@@ -201,7 +198,7 @@ export async function _checkDictWords(dict: Dict) {
if (res && res.request.responseURL !== url) {
r = res.data
} else {
let dictLocalUrl = `./dicts/${dict.langTypeStr}/${dict.dictType}/${dict.tranTypeStr}/${dict.fileName}-v${dict.version}.json`;
let dictLocalUrl = `./dicts/${dict.language}/${dict.type}/${dict.translateLanguage}/${dict.url}`;
let r3 = await fetch(dictLocalUrl)
try {
r = await r3.json()
@@ -286,4 +283,4 @@ export function _parseLRC(lrc: string): { start: number, end: number, text: stri
}
return parsed;
}
}