save
This commit is contained in:
@@ -1,5 +1,18 @@
|
||||
[
|
||||
[
|
||||
{
|
||||
"id": "article_nce1",
|
||||
"name": "新概念英语1-课文",
|
||||
"description": "",
|
||||
"category": "文章学习",
|
||||
"tags": [
|
||||
"新概念英语"
|
||||
],
|
||||
"url": "NCE_1.json",
|
||||
"length": 72,
|
||||
"translateLanguage": "common",
|
||||
"language": "en"
|
||||
},
|
||||
{
|
||||
"id": "article_nce2",
|
||||
"name": "新概念英语2-课文",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
--color-sub-gray: #c0bfbf;
|
||||
|
||||
--article-width: 50vw;
|
||||
--article-toolbar-width: 50rem;
|
||||
--article-toolbar-width: 50vw;
|
||||
--article-panel-width: 20rem;
|
||||
--article-panel-margin-left: calc(50% + var(--article-width) / 2 + 1rem);
|
||||
|
||||
@@ -129,7 +129,7 @@ html.dark {
|
||||
--panel-width: 20vw;
|
||||
--space: 0.5rem;
|
||||
|
||||
--article-toolbar-width: 45rem;
|
||||
--article-toolbar-width: 50rem;
|
||||
--article-panel-width: 18rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import BaseButton from "@/components/BaseButton.vue";
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
import EditBook from "@/pages/pc/article/components/EditBook.vue";
|
||||
import {computed, onMounted} from "vue";
|
||||
import {_getDictDataByUrl, cloneDeep, useNav} from "@/utils";
|
||||
import {_dateFormat, _getDictDataByUrl, cloneDeep, msToHourMinute, total, useNav} from "@/utils";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import {useArticleOptions} from "@/hooks/dict.ts";
|
||||
import {getDefaultArticle, getDefaultDict} from "@/types/func.ts";
|
||||
@@ -102,7 +102,6 @@ function reset() {
|
||||
'继续此操作会重置所有文章,并从官方书籍获取最新文章列表,学习记录不会被重置。确认恢复默认吗?',
|
||||
'恢复默认',
|
||||
async () => {
|
||||
debugger
|
||||
let dict = book_list.flat().find(v => v.url === runtimeStore.editDict.url) as Dict
|
||||
if (dict && dict.id) {
|
||||
dict = await _getDictDataByUrl(dict, DictType.article)
|
||||
@@ -124,6 +123,21 @@ function reset() {
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const currentPractice = $computed(() => {
|
||||
if (runtimeStore.editDict.statistics?.length) {
|
||||
return runtimeStore.editDict.statistics.filter(v => v.title === selectArticle.title)
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
const totalSpend = $computed(() => {
|
||||
if (runtimeStore.editDict.statistics?.length) {
|
||||
return msToHourMinute(total(runtimeStore.editDict.statistics,'spend'))
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -142,6 +156,8 @@ function reset() {
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-lg ">介绍:{{ runtimeStore.editDict.description }}</div>
|
||||
<div class="text-base " v-if="totalSpend">总学习时长:{{ totalSpend }}</div>
|
||||
|
||||
<div class="line my-3"></div>
|
||||
|
||||
<div class="flex flex-1 overflow-hidden">
|
||||
@@ -166,6 +182,12 @@ function reset() {
|
||||
</div>
|
||||
<div class="right flex-[4] shrink-0 pl-4 overflow-auto">
|
||||
<div v-if="selectArticle.id">
|
||||
<div class="font-family text-base mb-4" v-if="currentPractice.length">
|
||||
<div>学习记录:{{ msToHourMinute(total(currentPractice, 'spend'))}}</div>
|
||||
<div class="item" v-for="i in currentPractice">
|
||||
{{_dateFormat(i.startDate,'YYYY-MM-DD HH-mm')}}: {{ msToHourMinute(i.spend) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="en-article-family title text-xl">
|
||||
<div class="text-center text-2xl my-2">
|
||||
<ArticleAudio :article="selectArticle"></ArticleAudio>
|
||||
|
||||
@@ -8,7 +8,7 @@ import {Article, ArticleItem, ArticleWord, Dict, DictType, ShortcutKey, Statisti
|
||||
import {useDisableEventListener, useOnKeyboardEventListener, useStartKeyboardEventListener} from "@/hooks/event.ts";
|
||||
import useTheme from "@/hooks/theme.ts";
|
||||
import Toast from '@/pages/pc/components/base/toast/Toast.ts'
|
||||
import {_getDictDataByUrl, cloneDeep} from "@/utils";
|
||||
import {_getDictDataByUrl, cloneDeep, msToHourMinute, msToMinute, total} from "@/utils";
|
||||
import {usePracticeStore} from "@/stores/practice.ts";
|
||||
import {useArticleOptions} from "@/hooks/dict.ts";
|
||||
import {genArticleSectionData, usePlaySentenceAudio} from "@/hooks/article.ts";
|
||||
@@ -176,6 +176,10 @@ function complete() {
|
||||
window.umami?.track('studyWordArticle', reportData)
|
||||
store.sbook.statistics.push(data as any)
|
||||
console.log(data, reportData)
|
||||
|
||||
//重置
|
||||
statStore.wrong = 0
|
||||
statStore.startDate = Date.now()
|
||||
}
|
||||
|
||||
function getCurrentPractice() {
|
||||
@@ -308,6 +312,12 @@ function play2(e) {
|
||||
}
|
||||
}
|
||||
|
||||
const currentPractice = $computed(() => {
|
||||
if (store.sbook.statistics?.length) {
|
||||
return store.sbook.statistics.filter(v => v.title === articleData.article.title)
|
||||
}
|
||||
return []
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<PracticeLayout
|
||||
@@ -334,7 +344,7 @@ function play2(e) {
|
||||
</template>
|
||||
<div class="panel-page-item pl-4">
|
||||
<ArticleList
|
||||
:isActive="true"
|
||||
:isActive="settingStore.showPanel"
|
||||
:static="false"
|
||||
:show-translate="settingStore.translate"
|
||||
@click="changeArticle"
|
||||
@@ -365,6 +375,11 @@ function play2(e) {
|
||||
<div class="bottom">
|
||||
<div class="flex justify-between items-center gap-2">
|
||||
<div class="stat">
|
||||
<div class="row">
|
||||
<div class="num">{{ currentPractice.length }}次/{{ msToMinute(total(currentPractice, 'spend'))}}</div>
|
||||
<div class="line"></div>
|
||||
<div class="name">记录</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="num">{{ speedMinute }}分钟</div>
|
||||
<div class="line"></div>
|
||||
@@ -473,7 +488,7 @@ function play2(e) {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: .3rem;
|
||||
width: 5rem;
|
||||
width: 6rem;
|
||||
color: gray;
|
||||
|
||||
.line {
|
||||
|
||||
@@ -343,17 +343,33 @@ function onTyping(e: KeyboardEvent) {
|
||||
}
|
||||
|
||||
// 检查是否是句子的最后一个单词
|
||||
const isLastWordInSentence = wordIndex + 1 >= currentSentence.words.length;
|
||||
// const isLastWordInSentence = wordIndex + 1 >= currentSentence.words.length;
|
||||
// if (isLastWordInSentence) {
|
||||
// // 如果是句子的最后一个单词,自动跳转到下一句,不用再输入空格
|
||||
// nextSentence();
|
||||
// } else if (currentWord.nextSpace) {
|
||||
// // 如果不是最后一个单词,且需要空格,设置等待空格输入
|
||||
// isSpace = true;
|
||||
// } else {
|
||||
// // 如果不需要空格,直接移动到下一个单词
|
||||
// nextWord();
|
||||
// }
|
||||
|
||||
if (isLastWordInSentence) {
|
||||
// 如果是句子的最后一个单词,自动跳转到下一句,不用再输入空格
|
||||
nextSentence();
|
||||
} else if (currentWord.nextSpace) {
|
||||
// 如果不是最后一个单词,且需要空格,设置等待空格输入
|
||||
isSpace = true;
|
||||
//换句不打空格不符合习惯
|
||||
if (currentWord.nextSpace) {
|
||||
if (
|
||||
sectionIndex === props.article.sections.length - 1 &&
|
||||
sentenceIndex === currentSection.length - 1 &&
|
||||
wordIndex === currentSentence.words.length - 1
|
||||
) {
|
||||
console.log('打完了')
|
||||
isEnd = true
|
||||
emit('complete')
|
||||
} else {
|
||||
isSpace = true
|
||||
}
|
||||
} else {
|
||||
// 如果不需要空格,直接移动到下一个单词
|
||||
nextWord();
|
||||
nextWord()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import Input from "@/pages/pc/components/Input.vue";
|
||||
import {Article} from "@/types/types.ts";
|
||||
import BaseList from "@/pages/pc/components/list/BaseList.vue";
|
||||
import * as sea from "node:sea";
|
||||
import {watch, watchEffect} from "vue";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
list: Article[],
|
||||
@@ -20,12 +22,31 @@ const emit = defineEmits<{
|
||||
let searchKey = $ref('')
|
||||
let localList = $computed(() => {
|
||||
if (searchKey) {
|
||||
return props.list.filter((item: Article) => {
|
||||
//把搜索内容,分词之后,判断是否有这个词,比单纯遍历包含体验更好
|
||||
return searchKey.toLowerCase().split(' ').filter(v => v).some(value => {
|
||||
//把搜索内容,分词之后,判断是否有这个词,比单纯遍历包含体验更好
|
||||
let t = searchKey.toLowerCase()
|
||||
let strings = t.split(' ').filter(v => v);
|
||||
let res = props.list.filter((item: Article) => {
|
||||
return strings.some(value => {
|
||||
return item.title.toLowerCase().includes(value) || item.titleTranslate.toLowerCase().includes(value)
|
||||
})
|
||||
})
|
||||
try {
|
||||
let d = Number(t)
|
||||
//如果是纯数字,把那一条加进去
|
||||
if (!isNaN(d)) {
|
||||
res.push(props.list[d])
|
||||
}
|
||||
} catch (err) {
|
||||
}
|
||||
return res.sort((a: Article, b: Article) => {
|
||||
//使完整包含的条目更靠前
|
||||
const aMatch = a.title.toLowerCase().includes(t);
|
||||
const bMatch = b.title.toLowerCase().includes(t);
|
||||
|
||||
if (aMatch && !bMatch) return -1; // a 靠前
|
||||
if (!aMatch && bMatch) return 1; // b 靠前
|
||||
return 0; // 都匹配或都不匹配,保持原顺序
|
||||
})
|
||||
} else {
|
||||
return props.list
|
||||
}
|
||||
|
||||
@@ -146,8 +146,8 @@ defineExpose({scrollToBottom, scrollToItem})
|
||||
style="overflow: auto;"
|
||||
ref="listRef">
|
||||
<div class="list-item-wrapper"
|
||||
v-for="(item,index) in list"
|
||||
:key="item.id"
|
||||
v-for="(item,index) in props.list"
|
||||
:key="item.title"
|
||||
>
|
||||
<div class="common-list-item"
|
||||
:class="{
|
||||
|
||||
@@ -431,7 +431,7 @@ useEvents([
|
||||
<div class="panel-page-item pl-4">
|
||||
<WordList
|
||||
v-if="data.words.length"
|
||||
:is-active="true"
|
||||
:is-active="settingStore.showPanel"
|
||||
:static="false"
|
||||
:show-word="!settingStore.dictation"
|
||||
:show-translate="settingStore.translate"
|
||||
|
||||
@@ -13,6 +13,9 @@ import {getDefaultArticle, getDefaultDict, getDefaultWord} from "@/types/func.ts
|
||||
import {set} from "idb-keyval";
|
||||
import book_list from "@/assets/book-list.json";
|
||||
import dict_list from "@/assets/dict-list.json";
|
||||
import duration from "dayjs/plugin/duration";
|
||||
|
||||
dayjs.extend(duration);
|
||||
|
||||
export function no() {
|
||||
Toast.warning('未现实')
|
||||
@@ -47,12 +50,12 @@ export function checkAndUpgradeSaveDict(val: any) {
|
||||
// console.log('state', state)
|
||||
if (version === SAVE_DICT_KEY.version) {
|
||||
checkRiskKey(defaultState, state)
|
||||
defaultState.article.bookList = defaultState.article.bookList.map(v=>{
|
||||
return getDefaultDict(checkRiskKey(getDefaultDict(),v))
|
||||
})
|
||||
defaultState.word.bookList = defaultState.word.bookList.map(v=>{
|
||||
return getDefaultDict(checkRiskKey(getDefaultDict(),v))
|
||||
})
|
||||
defaultState.article.bookList = defaultState.article.bookList.map(v => {
|
||||
return getDefaultDict(checkRiskKey(getDefaultDict(), v))
|
||||
})
|
||||
defaultState.word.bookList = defaultState.word.bookList.map(v => {
|
||||
return getDefaultDict(checkRiskKey(getDefaultDict(), v))
|
||||
})
|
||||
return defaultState
|
||||
} else {
|
||||
if (version === 3) {
|
||||
@@ -294,6 +297,18 @@ export function _dateFormat(val: any, format?: string): string {
|
||||
return dayjs(d).format(format)
|
||||
}
|
||||
|
||||
export function msToHourMinute(ms) {
|
||||
const d = dayjs.duration(ms);
|
||||
const hours = d.hours();
|
||||
const minutes = d.minutes();
|
||||
return `${hours}小时${minutes}分钟`;
|
||||
}
|
||||
|
||||
export function msToMinute(ms) {
|
||||
return `${Math.floor(dayjs.duration(ms).asMinutes())}分钟`;
|
||||
}
|
||||
|
||||
|
||||
export function _fetch(url: string) {
|
||||
return new Promise<any[]>(async (resolve, reject) => {
|
||||
await fetch(url).then(async r => {
|
||||
@@ -637,3 +652,10 @@ export async function loadJsLib(key: string, url: string) {
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
export function total(arr, key) {
|
||||
return arr.reduce((a, b) => {
|
||||
a += b[key];
|
||||
return a
|
||||
}, 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user