save
This commit is contained in:
2
components.d.ts
vendored
2
components.d.ts
vendored
@@ -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']
|
||||
|
||||
@@ -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
12
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
@@ -156,7 +156,7 @@ export function getCurrentStudyWord() {
|
||||
})
|
||||
}
|
||||
|
||||
console.timeEnd()
|
||||
console.log('data', data)
|
||||
// console.timeEnd()
|
||||
// console.log('data', data)
|
||||
return data
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -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++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -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 = '正在'
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
282
src/pages/pc/word/EditWordDict_old.vue
Normal file
282
src/pages/pc/word/EditWordDict_old.vue
Normal 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>
|
||||
377
src/pages/pc/word/EditWordDict_two.vue
Normal file
377
src/pages/pc/word/EditWordDict_two.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
正确
|
||||
|
||||
@@ -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: [],
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'},
|
||||
]
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
},
|
||||
})
|
||||
12
src/types.ts
12
src/types.ts
@@ -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: [],
|
||||
}
|
||||
|
||||
@@ -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
4
src/vite-env.d.ts
vendored
@@ -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" />
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user