feat: save

This commit is contained in:
zyronon
2024-07-21 02:18:22 +08:00
parent 6cf077bf2f
commit 7ce9fdd682
10 changed files with 263 additions and 99 deletions

View File

@@ -0,0 +1,223 @@
<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">
@import "@/assets/css/variable";
.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,9 +1,7 @@
<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";
@@ -16,6 +14,7 @@ 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()
@@ -140,7 +139,7 @@ useStartKeyboardEventListener()
<template>
<div class="practice-wrapper">
<PracticeArticle ref="practiceRef"/>
<Footer/>
<ArticleFooter/>
</div>
<DictModal/>
<Statistics/>
@@ -150,8 +149,9 @@ useStartKeyboardEventListener()
.practice-wrapper {
font-size: 0.9rem;
width: 100%;
height: 100%;
height: 100vh;
display: flex;
overflow: hidden;
flex-direction: column;
justify-content: space-between;
align-items: center;

View File

@@ -35,7 +35,7 @@ const playWordAudio = usePlayWordAudio()
<VolumeIcon class="volume" @click="playWordAudio(item.word)"></VolumeIcon>
</div>
<div class="item-sub-title" v-if="item.trans.length && showTranslate">
<div v-for="v in item.trans">{{ (v.pos ? v.pos + '.' : '') + (v.cn || v.en) }}</div>
<div v-for="v in item.trans">{{ (v.pos ? v.pos + '.' : '') + (v.tran) }}</div>
</div>
</div>
</div>

View File

@@ -158,7 +158,7 @@ defineExpose({del, showWord, hideWord, play})
}"
>
<div class="translate-item" v-for="(v,i) in word.trans">
<span>{{ (v.pos ? v.pos + '.' : '') + (v.cn || v.en) }}</span>
<span>{{ (v.pos ? v.pos + '.' : '') + (v.tran ) }}</span>
<!-- <div class="volumeIcon">-->
<!-- <Tooltip-->
<!-- v-if="i === word.trans.length - 1"-->

View File

@@ -33,7 +33,7 @@ const playWordAudio = usePlayWordAudio()
<VolumeIcon class="volume" @click="playWordAudio(item.word)"></VolumeIcon>
</div>
<div class="item-sub-title" v-if="item.trans.length && showTranslate">
<div v-for="v in item.trans">{{ (v.pos ? v.pos + '.' : '') + (v.cn || v.en) }}</div>
<div v-for="v in item.trans">{{ (v.pos ? v.pos + '.' : '') + (v.tran ) }}</div>
</div>
</div>
</div>

View File

@@ -52,7 +52,7 @@ defineExpose({scrollToBottom, scrollToItem})
<VolumeIcon class="volume" @click="playWordAudio(item.word)"></VolumeIcon>
</div>
<div class="item-sub-title" v-if="item.trans.length && showTranslate">
<div v-for="v in item.trans">{{ (v.pos ? v.pos + '.' : '') + (v.cn || v.en)}}</div>
<div v-for="v in item.trans">{{ (v.pos ? v.pos + '.' : '') + (v.tran) }}</div>
</div>
</template>
<template v-slot:suffix="{ item, index }">

View File

