This commit is contained in:
王念超
2024-06-06 19:02:52 +08:00
parent 99341a699f
commit 6f0f87ade2
27 changed files with 827 additions and 688 deletions

2
components.d.ts vendored
View File

@@ -13,6 +13,7 @@ declare module 'vue' {
Close: typeof import('./src/components/icon/Close.vue')['default']
DeleteIcon: typeof import('./src/components/icon/DeleteIcon.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElInput: typeof import('element-plus/es')['ElInput']
@@ -27,6 +28,7 @@ declare module 'vue' {
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTableV2: typeof import('element-plus/es')['ElTableV2']
Empty: typeof import('./src/components/Empty.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']

View File

@@ -47,7 +47,6 @@
"@unocss/postcss": "^0.60.2",
"@vitejs/plugin-vue": "^4.2.3",
"@vitejs/plugin-vue-jsx": "^3.0.1",
"@vue-macros/reactivity-transform": "^0.4.5",
"@vue/compiler-sfc": "^3.3.4",
"commitizen": "^4.3.0",
"cz-conventional-changelog": "^3.3.0",
@@ -61,7 +60,6 @@
"unocss": "^0.60.2",
"unplugin-auto-import": "^0.16.6",
"unplugin-vue-components": "^0.25.2",
"unplugin-vue-define-options": "^1.4.1",
"unplugin-vue-macros": "^2.9.3",
"vite": "^5.2.11",
"vue-tsc": "^2.0.19",

12
pnpm-lock.yaml generated
View File

@@ -99,9 +99,6 @@ importers:
'@vitejs/plugin-vue-jsx':
specifier: ^3.0.1
version: 3.1.0(vite@5.2.12(@types/node@20.12.13)(sass@1.77.4))(vue@3.4.27(typescript@5.4.5))
'@vue-macros/reactivity-transform':
specifier: ^0.4.5
version: 0.4.6(rollup@4.18.0)(vue@3.4.27(typescript@5.4.5))
'@vue/compiler-sfc':
specifier: ^3.3.4
version: 3.4.27
@@ -141,9 +138,6 @@ importers:
unplugin-vue-components:
specifier: ^0.25.2
version: 0.25.2(@babel/parser@7.24.6)(rollup@4.18.0)(vue@3.4.27(typescript@5.4.5))
unplugin-vue-define-options:
specifier: ^1.4.1
version: 1.4.5(rollup@4.18.0)(vue@3.4.27(typescript@5.4.5))
unplugin-vue-macros:
specifier: ^2.9.3
version: 2.9.3(@vueuse/core@9.13.0(vue@3.4.27(typescript@5.4.5)))(esbuild@0.20.2)(rollup@4.18.0)(typescript@5.4.5)(vite@5.2.12(@types/node@20.12.13)(sass@1.77.4))(vue@3.4.27(typescript@5.4.5))
@@ -1189,7 +1183,7 @@ packages:
engines: {node: '>=8'}
bindings@1.5.0:
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==, tarball: https://r2.cnpmjs.org/bindings/-/bindings-1.5.0.tgz}
bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
@@ -1720,7 +1714,7 @@ packages:
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
file-uri-to-path@1.0.0:
resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==, tarball: https://r2.cnpmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz}
fill-range@4.0.0:
resolution: {integrity: sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==}
@@ -1815,7 +1809,7 @@ packages:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
fsevents@1.2.13:
resolution: {integrity: sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==}
resolution: {integrity: sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==, tarball: https://r2.cnpmjs.org/fsevents/-/fsevents-1.2.13.tgz}
engines: {node: '>= 4.0'}
os: [darwin]
deprecated: The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2

View File

@@ -156,7 +156,7 @@ export function getCurrentStudyWord() {
})
}
console.timeEnd()
console.log('data', data)
// console.timeEnd()
// console.log('data', data)
return data
}

View File

@@ -1,368 +0,0 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {DefaultDisplayStatistics, DictType, Sort, Word} from "@/types.ts";
import {cloneDeep} from "lodash-es";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import {syncMyDictList, useWordOptions} from "@/hooks/dict.ts";
import {onMounted, onUnmounted, watch} from "vue";
import BaseButton from "@/components/BaseButton.vue";
import BaseIcon from "@/components/BaseIcon.vue";
import MobilePanel from "@/pages/mobile/components/MobilePanel.vue";
import MiniDialog from "@/pages/pc/components/dialog/MiniDialog.vue";
import WordList from "@/pages/pc/components/list/WordList.vue";
import Empty from "@/components/Empty.vue";
import {Icon} from "@iconify/vue";
import router from "@/router.ts";
import Typing from "@/pages/pc/components/Typing.vue";
import {usePracticeStore} from "@/stores/practice.ts";
const store = useBaseStore()
const runtimeStore = useRuntimeStore()
const settingStore = useSettingStore()
let data = $ref({
words: [],
index: -1,
wrongWords: [],
})
const word: Word = $computed(() => {
return data.words[data.index] ?? {
trans: [],
word: '',
phonetic0: '',
phonetic1: '',
}
})
function getCurrentPractice() {
}
function sort(list: Word[]) {
data.index = 0
syncMyDictList(store.currentDict)
}
function nextChapter() {
getCurrentPractice()
}
let stat = cloneDeep(DefaultDisplayStatistics)
const statisticsStore = usePracticeStore()
function next(isTyping: boolean = true) {
if (data.index === data.words.length - 1) {
//复制当前错词,因为第一遍错词是最多的,后续的练习都是从错词中练习
if (stat.total === -1) {
let now = Date.now()
stat = {
startDate: statisticsStore.startDate,
endDate: now,
spend: now - statisticsStore.startDate,
total: props.words.length,
correctRate: -1,
inputWordNumber: statisticsStore.inputWordNumber,
wrongWordNumber: data.wrongWords.length,
wrongWords: data.wrongWords,
}
stat.correctRate = 100 - Math.trunc(((stat.wrongWordNumber) / (stat.total)) * 100)
}
if (data.wrongWords.length) {
console.log('当前背完了,但还有错词')
data.words = cloneDeep(data.wrongWords)
statisticsStore.total = data.words.length
statisticsStore.index = data.index = 0
statisticsStore.inputWordNumber = 0
statisticsStore.wrongWordNumber = 0
data.wrongWords = []
} else {
console.log('这章节完了')
isTyping && statisticsStore.inputWordNumber++
let now = Date.now()
stat.endDate = now
stat.spend = now - stat.startDate
emitter.emit(EventKey.openStatModal, stat)
}
} else {
data.index++
isTyping && practiceStore.inputWordNumber++
console.log('这个词完了')
if ([DictType.word].includes(store.currentDict.type)
&& store.skipWordNames.includes(word.word.toLowerCase())) {
next()
}
}
}
watch(() => store.load, n => {
getCurrentPractice()
})
let bodyHeight = $ref('100vh')
onMounted(() => {
bodyHeight = document.body.clientHeight + 'px'
getCurrentPractice()
})
onUnmounted(() => {
})
const {
isWordCollect,
toggleWordCollect,
isWordSimple,
toggleWordSimple,
} = useWordOptions()
let showSortOption = $ref(false)
let showTranslate = $ref(false)
let keyboardKeys = [
['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'],
['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'],
['z', 'x', 'c', 'v', 'b', 'n', 'm']
]
let inputVal = $ref('')
let inputRef = $ref<HTMLInputElement>()
function change(e) {
console.log('e', e)
e.key = e.data
emitter.emit(EventKey.onTyping, e)
inputRef.value = ''
}
function know() {
settingStore.translate = false
setTimeout(() => {
data.index++
}, 300)
}
function unknow() {
settingStore.translate = true
inputRef.focus()
}
</script>
<template>
<div class="practice-center" :style="{height:bodyHeight}">
<div class="slide">
<div class="slide-list" :class="{showPanel:settingStore.showPanel}">
<div class="practice" @click.stop="settingStore.showPanel = false">
<div class="tool-bar">
<div class="left">
<Icon icon="octicon:arrow-left-24" width="22"
@click="router.back()"
/>
</div>
<div class="right">
<BaseIcon
v-if="!isWordCollect(word)"
class="collect"
@click="toggleWordCollect(word)"
icon="ph:star"/>
<BaseIcon
v-else
class="fill"
@click="toggleWordCollect(word)"
icon="ph:star-fill"/>
<BaseIcon
@click="settingStore.showPanel = !settingStore.showPanel"
icon="tdesign:menu-unfold"/>
</div>
</div>
<input ref="inputRef"
style="position:fixed;top:200vh;"
@input="change"
type="text">
<Typing
style="width: 90%;"
v-loading="!store.load"
ref="typingRef"
:word="word"
@next="next"
/>
<div class="options">
<div class="wrapper">
<BaseButton @click="unknow">不认识</BaseButton>
<BaseButton @click="know">认识</BaseButton>
</div>
</div>
</div>
<div class="list">
<MobilePanel>
<template v-slot="{active}">
<div class="panel-page-item"
v-loading="!store.load"
>
<div class="list-header">
<div class="left">
<div class="title">
</div>
<BaseIcon title="切换词典"
@click="emitter.emit(EventKey.openDictModal,'list')"
icon="carbon:change-catalog"/>
<div style="position:relative;"
@click.stop="null">
<BaseIcon
title="改变顺序"
icon="icon-park-outline:sort-two"
@click="showSortOption = !showSortOption"
/>
<MiniDialog
v-model="showSortOption"
style="width: 130rem;"
>
<div class="mini-row-title">
列表循环设置
</div>
<div class="mini-row">
<BaseButton size="small" @click="sort(Sort.reverse)">翻转</BaseButton>
<BaseButton size="small" @click="sort(Sort.random)">随机</BaseButton>
</div>
</MiniDialog>
</div>
<BaseIcon icon="bi:arrow-right"
@click="next"
/>
</div>
<div class="right">
{{ data.words.length }}个单词
</div>
</div>
<WordList
v-if="data.words.length"
:is-active="active"
: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
v-if="!isWordCollect(item)"
class="collect"
@click="toggleWordCollect(item)"
title="收藏" icon="ph:star"/>
<BaseIcon
v-else
class="fill"
@click="toggleWordCollect(item)"
title="取消收藏" icon="ph:star-fill"/>
<BaseIcon
v-if="!isWordSimple(item)"
class="easy"
@click="toggleWordSimple(item)"
title="标记为简单词"
icon="material-symbols:check-circle-outline-rounded"/>
<BaseIcon
v-else
class="fill"
@click="toggleWordSimple(item)"
title="取消标记简单词"
icon="material-symbols:check-circle-rounded"/>
</template>
</WordList>
<Empty v-else/>
</div>
</template>
</MobilePanel>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.practice-center {
position: fixed;
z-index: 1;
font-size: 14rem;
color: black;
width: 100%;
left: 0;
top: 0;
height: 100vh;
$list-width: 75vw;
.slide {
width: 100%;
height: 100%;
.slide-list {
width: calc(100vw + $list-width);
height: 100%;
display: flex;
transition: all .5s;
}
.showPanel {
transform: translateX(-$list-width);
}
}
.practice {
width: 100vw;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 10rem;
.tool-bar {
width: 100%;
height: 50rem;
display: flex;
padding: 0 10rem;
align-items: center;
justify-content: space-between;
gap: 10rem;
}
:deep(.word){
letter-spacing: 0;
font-size: 40rem!important;
}
.options {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding-bottom: 20rem;
.wrapper {
width: 80%;
display: flex;
flex-direction: column;
gap: 10rem;
}
.base-button {
width: 100%;
}
}
}
.list {
width: $list-width;
}
}
</style>

View File

@@ -29,10 +29,10 @@ watch(statisticsStore, () => {
if (statisticsStore.inputWordNumber < 1) {
return statisticsStore.correctRate = -1
}
if (statisticsStore.wrongWordNumber > statisticsStore.inputWordNumber) {
if (statisticsStore.wrong > statisticsStore.inputWordNumber) {
return statisticsStore.correctRate = 0
}
statisticsStore.correctRate = 100 - Math.trunc(((statisticsStore.wrongWordNumber) / (statisticsStore.inputWordNumber)) * 100)
statisticsStore.correctRate = 100 - Math.trunc(((statisticsStore.wrong) / (statisticsStore.inputWordNumber)) * 100)
})

View File

@@ -69,7 +69,7 @@ watch(() => props.words, () => {
statisticsStore.startDate = Date.now()
statisticsStore.correctRate = -1
statisticsStore.inputWordNumber = 0
statisticsStore.wrongWordNumber = 0
statisticsStore.wrong = 0
stat = cloneDeep(DefaultDisplayStatistics)
}, {immediate: true})
@@ -96,10 +96,10 @@ function next(isTyping: boolean = true) {
total: props.words.length,
correctRate: -1,
inputWordNumber: statisticsStore.inputWordNumber,
wrongWordNumber: data.wrongWords.length,
wrong: data.wrongWords.length,
wrongWords: data.wrongWords,
}
stat.correctRate = 100 - Math.trunc(((stat.wrongWordNumber) / (stat.total)) * 100)
stat.correctRate = 100 - Math.trunc(((stat.wrong) / (stat.total)) * 100)
}
if (data.wrongWords.length) {
@@ -109,7 +109,7 @@ function next(isTyping: boolean = true) {
statisticsStore.total = data.words.length
statisticsStore.index = data.index = 0
statisticsStore.inputWordNumber = 0
statisticsStore.wrongWordNumber = 0
statisticsStore.wrong = 0
data.wrongWords = []
} else {
console.log('这章节完了')
@@ -138,7 +138,7 @@ function wordWrong() {
}
if (!data.wrongWords.find((v: Word) => v.word.toLowerCase() === word.word.toLowerCase())) {
data.wrongWords.push(word)
statisticsStore.wrongWordNumber++
statisticsStore.wrong++
}
}

View File

@@ -28,10 +28,10 @@ watch(statisticsStore, () => {
if (statisticsStore.inputWordNumber < 1) {
return statisticsStore.correctRate = -1
}
if (statisticsStore.wrongWordNumber > statisticsStore.inputWordNumber) {
if (statisticsStore.wrong > statisticsStore.inputWordNumber) {
return statisticsStore.correctRate = 0
}
statisticsStore.correctRate = 100 - Math.trunc(((statisticsStore.wrongWordNumber) / (statisticsStore.inputWordNumber)) * 100)
statisticsStore.correctRate = 100 - Math.trunc(((statisticsStore.wrong) / (statisticsStore.inputWordNumber)) * 100)
})

View File

@@ -28,10 +28,10 @@ watch(statisticsStore, () => {
if (statisticsStore.inputWordNumber < 1) {
return statisticsStore.correctRate = -1
}
if (statisticsStore.wrongWordNumber > statisticsStore.inputWordNumber) {
if (statisticsStore.wrong > statisticsStore.inputWordNumber) {
return statisticsStore.correctRate = 0
}
statisticsStore.correctRate = 100 - Math.trunc(((statisticsStore.wrongWordNumber) / (statisticsStore.inputWordNumber)) * 100)
statisticsStore.correctRate = 100 - Math.trunc(((statisticsStore.wrong) / (statisticsStore.inputWordNumber)) * 100)
})

View File

@@ -2,7 +2,7 @@
import {onMounted, onUnmounted, watch} from "vue"
import {useBaseStore} from "@/stores/base.ts"
import {DefaultDisplayStatistics, DictType, getDefaultWord, ShortcutKey, Sort, Word} from "@/types.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts"
import {emitter, EventKey, useEvents} from "@/utils/eventBus.ts"
import {cloneDeep, reverse, shuffle} from "lodash-es"
import {usePracticeStore} from "@/stores/practice.ts"
import {useSettingStore} from "@/stores/setting.ts";
@@ -72,7 +72,7 @@ watch(() => props.data, () => {
statStore.startDate = Date.now()
statStore.correctRate = -1
statStore.inputWordNumber = 0
statStore.wrongWordNumber = 0
statStore.wrong = 0
statStore.total = props.data.review.concat(props.data.new).concat(props.data.write).length
statStore.newWordNumber = props.data.new.length
statStore.index = 0
@@ -142,7 +142,7 @@ function wordWrong() {
}
if (!allWrongWords.find((v: Word) => v.word.toLowerCase() === word.word.toLowerCase())) {
allWrongWords.push(word)
statStore.wrongWordNumber++
statStore.wrong++
}
}
@@ -197,23 +197,14 @@ function play() {
typingRef.play()
}
onMounted(() => {
emitter.on(ShortcutKey.ShowWord, show)
emitter.on(ShortcutKey.Previous, prev)
emitter.on(ShortcutKey.Next, skip)
emitter.on(ShortcutKey.ToggleCollect, collect)
emitter.on(ShortcutKey.ToggleSimple, toggleWordSimpleWrapper)
emitter.on(ShortcutKey.PlayWordPronunciation, play)
})
onUnmounted(() => {
emitter.off(ShortcutKey.ShowWord, show)
emitter.off(ShortcutKey.Previous, prev)
emitter.off(ShortcutKey.Next, skip)
emitter.off(ShortcutKey.ToggleCollect, collect)
emitter.off(ShortcutKey.ToggleSimple, toggleWordSimpleWrapper)
emitter.off(ShortcutKey.PlayWordPronunciation, play)
})
useEvents([
[ShortcutKey.ShowWord, show],
[ShortcutKey.Previous, prev],
[ShortcutKey.Next, skip],
[ShortcutKey.ToggleCollect, collect],
[ShortcutKey.ToggleSimple, toggleWordSimpleWrapper],
[ShortcutKey.PlayWordPronunciation, play],
])
const status = $computed(() => {
let str = '正在'

View File

@@ -5,6 +5,8 @@ import WordListDialog from "@/pages/pc/components/dialog/WordListDialog.vue";
import {emitter, EventKey, useEvent} from "@/utils/eventBus.ts";
import BaseIcon from "@/components/BaseIcon.vue";
import Dialog from "@/pages/pc/components/dialog/Dialog.vue";
import {_dateFormat} from "@/utils";
import {sumBy} from "lodash-es";
const store = useBaseStore()
@@ -23,7 +25,17 @@ useEvent(EventKey.openDictModal, () => {
})
const startDate = $computed(() => {
if (store.sdict.statistics.length) {
return _dateFormat(store.sdict.statistics[0].startDate, 'YYYY-MM-DD')
} else {
return '-'
}
})
const speedTime = $computed(() => {
let d = Math.ceil(sumBy(store.sdict.statistics, 'speed') / 1000 / 60)
if (d < 60) return d + '分钟'
else return (d / 60).toFixed(1) + '小时'
})
</script>
@@ -43,9 +55,9 @@ const startDate = $computed(() => {
title="单词列表"
/>
</div>
<div class="text">开始日期-</div>
<div class="text">花费时-</div>
<div class="text">累积错误-</div>
<div class="text">开始日期{{ startDate }}</div>
<div class="text">花费时{{ speedTime }}</div>
<div class="text">累积错误{{ sumBy(store.sdict.statistics, 'wrong') }}</div>
<div class="mt-2">
<div class="text-sm flex justify-between">
已学习{{ store.currentStudyProgress }}%
@@ -64,7 +76,7 @@ const startDate = $computed(() => {
<style scoped lang="scss">
#DictDialog {
width: 26rem;
width: 20rem;
.detail {
color: var(--color-font-1);

View File

@@ -1,126 +1,7 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts";
import DictListPanel from "@/pages/pc/components/DictListPanel.vue";
import {Icon} from '@iconify/vue'
import {ActivityCalendar} from "vue-activity-calendar";
import "vue-activity-calendar/style.css";
import {useRouter} from "vue-router";
import BaseIcon from "@/components/BaseIcon.vue";
const base = useBaseStore()
const router = useRouter()
function clickEvent(e) {
console.log('e', e)
}
</script>
<template>
<div class="word flex justify-center ">
<div class="w-5/10 pt-5">
<div class="flex gap-6">
<div class="card w-1/2 flex flex-col">
<div class="title">
我的词典
</div>
<div class="grid flex-1 flex gap-5 mt-4">
<div class="p-4 flex-1 rounded-md bg-slate-200 relative" v-for="i in 3">
<span>收藏</span>
<div class="absolute bottom-4 right-4">333个词</div>
</div>
</div>
</div>
<div class="w-1/2">
<div class="card ">
<div class="flex justify-between items-center">
<div class="bg-slate-200 p-3 rounded-md cursor-pointer flex items-center">
<span class="text-lg font-bold">{{ base.currentDict.name }}</span>
<Icon icon="gg:arrows-exchange" class="text-2xl ml-2"/>
<Icon icon="uil:setting" class="text-2xl ml-2"/>
</div>
<div class="rounded-xl bg-slate-800 flex items-center py-3 px-5 text-white cursor-pointer"
@click="router.push('/practice')">
开始学习
</div>
</div>
<div class="mt-5 text-sm">已学习5555个单词的1%</div>
<el-progress class="mt-1" :percentage="80" :show-text="false"></el-progress>
</div>
<div class="card flex gap-3">
<div class="bg-slate-200 w-10 h-10 flex center text-2xl rounded">
0
</div>
<div class="flex-1">
<div class="flex justify-between">
<div class="title">
每日目标
</div>
<div style="color:#ac6ed1;" class="cursor-pointer">
更改目标
</div>
</div>
<div class="mt-2 text-xs">学习 50 个单词</div>
<el-progress class="flex-1 mt-1" :percentage="80" :show-text="false"></el-progress>
</div>
</div>
</div>
</div>
<div class="card">
<div class="flex justify-between">
<div class="title">
其他学习词典
</div>
<BaseIcon icon="ic:round-add" @click="router.push('/dict')"/>
</div>
<div class="grid grid-cols-2 gap-6 mt-5 ">
<div class=" p-4 rounded-md justify-between items-center bg-slate-200 " v-for="i in 3">
<div class="flex justify-between w-full">
<span>{{ base.currentDict.name }}</span>
<div class="text-2xl ml-2 flex gap-4">
<Icon icon="hugeicons:delete-02"/>
<Icon icon="nonicons:go-16"/>
</div>
</div>
<div class="mt-5 text-sm">已学习5555个单词的1%</div>
<el-progress class="mt-1" :percentage="80" color="white" :show-text="false"></el-progress>
</div>
</div>
<div class="flex justify-center mt-2 text-2xl">
<Icon icon="mingcute:down-line"/>
</div>
</div>
<div class="card">
<div class="title">
学习记录
</div>
<div class="center">
<ActivityCalendar
:data="[{ date: '2023-05-22', count: 5 }]"
:width="40"
:height="7"
:cellLength="16"
:cellInterval="8"
:fontSize="12"
:showLevelFlag="false"
:showWeekDayFlag="false"
:clickEvent="clickEvent"
/>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.card {
@apply rounded-xl bg-white p-4 mt-5;
}
.title {
@apply text-lg font-medium;
}
</style>

View File

@@ -75,7 +75,7 @@ function setArticle(val: Article) {
articleData.articles[store.currentArticleDict.chapterIndex] = tempVal
articleData.article = tempVal
statisticsStore.inputWordNumber = 0
statisticsStore.wrongWordNumber = 0
statisticsStore.wrong = 0
statisticsStore.total = 0
statisticsStore.startDate = Date.now()
articleData.article.sections.map((v, i) => {
@@ -195,7 +195,7 @@ function wrong(word: Word) {
}
function over() {
if (statisticsStore.wrongWordNumber === 0) {
if (statisticsStore.wrong === 0) {
// if (false) {
console.log('这章节完了')
let now = Date.now()
@@ -205,9 +205,9 @@ function over() {
spend: now - statisticsStore.startDate,
total: statisticsStore.total,
correctRate: -1,
wrongWordNumber: statisticsStore.wrongWordNumber,
wrong: statisticsStore.wrong,
}
stat.correctRate = 100 - Math.trunc(((stat.wrongWordNumber) / (stat.total)) * 100)
stat.correctRate = 100 - Math.trunc(((stat.wrong) / (stat.total)) * 100)
emitter.emit(EventKey.openStatModal, stat)
} else {
tabIndex = 1

View File

@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="tsx">
import BasePage from "@/pages/pc/components/BasePage.vue";
import {onMounted, reactive} from "vue";
@@ -9,8 +9,8 @@ import {assign, cloneDeep, reverse, shuffle} from "lodash-es";
import {Sort, Word} from "@/types.ts";
import {nanoid} from "nanoid";
import BaseIcon from "@/components/BaseIcon.vue";
import {useNav} from "@/utils";
import {FormInstance, FormRules} from "element-plus";
import {_checkDictWords, useNav} from "@/utils";
import {FormInstance, FormRules, TableV2FixedDir} from "element-plus";
import MiniDialog from "@/pages/pc/components/dialog/MiniDialog.vue";
import BaseButton from "@/components/BaseButton.vue";
@@ -34,6 +34,10 @@ let list = $computed({
onMounted(() => {
switch (Number(route.query.type)) {
case -1:
runtimeStore.editDict = cloneDeep(runtimeStore.routeData)
_checkDictWords(runtimeStore.editDict)
break
case 0:
runtimeStore.editDict = cloneDeep(store.collectWord)
break
@@ -52,7 +56,6 @@ onMounted(() => {
})
const {back} = useNav()
let wordFormData = $ref({
where: '',
type: '',
@@ -168,13 +171,38 @@ function sort(type: Sort) {
}
}
const columns = [
{dataKey: 'word', title: '单词', width: 120},
{
key: 'date',
title: 'Date',
dataKey: 'date',
width: 150,
fixed: TableV2FixedDir.LEFT,
cellRenderer: ({rowData}) => (
<div>
<el-button size="small" onClick={editWord(rowData)}>
Edit
</el-button>
<el-button
size="small"
type="danger"
onClick={delWord({item: rowData})}
>
Delete
</el-button>
</div>
),
},
]
</script>
<template>
<BasePage>
<header class="flex gap-4 items-center">
<BaseIcon @click="back" icon="octicon:arrow-left-24" width="20"/>
<BaseIcon @click="back" icon="octicon:arrow-left-24" width="20"/>
<div class="left">
<div class="top">
<div class="text-xl">
@@ -216,31 +244,14 @@ function sort(type: Sort) {
</div>
</MiniDialog>
</div>
<el-table :data="list"
@selection-change="handleSelectionChange"
max-height="80vh">
<el-table-column type="selection" width="55"/>
<el-table-column prop="word" label="单词" width="120"/>
<el-table-column prop="trans" label="翻译" width="180">
<template #default="scope">
{{ scope.row.trans.map(v => v.cn) }}
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button size="small" @click="editWord( scope.row)">
Edit
</el-button>
<el-button
size="small"
type="danger"
@click="delWord({item: scope.row})"
>
Delete
</el-button>
</template>
</el-table-column>
</el-table>
<el-table-v2
:columns="columns"
:data="list"
:width="500"
:height="400"
@selection-change="handleSelectionChange"
>
</el-table-v2>
</div>
<div class="add w-1/2" v-if="wordFormData.type">
<div class="common-title">

View File

@@ -0,0 +1,282 @@
<script setup lang="ts">
import BasePage from "@/pages/pc/components/BasePage.vue";
import {onMounted, reactive} from "vue";
import {useRoute} from "vue-router";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useBaseStore} from "@/stores/base.ts";
import {assign, cloneDeep, reverse, shuffle} from "lodash-es";
import {Sort, Word} from "@/types.ts";
import {nanoid} from "nanoid";
import BaseIcon from "@/components/BaseIcon.vue";
import {useNav} from "@/utils";
import {FormInstance, FormRules} from "element-plus";
import MiniDialog from "@/pages/pc/components/dialog/MiniDialog.vue";
import BaseButton from "@/components/BaseButton.vue";
const runtimeStore = useRuntimeStore()
const store = useBaseStore()
const route = useRoute()
let show = $ref(false)
let searchKey = $ref('')
let list = $computed({
get() {
if (searchKey) {
return runtimeStore.editDict.words.filter(v => v.word.includes(searchKey))
}
return runtimeStore.editDict.words
},
set(v) {
runtimeStore.editDict.words = v
}
})
onMounted(() => {
switch (Number(route.query.type)) {
case 0:
runtimeStore.editDict = cloneDeep(store.collectWord)
break
case 1:
runtimeStore.editDict = cloneDeep(store.wrong)
break
case 2:
runtimeStore.editDict = cloneDeep(store.simple)
break
case 3:
runtimeStore.editDict = cloneDeep(store.master)
break
default:
break
}
})
const {back} = useNav()
let wordFormData = $ref({
where: '',
type: '',
id: '',
index: 0
})
enum FormMode {
None = '',
Add = 'Add',
Edit = 'Edit',
}
const DefaultFormWord = {
word: '',
phonetic0: '',
phonetic1: '',
trans: '',
}
let wordForm = $ref(cloneDeep(DefaultFormWord))
const wordFormRef = $ref<FormInstance>()
const wordRules = reactive<FormRules>({
name: [
{required: true, message: '请输入单词', trigger: 'blur'},
{max: 30, message: '名称不能超过30个字符', trigger: 'blur'},
],
})
//TODO trans结构变了
async function onSubmitWord() {
await wordFormRef.validate((valid, fields) => {
if (valid) {
let data: any = cloneDeep(wordForm)
if (data.trans) {
data.trans = data.trans.split('\n');
} else {
data.trans = []
}
if (wordFormData.type === FormMode.Add) {
data.id = nanoid(6)
data.checked = false
let r = list.find(v => v.word === wordForm.word)
// if (r) return ElMessage.warning('已有相同名称单词!')
// else list.push(data)
list.push(data)
ElMessage.success('添加成功')
wordForm = cloneDeep(DefaultFormWord)
// setTimeout(wordListRef?.scrollToBottom, 100)
} else {
let r = list.find(v => v.id === wordFormData.id)
if (r) assign(r, data)
r = list.find(v => v.id === wordFormData.id)
if (r) assign(r, data)
ElMessage.success('修改成功')
}
} else {
ElMessage.warning('请填写完整')
}
})
}
function delWord(val: {
item: Word
}) {
let rIndex2 = list.findIndex(v => v.id === val.item.id)
if (rIndex2 > -1) {
list.splice(rIndex2, 1)
}
if (wordFormData.type === FormMode.Edit && wordForm.word === val.item.word) {
closeWordForm()
}
}
function batchDelWord() {
console.log('multipleSelection', multipleSelection)
multipleSelection.map(v => delWord({item: v}))
}
function editWord(word: Word,) {
wordFormData.type = FormMode.Edit
wordFormData.id = word.id
wordForm.word = word.word
wordForm.phonetic1 = word.phonetic1
wordForm.phonetic0 = word.phonetic0
wordForm.trans = word.trans.join('\n')
}
function addWord() {
// setTimeout(wordListRef?.scrollToBottom, 100)
wordFormData.type = FormMode.Add
wordForm = cloneDeep(DefaultFormWord)
}
function closeWordForm() {
wordFormData.type = FormMode.None
wordForm = cloneDeep(DefaultFormWord)
}
let multipleSelection = $ref<any[]>([])
const handleSelectionChange = (val: any[]) => {
multipleSelection = val
}
function sort(type: Sort) {
if (type === Sort.reverse) {
ElMessage.success('已翻转排序')
list = reverse(cloneDeep(list))
}
if (type === Sort.random) {
ElMessage.success('已随机排序')
list = shuffle(cloneDeep(list))
}
}
</script>
<template>
<BasePage>
<header class="flex gap-4 items-center">
<BaseIcon @click="back" icon="octicon:arrow-left-24" width="20"/>
<div class="left">
<div class="top">
<div class="text-xl">
{{ runtimeStore.editDict.name }}
</div>
</div>
<div class="desc" v-if="runtimeStore.editDict.description">{{ runtimeStore.editDict.description }}</div>
</div>
</header>
<div class="flex">
<div class="w-1/2">
<div class="flex justify-end py-5 gap-2 relative">
<el-input v-model="searchKey"/>
<BaseIcon
v-if="multipleSelection.length"
@click="batchDelWord"
class="del"
title="删除"
icon="solar:trash-bin-minimalistic-linear"/>
<BaseIcon
@click="addWord"
icon="fluent:add-20-filled"
title="添加单词"/>
<BaseIcon
title="改变顺序"
icon="icon-park-outline:sort-two"
@click="show = !show"
/>
<MiniDialog
v-model="show"
style="width: 8rem;"
>
<div class="mini-row-title">
列表循环设置
</div>
<div class="mini-row">
<BaseButton size="small" @click="sort(Sort.reverse)">翻转</BaseButton>
<BaseButton size="small" @click="sort(Sort.random)">随机</BaseButton>
</div>
</MiniDialog>
</div>
<el-table :data="list"
@selection-change="handleSelectionChange"
max-height="80vh">
<el-table-column type="selection" width="55"/>
<el-table-column prop="word" label="单词" width="120"/>
<el-table-column prop="trans" label="翻译" width="180">
<template #default="scope">
{{ scope.row.trans.map(v => v.cn) }}
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button size="small" @click="editWord( scope.row)">
Edit
</el-button>
<el-button
size="small"
type="danger"
@click="delWord({item: scope.row})"
>
Delete
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="add w-1/2" v-if="wordFormData.type">
<div class="common-title">
{{ wordFormData.type === FormMode.Add ? '添加' : '修改' }}单词
</div>
<el-form
class="form"
ref="wordFormRef"
:rules="wordRules"
:model="wordForm"
label-width="6rem">
<el-form-item label="单词" prop="word">
<el-input v-model="wordForm.word"/>
</el-form-item>
<el-form-item label="翻译">
<el-input v-model="wordForm.trans"
placeholder="多个翻译请换行"
:autosize="{ minRows: 2, maxRows: 6 }"
type="textarea"/>
</el-form-item>
<el-form-item label="音标/发音①">
<el-input v-model="wordForm.phonetic0"/>
</el-form-item>
<el-form-item label="音标/发音②">
<el-input v-model="wordForm.phonetic1"/>
</el-form-item>
<div class="flex-center">
<el-button @click="closeWordForm">关闭</el-button>
<el-button type="primary" @click="onSubmitWord">保存</el-button>
</div>
</el-form>
</div>
</div>
</BasePage>
</template>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,377 @@
<script setup lang="ts">
import BasePage from "@/pages/pc/components/BasePage.vue";
import {nextTick, onMounted, reactive} from "vue";
import {useRoute} from "vue-router";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useBaseStore} from "@/stores/base.ts";
import {assign, cloneDeep, reverse, shuffle} from "lodash-es";
import {Sort, Word} from "@/types.ts";
import {nanoid} from "nanoid";
import BaseIcon from "@/components/BaseIcon.vue";
import {useNav} from "@/utils";
import {FormInstance, FormRules} from "element-plus";
import MiniDialog from "@/pages/pc/components/dialog/MiniDialog.vue";
import BaseButton from "@/components/BaseButton.vue";
import WordList from "@/pages/pc/components/list/WordList.vue";
import Empty from "@/components/Empty.vue";
import {useWindowClick} from "@/hooks/event.ts";
const runtimeStore = useRuntimeStore()
const store = useBaseStore()
const route = useRoute()
let show = $ref(false)
let searchKey = $ref('')
let list = $computed({
get() {
if (searchKey) {
return runtimeStore.editDict.words.filter(v => v.word.includes(searchKey))
}
return runtimeStore.editDict.words
},
set(v) {
runtimeStore.editDict.words = v
}
})
onMounted(() => {
switch (Number(route.query.type)) {
case 0:
runtimeStore.editDict = cloneDeep(store.collectWord)
break
case 1:
runtimeStore.editDict = cloneDeep(store.wrong)
break
case 2:
runtimeStore.editDict = cloneDeep(store.simple)
break
case 3:
runtimeStore.editDict = cloneDeep(store.master)
break
default:
break
}
})
const {back} = useNav()
let wordFormData = $ref({
where: '',
type: '',
id: '',
index: 0
})
enum FormMode {
None = '',
Add = 'Add',
Edit = 'Edit',
}
const DefaultFormWord = {
word: '',
phonetic0: '',
phonetic1: '',
trans: '',
}
let wordForm = $ref(cloneDeep(DefaultFormWord))
const wordFormRef = $ref<FormInstance>()
const wordRules = reactive<FormRules>({
name: [
{required: true, message: '请输入单词', trigger: 'blur'},
{max: 30, message: '名称不能超过30个字符', trigger: 'blur'},
],
})
//TODO trans结构变了
async function onSubmitWord() {
await wordFormRef.validate((valid, fields) => {
if (valid) {
let data: any = cloneDeep(wordForm)
if (data.trans) {
data.trans = data.trans.split('\n');
} else {
data.trans = []
}
if (wordFormData.type === FormMode.Add) {
data.id = nanoid(6)
data.checked = false
let r = list.find(v => v.word === wordForm.word)
// if (r) return ElMessage.warning('已有相同名称单词!')
// else list.push(data)
list.push(data)
ElMessage.success('添加成功')
wordForm = cloneDeep(DefaultFormWord)
// setTimeout(wordListRef?.scrollToBottom, 100)
} else {
let r = list.find(v => v.id === wordFormData.id)
if (r) assign(r, data)
r = list.find(v => v.id === wordFormData.id)
if (r) assign(r, data)
ElMessage.success('修改成功')
}
} else {
ElMessage.warning('请填写完整')
}
})
}
function delWord(val: {
item: Word
}) {
let rIndex2 = list.findIndex(v => v.id === val.item.id)
if (rIndex2 > -1) {
list.splice(rIndex2, 1)
}
if (wordFormData.type === FormMode.Edit && wordForm.word === val.item.word) {
closeWordForm()
}
}
function batchDelWord() {
console.log('multipleSelection', multipleSelection)
multipleSelection.map(v => delWord({item: v}))
}
function editWord(word: Word,) {
wordFormData.type = FormMode.Edit
wordFormData.id = word.id
wordForm.word = word.word
wordForm.phonetic1 = word.phonetic1
wordForm.phonetic0 = word.phonetic0
wordForm.trans = word.trans.join('\n')
}
function addWord() {
// setTimeout(wordListRef?.scrollToBottom, 100)
wordFormData.type = FormMode.Add
wordForm = cloneDeep(DefaultFormWord)
}
function closeWordForm() {
wordFormData.type = FormMode.None
wordForm = cloneDeep(DefaultFormWord)
}
let multipleSelection = $ref<any[]>([])
const handleSelectionChange = (val: any[]) => {
multipleSelection = val
}
function sort(type: Sort) {
if (type === Sort.reverse) {
ElMessage.success('已翻转排序')
list = reverse(cloneDeep(list))
}
if (type === Sort.random) {
ElMessage.success('已随机排序')
list = shuffle(cloneDeep(list))
}
}
let checkedAll = $ref(false)
let isIndeterminate = $ref(false)
function handleCheckedAll() {
list.map(v => v.checked = checkedAll)
}
let checkedTotal = $computed(() => {
return list.filter(v => v.checked).length
})
function handleCheckedChange({item: source}: any) {
source.checked = !source.checked
checkStatus()
}
function checkStatus() {
checkedAll = list.every(v => v.checked)
if (checkedAll) {
isIndeterminate = false
} else {
isIndeterminate = list.some(v => v.checked)
}
}
function del(val: { item: Word, index: number }) {
list.splice(val.index, 1)
}
let listRef: any = $ref()
function scrollToBottom() {
listRef.scrollToBottom()
}
function scrollToItem(index: number) {
nextTick(() => listRef.scrollToItem(index))
}
defineExpose({scrollToBottom, scrollToItem})
useWindowClick(() => show = false)
</script>
<template>
<BasePage>
<header class="flex gap-4 items-center">
<BaseIcon @click="back" icon="octicon:arrow-left-24" width="20"/>
<div class="left">
<div class="top">
<div class="text-xl">
{{ runtimeStore.editDict.name }}
</div>
</div>
<div class="desc" v-if="runtimeStore.editDict.description">{{ runtimeStore.editDict.description }}</div>
</div>
</header>
<div class="flex">
<div class="w-1/2">
<div class="flex justify-end py-5 gap-2 relative">
<el-input v-model="searchKey"/>
<BaseIcon
v-if="multipleSelection.length"
@click="batchDelWord"
class="del"
title="删除"
icon="solar:trash-bin-minimalistic-linear"/>
<BaseIcon
@click="addWord"
icon="fluent:add-20-filled"
title="添加单词"/>
<BaseIcon
title="改变顺序"
icon="icon-park-outline:sort-two"
@click="show = !show"
/>
<MiniDialog
v-model="show"
style="width: 8rem;"
>
<div class="mini-row-title">
列表循环设置
</div>
<div class="mini-row">
<BaseButton size="small" @click="sort(Sort.reverse)">翻转</BaseButton>
<BaseButton size="small" @click="sort(Sort.random)">随机</BaseButton>
</div>
</MiniDialog>
</div>
<div class="column">
<div class="header">
<div class="common-title">
<div class="options">
<div class="setting"
v-if="list.length"
@click.stop="null">
<BaseIcon
title="改变顺序"
icon="icon-park-outline:sort-two"
@click="show = !show"
/>
<MiniDialog
v-model="show"
style="width: 130rem;"
>
<div class="mini-row-title">
列表循环设置
</div>
<div class="mini-row">
<BaseButton size="small" @click="sort(Sort.reverse)">翻转</BaseButton>
<BaseButton size="small" @click="sort(Sort.random)">随机</BaseButton>
</div>
</MiniDialog>
</div>
<BaseIcon
@click="addWord"
icon="fluent:add-20-filled"
title="添加单词"/>
</div>
</div>
<div class="select"
v-if="list.length "
>
<div class="left">
<el-checkbox
v-model="checkedAll"
:indeterminate="isIndeterminate"
@change="handleCheckedAll"
size="large"/>
<span>全选</span>
</div>
<div class="right">{{ checkedTotal }}/{{ list.length }}</div>
</div>
</div>
<div class="wrapper">
<WordList
ref="listRef"
:list="list"
v-if="list.length"
@click="handleCheckedChange"
>
<template v-slot:prefix="{item}">
<el-checkbox v-model="item.checked"
@change="handleCheckedChange({item})"
size="large"/>
</template>
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click="editWord(item)"
title="编辑"
icon="tabler:edit"/>
<BaseIcon
class="del"
@click="del({item,index})"
title="删除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList>
<Empty v-else/>
</div>
</div>
</div>
<div class="add w-1/2" v-if="wordFormData.type">
<div class="common-title">
{{ wordFormData.type === FormMode.Add ? '添加' : '修改' }}单词
</div>
<el-form
class="form"
ref="wordFormRef"
:rules="wordRules"
:model="wordForm"
label-width="6rem">
<el-form-item label="单词" prop="word">
<el-input v-model="wordForm.word"/>
</el-form-item>
<el-form-item label="翻译">
<el-input v-model="wordForm.trans"
placeholder="多个翻译请换行"
:autosize="{ minRows: 2, maxRows: 6 }"
type="textarea"/>
</el-form-item>
<el-form-item label="音标/发音①">
<el-input v-model="wordForm.phonetic0"/>
</el-form-item>
<el-form-item label="音标/发音②">
<el-input v-model="wordForm.phonetic1"/>
</el-form-item>
<div class="flex-center">
<el-button @click="closeWordForm">关闭</el-button>
<el-button type="primary" @click="onSubmitWord">保存</el-button>
</div>
</el-form>
</div>
</div>
</BasePage>
</template>
<style scoped lang="scss">
</style>

View File

@@ -55,7 +55,7 @@ onUnmounted(() => {
<div class="name">输入数</div>
</div>
<div class="row">
<div class="num">{{ format(statisticsStore.wrongWordNumber, '', 0) }}</div>
<div class="num">{{ format(statisticsStore.wrong, '', 0) }}</div>
<div class="line"></div>
<div class="name">错误数</div>
</div>

View File

@@ -6,7 +6,7 @@ import Tooltip from "@/pages/pc/components/Tooltip.vue";
import Fireworks from "@/pages/pc/components/Fireworks.vue";
import BaseButton from "@/components/BaseButton.vue";
import {ShortcutKey} from "@/types.ts";
import {emitter, EventKey, useEvent} from "@/utils/eventBus.ts";
import {emitter, EventKey, useEvent, useEvents} from "@/utils/eventBus.ts";
import {onMounted} from "vue";
import {Icon} from '@iconify/vue';
import {useSettingStore} from "@/stores/setting.ts";
@@ -22,16 +22,16 @@ useEvent(EventKey.openStatModal, () => {
console.log('on')
let data = {
startIndex: store.currentStudyWordDict.lastLearnIndex,
endIndex: store.currentStudyWordDict.lastLearnIndex + store.currentStudyWordDict.perDayStudyNumber,
speed: statStore.speed,
startDate: statStore.startDate,
total: statStore.total,
wrong: statStore.wrong,
}
store.currentStudyWordDict.lastLearnIndex = data.endIndex
store.currentStudyWordDict.statistics.push(data as any)
store.currentStudyWordDict.statistics.sort((a, b) => a.startDate - b.startDate)
store.sdict.lastLearnIndex = store.sdict.lastLearnIndex + store.sdict.perDayStudyNumber
store.sdict.statistics.push(data as any)
store.sdict.statistics.sort((a, b) => a.startDate - b.startDate)
console.log('staa', JSON.parse(JSON.stringify(store.currentStudyWordDict.statistics)))
console.log('staa', JSON.parse(JSON.stringify(store.sdict.statistics)))
open = true
})
@@ -39,9 +39,11 @@ const close = () => {
open = false
}
useEvent(ShortcutKey.NextChapter, close)
useEvent(ShortcutKey.RepeatChapter, close)
useEvent(ShortcutKey.DictationChapter, close)
useEvents([
[ShortcutKey.NextChapter, close],
[ShortcutKey.RepeatChapter, close],
[ShortcutKey.DictationChapter, close],
])
function options(emitType: 'write' | 'repeat' | 'next') {
open = false
@@ -60,7 +62,7 @@ const isEnd = $computed(() => {
v-model="open">
<div class="statistics relative flex flex-col gap-6">
<header>
<div class="text-2xl">{{ store.currentStudyWordDict.name }}</div>
<div class="text-2xl">{{ store.sdict.name }}</div>
</header>
<div class="flex justify-center gap-10">
<div class="text-xl text-center flex flex-col justify-around">
@@ -78,7 +80,7 @@ const isEnd = $computed(() => {
<div class="flex justify-center gap-10">
<div class="flex justify-center items-center py-3 px-10 rounded-md color-red-500 flex-col"
style="background: rgb(254,236,236)">
<div class="text-3xl">{{ statStore.wrongWordNumber }}</div>
<div class="text-3xl">{{ statStore.wrong }}</div>
<div class="center gap-2">
<Icon icon="iconamoon:close" class="text-2xl"/>
错词
@@ -86,7 +88,7 @@ const isEnd = $computed(() => {
</div>
<div class="flex justify-center items-center py-3 px-10 rounded-md color-green-600 flex-col"
style="background: rgb(231,248,241)">
<div class="text-3xl">{{ statStore.total - statStore.wrongWordNumber }}</div>
<div class="text-3xl">{{ statStore.total - statStore.wrong }}</div>
<div class="center gap-2">
<Icon icon="tabler:check" class="text-2xl"/>
正确

View File

@@ -1,22 +1,22 @@
<script setup lang="ts">
import Toolbar from "@/pages/pc/components/toolbar/index.vue"
import {onMounted, onUnmounted, watch} from "vue";
import {onMounted, 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, useEvent} from "@/utils/eventBus.ts";
import {emitter, EventKey, useEvent, useEvents} from "@/utils/eventBus.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {ShortcutKey, Word} from "@/types.ts";
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 TypingWord from "@/pages/pc/components/TypingWord.vue";
import {getCurrentStudyWord, syncMyDictList} from "@/hooks/dict.ts";
import {cloneDeep, shuffle} from "lodash-es";
import {getCurrentStudyWord} from "@/hooks/dict.ts";
import {cloneDeep} from "lodash-es";
const statStore = usePracticeStore()
const store = useBaseStore()
@@ -28,10 +28,10 @@ watch(statStore, () => {
if (statStore.inputWordNumber < 1) {
return statStore.correctRate = -1
}
if (statStore.wrongWordNumber > statStore.inputWordNumber) {
if (statStore.wrong > statStore.inputWordNumber) {
return statStore.correctRate = 0
}
statStore.correctRate = 100 - Math.trunc(((statStore.wrongWordNumber) / (statStore.inputWordNumber)) * 100)
statStore.correctRate = 100 - Math.trunc(((statStore.wrong) / (statStore.inputWordNumber)) * 100)
})
function next() {
@@ -80,19 +80,21 @@ onMounted(() => {
}
})
useEvent(EventKey.changeDict, getCurrentPractice)
useEvents([
[EventKey.changeDict, getCurrentPractice],
[EventKey.repeat, repeat],
[EventKey.next, next],
useEvent(EventKey.repeat, repeat)
useEvent(EventKey.next, next)
[ShortcutKey.RepeatChapter, repeat],
[ShortcutKey.ToggleShowTranslate, toggleTranslate],
[ShortcutKey.ToggleDictation, toggleDictation],
[ShortcutKey.OpenSetting, openSetting],
[ShortcutKey.OpenDictDetail, openDictDetail],
[ShortcutKey.ToggleTheme, toggleTheme],
[ShortcutKey.ToggleConciseMode, toggleConciseMode],
[ShortcutKey.TogglePanel, togglePanel],
])
useEvent(ShortcutKey.RepeatChapter, repeat)
useEvent(ShortcutKey.ToggleShowTranslate, toggleTranslate)
useEvent(ShortcutKey.ToggleDictation, toggleDictation)
useEvent(ShortcutKey.OpenSetting, openSetting)
useEvent(ShortcutKey.OpenDictDetail, openDictDetail)
useEvent(ShortcutKey.ToggleTheme, toggleTheme)
useEvent(ShortcutKey.ToggleConciseMode, toggleConciseMode)
useEvent(ShortcutKey.TogglePanel, togglePanel)
let data = $ref({
new: [],

View File

@@ -6,10 +6,10 @@ import "vue-activity-calendar/style.css";
import {useRouter} from "vue-router";
import BaseIcon from "@/components/BaseIcon.vue";
import Dialog from "@/pages/pc/components/dialog/Dialog.vue";
import {_getAccomplishDate, _getAccomplishDays, _getStudyProgress, useNav} from "@/utils";
import {_getAccomplishDate, _getAccomplishDays, useNav} from "@/utils";
import BasePage from "@/pages/pc/components/BasePage.vue";
import {getDefaultDict} from "@/types.ts";
import {onMounted, watch} from "vue";
import {onMounted} from "vue";
import {getCurrentStudyWord} from "@/hooks/dict.ts";
import {usePracticeStore} from "@/stores/practice.ts";
import {EventKey, useEvent} from "@/utils/eventBus.ts";
@@ -168,6 +168,7 @@ function changePerDayStudyNumber() {
<div class="text-2xl ml-2 flex gap-4">
<BaseIcon title="删除" icon="hugeicons:delete-02" @click="store.delWordDict(i)"/>
<BaseIcon title="学习" icon="nonicons:go-16" @click="store.changeWordDict(getDefaultDict(i))"/>
<BaseIcon title="修改" icon="nonicons:go-16" @click="nav('edit-word-dict',{type:-1},i)"/>
</div>
</div>
<div class="mt-5 text-sm">已学习5555个单词的1%</div>

View File

@@ -39,20 +39,20 @@ export const routes: RouteRecordRaw[] = [
]
},
{path: '/mobile', component: Mobile,},
{path: '/mobile/practice', component: MobilePractice},
{path: '/mobile/dict-detail', component: DictDetail},
{path: '/mobile/set-dict-plan', name: 'set-dict-plan', component: SetDictPlan},
{path: '/mobile/setting', component: Setting},
{path: '/mobile/music-setting', component: MusicSetting},
{path: '/mobile/other-setting', component: OtherSetting},
{path: '/mobile/data-manage', component: DataManage},
{path: '/mobile/collect', component: CollectPage},
{path: '/mobile/wrong', component: WrongPage},
{path: '/mobile/simple', component: SimplePage},
{path: '/mobile/about', component: About},
{path: '/mobile/feedback', component: Feedback},
{path: '/test', component: Test},
// {path: '/mobile', component: Mobile,},
// {path: '/mobile/practice', component: MobilePractice},
// {path: '/mobile/dict-detail', component: DictDetail},
// {path: '/mobile/set-dict-plan', name: 'set-dict-plan', component: SetDictPlan},
// {path: '/mobile/setting', component: Setting},
// {path: '/mobile/music-setting', component: MusicSetting},
// {path: '/mobile/other-setting', component: OtherSetting},
// {path: '/mobile/data-manage', component: DataManage},
// {path: '/mobile/collect', component: CollectPage},
// {path: '/mobile/wrong', component: WrongPage},
// {path: '/mobile/simple', component: SimplePage},
// {path: '/mobile/about', component: About},
// {path: '/mobile/feedback', component: Feedback},
// {path: '/test', component: Test},
// {path: '/', redirect: '/pc/practice'},
]

View File

@@ -247,20 +247,20 @@ export const useBaseStore = defineStore('base', {
return this.myDictList[0]
},
collectWord(): Dict {
return this.commonDictList[0]
},
collectArticle(): Dict {
return this.commonDictList[1]
},
simple(): Dict {
collectArticle(): Dict {
return this.commonDictList[2]
},
wrong(): Dict {
simple(): Dict {
return this.commonDictList[3]
},
master(): Dict {
wrong(): Dict {
return this.commonDictList[4]
},
master(): Dict {
return this.commonDictList[5]
},
skipWordNames() {
return this.simple.words.map(v => v.word.toLowerCase())
},
@@ -344,7 +344,7 @@ export const useBaseStore = defineStore('base', {
}))
}
console.log('this.wordDictList', this.wordDictList[0].words[0])
// console.log('this.wordDictList', this.wordDictList[0].words[0])
}
emitter.emit(EventKey.changeDict)
resolve(true)

View File

@@ -8,7 +8,7 @@ export interface PracticeState {
index: number,//当前输入的第几个用于和total计算进度
newWordNumber: number,
inputWordNumber: number,//当前总输入了多少个单词(不包含跳过)
wrongWordNumber: number,
wrong: number,
correctRate: number,
startIndex:number,
endIndex:number,
@@ -27,7 +27,7 @@ export const usePracticeStore = defineStore('practice', {
endIndex: 0,
newWordNumber: 0,
inputWordNumber: 0,
wrongWordNumber: 0,
wrong: 0,
}
},
})

View File

@@ -136,14 +136,10 @@ export const DefaultArticle: Article = {
}
export interface Statistics {
startIndex: number,
endIndex: number,
startDate: number,//开始日期
endDate: number//结束日期
spend: number,//花费时间
total: number//单词数量
wrongWordNumber: number//错误数
correctRate: number//正确率
wrong: number//错误数
}
export interface DisplayStatistics extends Statistics {
@@ -152,14 +148,10 @@ export interface DisplayStatistics extends Statistics {
}
export const DefaultDisplayStatistics: DisplayStatistics = {
startIndex: -1,
endIndex: -1,
startDate: Date.now(),
endDate: -1,
spend: -1,
total: -1,
correctRate: -1,
wrongWordNumber: -1,
wrong: -1,
inputWordNumber: -1,
wrongWords: [],
}

View File

@@ -114,19 +114,11 @@ export function checkAndUpgradeSaveSetting(val: any) {
//筛选未自定义的词典,未自定义的词典不需要保存单词,用的时候再下载
export function shakeCommonDict(n: BaseState): BaseState {
let data: BaseState = cloneDeep(n)
data.myDictList.map((v: Dict) => {
if (v.isCustom) {
if (v.type === DictType.article) {
v.articles.map(s => {
delete s.sections
})
}
} else {
if (v.type === DictType.word) v.originWords = []
if (v.type === DictType.article) v.articles = []
v.words = []
v.chapterWords = []
}
data.wordDictList.map((v: Dict) => {
if (!v.isCustom) v.words = []
})
data.articleDictList.map((v: Dict) => {
if (!v.isCustom) v.articles = []
})
return data
}
@@ -172,39 +164,13 @@ export function useNav() {
return {nav, back: router.back}
}
export function _dateFormat(val, type?): string {
export function _dateFormat(val: any, format?: string): string {
if (!val) return
if (String(val).length === 10) {
val = val * 1000
}
const d = new Date(Number(val))
const year = d.getFullYear()
const m = d.getMonth() + 1
const mStr = m < 10 ? '0' + m : m
const day = d.getDate()
const dayStr = day < 10 ? '0' + day : day
const h = d.getHours()
const hStr = h < 10 ? '0' + h : h
const min = d.getMinutes()
const minStr = min < 10 ? '0' + min : min
const sec = d.getSeconds()
const secStr = sec < 10 ? '0' + sec : sec
switch (type) {
case 'Y':
return year + ''
case 'M':
return `${year}-${mStr}`
case 'M_D':
return `${mStr}-${dayStr}`
case 'M_CN':
return `${year}${mStr}`
case 'D':
return `${year}-${mStr}-${dayStr}`
case 'm':
return `${year}-${mStr}-${dayStr} ${hStr}:${minStr}`
default:
return `${year}-${mStr}-${dayStr} ${hStr}:${minStr}:${secStr}`
}
return dayjs(d).format(format)
}
export async function _checkDictWords(dict: Dict) {

4
src/vite-env.d.ts vendored
View File

@@ -3,7 +3,6 @@
import {ElMessageBox} from "element-plus";
// declare module '*.json' {
// const src: string
// export default src
@@ -51,6 +50,3 @@ declare module "*.vue" {
// }
declare var ElMessageBox: ElMessageBox;
/// <reference types="vite/client" />
/// <reference types="vue/macros-global" />

View File

@@ -7,9 +7,8 @@ import {ElementPlusResolver} from "unplugin-vue-components/resolvers";
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import {getLastCommit} from "git-last-commit";
import DefineOptions from 'unplugin-vue-define-options/vite' // 引入插件
import UnoCSS from 'unocss/vite'
import ReactivityTransform from '@vue-macros/reactivity-transform/vite'
import VueMacros from 'unplugin-vue-macros/vite'
function pathResolve(dir: string) {
return resolve(__dirname, ".", dir)
@@ -25,18 +24,19 @@ export default defineConfig(async () => {
})
return {
plugins: [
Vue(),
VueJsx(),
VueMacros({
plugins: {
vue: Vue(),
vueJsx: VueJsx(), // 如果需要
},
}),
UnoCSS(),
ReactivityTransform(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
//用于给setup组件定义名字的keep-alive需要name才能正常工作
DefineOptions(),
lifecycle === 'report' ?
visualizer({
gzipSize: true,