@@ -308,11 +308,6 @@ function otherWord(word: ArticleWord, i: number, i2: number, i3: number) {
return str
}
const {
isArticleCollect,
toggleArticleCollect
} = useArticleOptions()
function showSentence(i1: number = sectionIndex, i2: number = sentenceIndex) {
hoverIndex = {sectionIndex: i1, sentenceIndex: i2}
@@ -341,57 +336,9 @@ defineExpose({showSentence, play, del,hideSentence,nextSentence})
<template>
<div class="typing-article" ref="typeArticleRef">
<header>
<header class="mb-4">
<div class="title word">{{ props.article.title }}</div>
<div class="titleTranslate" v-if="settingStore.translate">{{ props.article.titleTranslate }}</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',props.article)"
/>
<BaseIcon
v-if="!isArticleCollect(props.article)"
class="collect"
@click="toggleArticleCollect(props.article)"
:title="`收藏(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
icon="ph:star"/>
<BaseIcon
v-else
class="fill"
@click="toggleArticleCollect(props.article)"
: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>
</header>
<div class="article-content" ref="articleWrapperRef">
<article>
@@ -493,14 +440,6 @@ $article-width: 1000px;
font-size: 1.2rem;
font-family: unset;
}
.options-wrapper {
display: flex;
justify-content: flex-end;
gap: .6rem;
font-size: 1.1rem;
margin-bottom: .3rem;
}
}
.article-content {

View File

@@ -54,9 +54,9 @@ let articleIsActive = $computed(() => tabIndex === 0)
function next() {
if (!articleIsActive) return
if (store.currentArticleDict.chapterIndex >= articleData.articles.length - 1) {
store.currentArticleDict.chapterIndex = 0
} else store.currentArticleDict.chapterIndex++
if (store.currentArticleDict.lastLearnIndex >= articleData.articles.length - 1) {
store.currentArticleDict.lastLearnIndex = 0
} else store.currentArticleDict.lastLearnIndex++
emitter.emit(EventKey.resetWord)
getCurrentPractice()
@@ -72,7 +72,7 @@ function init() {
function setArticle(val: Article) {
let tempVal = cloneDeep(val)
articleData.articles[store.currentArticleDict.chapterIndex] = tempVal
articleData.articles[store.currentArticleDict.lastLearnIndex] = tempVal
articleData.article = tempVal
statisticsStore.inputWordNumber = 0
statisticsStore.wrong = 0
@@ -95,7 +95,7 @@ function getCurrentPractice() {
tabIndex = 0
articleData.article = cloneDeep(DefaultArticle)
let currentArticle = articleData.articles[store.currentArticleDict.chapterIndex]
let currentArticle = articleData.articles[store.currentArticleDict.lastLearnIndex]
let tempArticle = {...DefaultArticle, ...currentArticle}
// console.log('article', tempArticle)
if (tempArticle.sections.length) {
@@ -224,7 +224,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.chapterIndex = rIndex
store.currentArticleDict.lastLearnIndex = rIndex
getCurrentPractice()
}
}
@@ -345,7 +345,7 @@ defineExpose({getCurrentPractice})
</div>
<Tooltip
:title="`下一章(${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`"
v-if="store.currentArticleDict.chapterIndex < articleData.articles .length - 1">
v-if="store.currentArticleDict.lastLearnIndex < articleData.articles.length - 1">
<IconWrapper>
<Icon @click="emitter.emit(EventKey.next)" icon="octicon:arrow-right-24"/>
</IconWrapper>

View File

@@ -26,7 +26,6 @@ export interface BaseState {
},
article: {
dictIndex: number,
lastLearnIndex: number,
}
}
}
@@ -164,7 +163,6 @@ export const DefaultBaseState = (): BaseState => ({
},
article: {
dictIndex: 0,
lastLearnIndex: 0,
},
},
@@ -263,7 +261,7 @@ export const useBaseStore = defineStore('base', {
return this.simple.words.map(v => v.word.toLowerCase())
},
skipWordNamesWithSimpleWords() {
return this.simple.originWords.map(v => v.word.toLowerCase()).concat(this.simpleWords)
return this.simple.words.map(v => v.word.toLowerCase()).concat(this.simpleWords)
},
isArticle(state: BaseState): boolean {
//如果是收藏时,特殊判断
@@ -325,18 +323,22 @@ export const useBaseStore = defineStore('base', {
}
if (this.currentStudy.word.dictIndex >= 0) {
await _checkDictWords(this.currentStudyWordDict)
// let current = this.articleDictList[this.currentStudy.article.dictIndex]
// let dictResourceUrl = `./dicts/${current.language}/${current.type}/${current.translateLanguage}/${current.url}`;
// if (!current.articles.length) {
// let s = await getDictFile(dictResourceUrl)
// current.articles = cloneDeep(s.map(v => {
// v.id = nanoid(6)
// return v
// }))
// }
// await _checkDictWords(this.currentStudyWordDict)
// console.log('this.wordDictList', this.wordDictList[0].words[0])
}
if (this.currentStudy.article.dictIndex >= 0) {
let current = this.articleDictList[this.currentStudy.article.dictIndex]
let dictResourceUrl = `./dicts/${current.language}/${current.type}/${current.translateLanguage}/${current.url}`;
if (!current.articles.length) {
let s = await getDictFile(dictResourceUrl)
current.articles = cloneDeep(s.map(v => {
v.id = nanoid(6)
return v
}))
}
console.log('this.currentArticleDict', this.currentArticleDict.articles[0])
}
emitter.emit(EventKey.changeDict)
resolve(true)
})

View File

@@ -133,12 +133,8 @@ export function getDictFile(url: string) {
return new Promise<any[]>(async resolve => {
let r = await fetch(url).catch(r => {
})
if (url.includes('.7z')) {
} else {
let v = await r.json()
resolve(v)
}
let v = await r.json()
resolve(v)
})
}
@@ -191,12 +187,17 @@ export async function _checkDictWords(dict: Dict) {
// console.log('r', rrr)
// return
let url = `http://localhost/index.php/v1/support/getDictFile?id=${dict.id}&v=${dict.version}`
let res: any = await axios(url)
// let res: any = await axios(`http://localhost/index.php/v1/support/getDictFile?id=2`)
let res: any
try {
res = await axios(url)
} catch (err) {
console.log('err', err)
}
console.log('res', res)
//说明重定向了
let r
if (res.request.responseURL !== url) {
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`;
@@ -204,7 +205,6 @@ export async function _checkDictWords(dict: Dict) {
try {
r = await r3.json()
} catch (e) {
}
console.log('r', r)
}