feat:重构代码
This commit is contained in:
@@ -89,7 +89,6 @@ export function usePlayCorrect() {
|
||||
|
||||
export function usePlayWordAudio() {
|
||||
const settingStore = useSettingStore()
|
||||
const store = useBaseStore()
|
||||
const audio = $ref(new Audio())
|
||||
|
||||
function playAudio(word: string) {
|
||||
@@ -99,12 +98,6 @@ export function usePlayWordAudio() {
|
||||
} else if (settingStore.wordSoundType === 'us') {
|
||||
url = `${PronunciationApi}${word}&type=2`
|
||||
}
|
||||
if (store.currentDict.language === 'ja') {
|
||||
url += '&le=jap'
|
||||
}
|
||||
if (store.currentDict.language === 'de') {
|
||||
url += '&le=de'
|
||||
}
|
||||
audio.src = url
|
||||
audio.volume = settingStore.wordSoundVolume / 100
|
||||
audio.playbackRate = settingStore.wordSoundSpeed
|
||||
@@ -160,7 +153,6 @@ export function useWatchAllSound() {
|
||||
watch([
|
||||
() => settingStore.wordSound,
|
||||
() => settingStore.keyboardSound,
|
||||
() => settingStore.translateSound,
|
||||
() => settingStore.effectSound,
|
||||
], (n) => {
|
||||
settingStore.allSound = n.some(v => v);
|
||||
@@ -173,6 +165,5 @@ export function useChangeAllSound(e: boolean) {
|
||||
settingStore.allSound = e
|
||||
settingStore.wordSound = e
|
||||
settingStore.keyboardSound = e
|
||||
settingStore.translateSound = e
|
||||
settingStore.effectSound = e
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,6 +228,7 @@ const store = useBaseStore()
|
||||
border-bottom: 1px solid #c4c3c3;
|
||||
}
|
||||
}
|
||||
|
||||
.el-option-row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
@@ -239,4 +240,4 @@ const store = useBaseStore()
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -18,36 +18,12 @@ defineOptions({
|
||||
name: 'PracticeWord'
|
||||
})
|
||||
|
||||
const statisticsStore = usePracticeStore()
|
||||
const store = useBaseStore()
|
||||
const settingStore = useSettingStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const {toggleTheme} = useTheme()
|
||||
const practiceRef: any = $ref()
|
||||
|
||||
watch(statisticsStore, () => {
|
||||
if (statisticsStore.inputWordNumber < 1) {
|
||||
return statisticsStore.correctRate = -1
|
||||
}
|
||||
if (statisticsStore.wrong > statisticsStore.inputWordNumber) {
|
||||
return statisticsStore.correctRate = 0
|
||||
}
|
||||
statisticsStore.correctRate = 100 - Math.trunc(((statisticsStore.wrong) / (statisticsStore.inputWordNumber)) * 100)
|
||||
})
|
||||
|
||||
|
||||
function test() {
|
||||
MessageBox.confirm(
|
||||
'2您选择了“本地翻译”,但译文内容却为空白,是否修改为“不需要翻译”并保存?',
|
||||
'1提示',
|
||||
() => {
|
||||
console.log('ok')
|
||||
},
|
||||
() => {
|
||||
console.log('cencal')
|
||||
})
|
||||
}
|
||||
|
||||
function write() {
|
||||
// console.log('write')
|
||||
settingStore.dictation = true
|
||||
@@ -83,10 +59,6 @@ function openSetting() {
|
||||
runtimeStore.showSettingModal = true
|
||||
}
|
||||
|
||||
function openDictDetail() {
|
||||
emitter.emit(EventKey.openDictModal, 'detail')
|
||||
}
|
||||
|
||||
function toggleConciseMode() {
|
||||
settingStore.showToolbar = !settingStore.showToolbar
|
||||
settingStore.showPanel = settingStore.showToolbar
|
||||
@@ -112,7 +84,6 @@ onMounted(() => {
|
||||
emitter.on(ShortcutKey.ToggleShowTranslate, toggleShowTranslate)
|
||||
emitter.on(ShortcutKey.ToggleDictation, toggleDictation)
|
||||
emitter.on(ShortcutKey.OpenSetting, openSetting)
|
||||
emitter.on(ShortcutKey.OpenDictDetail, openDictDetail)
|
||||
emitter.on(ShortcutKey.ToggleTheme, toggleTheme)
|
||||
emitter.on(ShortcutKey.ToggleConciseMode, toggleConciseMode)
|
||||
emitter.on(ShortcutKey.TogglePanel, togglePanel)
|
||||
@@ -129,7 +100,6 @@ onUnmounted(() => {
|
||||
emitter.off(ShortcutKey.ToggleShowTranslate, toggleShowTranslate)
|
||||
emitter.off(ShortcutKey.ToggleDictation, toggleDictation)
|
||||
emitter.off(ShortcutKey.OpenSetting, openSetting)
|
||||
emitter.off(ShortcutKey.OpenDictDetail, openDictDetail)
|
||||
emitter.off(ShortcutKey.ToggleTheme, toggleTheme)
|
||||
emitter.off(ShortcutKey.ToggleConciseMode, toggleConciseMode)
|
||||
emitter.off(ShortcutKey.TogglePanel, togglePanel)
|
||||
@@ -146,4 +116,4 @@ useStartKeyboardEventListener()
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -67,7 +67,6 @@ watch(() => props.words, () => {
|
||||
data.wrongWords = []
|
||||
|
||||
statisticsStore.startDate = Date.now()
|
||||
statisticsStore.correctRate = -1
|
||||
statisticsStore.inputWordNumber = 0
|
||||
statisticsStore.wrong = 0
|
||||
stat = cloneDeep(DefaultDisplayStatistics)
|
||||
@@ -320,7 +319,6 @@ onMounted(() => {
|
||||
<div class="title">
|
||||
</div>
|
||||
<BaseIcon title="切换词典"
|
||||
@click="emitter.emit(EventKey.openDictModal,'list')"
|
||||
icon="carbon:change-catalog"/>
|
||||
<div style="position:relative;"
|
||||
@click.stop="null">
|
||||
@@ -455,4 +453,4 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -11,39 +11,15 @@ import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import {MessageBox} from "@/utils/MessageBox.tsx";
|
||||
import PracticeArticle from "@/pages/pc/article/practice-article/index.vue";
|
||||
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";
|
||||
|
||||
const statisticsStore = usePracticeStore()
|
||||
const store = useBaseStore()
|
||||
const settingStore = useSettingStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const {toggleTheme} = useTheme()
|
||||
const practiceRef: any = $ref()
|
||||
|
||||
watch(statisticsStore, () => {
|
||||
if (statisticsStore.inputWordNumber < 1) {
|
||||
return statisticsStore.correctRate = -1
|
||||
}
|
||||
if (statisticsStore.wrong > statisticsStore.inputWordNumber) {
|
||||
return statisticsStore.correctRate = 0
|
||||
}
|
||||
statisticsStore.correctRate = 100 - Math.trunc(((statisticsStore.wrong) / (statisticsStore.inputWordNumber)) * 100)
|
||||
})
|
||||
|
||||
|
||||
function test() {
|
||||
MessageBox.confirm(
|
||||
'2您选择了“本地翻译”,但译文内容却为空白,是否修改为“不需要翻译”并保存?',
|
||||
'1提示',
|
||||
() => {
|
||||
console.log('ok')
|
||||
},
|
||||
() => {
|
||||
console.log('cencal')
|
||||
})
|
||||
}
|
||||
|
||||
function write() {
|
||||
// console.log('write')
|
||||
@@ -80,10 +56,6 @@ function openSetting() {
|
||||
runtimeStore.showSettingModal = true
|
||||
}
|
||||
|
||||
function openDictDetail() {
|
||||
emitter.emit(EventKey.openDictModal, 'detail')
|
||||
}
|
||||
|
||||
function toggleConciseMode() {
|
||||
settingStore.showToolbar = !settingStore.showToolbar
|
||||
settingStore.showPanel = settingStore.showToolbar
|
||||
@@ -109,7 +81,6 @@ onMounted(() => {
|
||||
emitter.on(ShortcutKey.ToggleShowTranslate, toggleShowTranslate)
|
||||
emitter.on(ShortcutKey.ToggleDictation, toggleDictation)
|
||||
emitter.on(ShortcutKey.OpenSetting, openSetting)
|
||||
emitter.on(ShortcutKey.OpenDictDetail, openDictDetail)
|
||||
emitter.on(ShortcutKey.ToggleTheme, toggleTheme)
|
||||
emitter.on(ShortcutKey.ToggleConciseMode, toggleConciseMode)
|
||||
emitter.on(ShortcutKey.TogglePanel, togglePanel)
|
||||
@@ -126,7 +97,6 @@ onUnmounted(() => {
|
||||
emitter.off(ShortcutKey.ToggleShowTranslate, toggleShowTranslate)
|
||||
emitter.off(ShortcutKey.ToggleDictation, toggleDictation)
|
||||
emitter.off(ShortcutKey.OpenSetting, openSetting)
|
||||
emitter.off(ShortcutKey.OpenDictDetail, openDictDetail)
|
||||
emitter.off(ShortcutKey.ToggleTheme, toggleTheme)
|
||||
emitter.off(ShortcutKey.ToggleConciseMode, toggleConciseMode)
|
||||
emitter.off(ShortcutKey.TogglePanel, togglePanel)
|
||||
@@ -137,7 +107,6 @@ useStartKeyboardEventListener()
|
||||
</script>
|
||||
<template>
|
||||
<PracticeArticle ref="practiceRef"/>
|
||||
<DictModal/>
|
||||
<Statistics/>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css'
|
||||
import ContextMenu from '@imengyu/vue3-context-menu'
|
||||
import {getTranslateText} from "@/hooks/article.ts";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import QuestionForm from "@/pages/pc/components/QuestionForm.vue";
|
||||
import QuestionForm from "@/pages/pc/article/components/QuestionForm.vue";
|
||||
|
||||
interface IProps {
|
||||
article: Article,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import TypingArticle from "./TypingArticle.vue";
|
||||
import {Article, ArticleItem, ArticleWord, DisplayStatistics, getDefaultArticle, ShortcutKey, Word} from "@/types.ts";
|
||||
import {cloneDeep} from "lodash-es";
|
||||
import TypingWord from "@/pages/pc/components/TypingWord.vue";
|
||||
import TypingWord from "@/pages/pc/word/components/TypingWord.vue";
|
||||
import Panel from "../../components/Panel.vue";
|
||||
import {onMounted, onUnmounted} from "vue";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
@@ -277,32 +277,16 @@ const {playSentenceAudio} = usePlaySentenceAudio()
|
||||
<template>
|
||||
<div class="practice-wrapper">
|
||||
<div class="practice-article">
|
||||
<div class="swiper-wrapper">
|
||||
<div class="swiper-list" :class="`step${tabIndex}`">
|
||||
<div class="swiper-item">
|
||||
<TypingArticle
|
||||
ref="typingArticleRef"
|
||||
:active="tabIndex === 0"
|
||||
@edit="edit"
|
||||
@wrong="wrong"
|
||||
@over="skip"
|
||||
@nextWord="nextWord"
|
||||
@play="e => playSentenceAudio(e,audioRef,articleData.article)"
|
||||
:article="articleData.article"
|
||||
/>
|
||||
</div>
|
||||
<div class="swiper-item">
|
||||
<div class="typing-word-wrapper">
|
||||
<TypingWord
|
||||
@sort="sort"
|
||||
:words="wordData.words"
|
||||
:index="wordData.index"
|
||||
v-if="tabIndex === 1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TypingArticle
|
||||
ref="typingArticleRef"
|
||||
:active="tabIndex === 0"
|
||||
@edit="edit"
|
||||
@wrong="wrong"
|
||||
@over="skip"
|
||||
@nextWord="nextWord"
|
||||
@play="e => playSentenceAudio(e,audioRef,articleData.article)"
|
||||
:article="articleData.article"
|
||||
/>
|
||||
|
||||
<Teleport to="body">
|
||||
<div class="panel-wrapper">
|
||||
@@ -312,7 +296,6 @@ const {playSentenceAudio} = usePlaySentenceAudio()
|
||||
<div class="list-header">
|
||||
<div class="left">
|
||||
<BaseIcon title="切换词典"
|
||||
@click="emitter.emit(EventKey.openDictModal,'list')"
|
||||
icon="carbon:change-catalog"/>
|
||||
<div class="title">
|
||||
{{ store.currentBook.name }}
|
||||
@@ -392,11 +375,6 @@ const {playSentenceAudio} = usePlaySentenceAudio()
|
||||
<div class="line"></div>
|
||||
<div class="name">错误数</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="num">{{ format(statisticsStore.correctRate, '%') }}</div>
|
||||
<div class="line"></div>
|
||||
<div class="name">正确率</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center gap-1">
|
||||
|
||||
@@ -1,262 +0,0 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<canvas ref="canvas"/>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted} from "vue";
|
||||
import {getRandom} from "@/utils/index.ts";
|
||||
import boom from '@/assets/sound/boom.mp3'
|
||||
import shotfire from '@/assets/sound/shotfire.mp3'
|
||||
import {useSound} from "@/hooks/sound.ts";
|
||||
|
||||
const canvas = $ref()
|
||||
const {play: playBoom} = useSound([boom], 3)
|
||||
const {play: playShotFire} = useSound([shotfire], 3)
|
||||
|
||||
onMounted(() => {
|
||||
let ctx = canvas.getContext("2d");
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
let bigBooms = [];
|
||||
let lastTime;
|
||||
|
||||
let raf = window.requestAnimationFrame
|
||||
let count = 0
|
||||
let isBreak = false
|
||||
|
||||
function initAnimate() {
|
||||
lastTime = new Date();
|
||||
animate();
|
||||
}
|
||||
|
||||
initAnimate()
|
||||
|
||||
function animate() {
|
||||
ctx.save();
|
||||
ctx.globalCompositeOperation = "destination-out";
|
||||
ctx.globalAlpha = 0.1;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.restore();
|
||||
|
||||
let newTime = new Date();
|
||||
if (newTime - lastTime > 200 + (window.innerHeight - 767) / 2 && count < 11) {
|
||||
let boomArea
|
||||
let startX
|
||||
if (count % 2 === 0) {
|
||||
startX = getRandom(0, canvas.width * 0.1);
|
||||
// startX = getRandom(500, 700);
|
||||
boomArea = {
|
||||
x: getRandom(canvas.width * 0.1, canvas.width * 0.3),
|
||||
y: getRandom(50, 200)
|
||||
}
|
||||
} else {
|
||||
startX = getRandom(canvas.width * 0.9, canvas.width);
|
||||
boomArea = {
|
||||
x: getRandom(canvas.width * 0.7, canvas.width * 0.9),
|
||||
y: getRandom(50, 200)
|
||||
}
|
||||
}
|
||||
|
||||
let bigBoom = new Boom(
|
||||
startX,
|
||||
2,
|
||||
"#FFF",
|
||||
boomArea
|
||||
);
|
||||
bigBooms.push(bigBoom);
|
||||
lastTime = newTime;
|
||||
count++
|
||||
}
|
||||
|
||||
bigBooms.map((itemI) => {
|
||||
if (!itemI.dead) {
|
||||
itemI._move();
|
||||
} else {
|
||||
itemI.booms.map((itemJ, index) => {
|
||||
if (!itemJ.dead) {
|
||||
itemJ.moveTo();
|
||||
} else if (index === itemI.booms.length - 1) {
|
||||
bigBooms.splice(bigBooms.indexOf(itemI), 1);
|
||||
}
|
||||
});
|
||||
if (bigBooms.length === 0) {
|
||||
setTimeout(() => {
|
||||
isBreak = true
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!isBreak) {
|
||||
raf(animate);
|
||||
} else {
|
||||
canvas.style.display = 'none'
|
||||
}
|
||||
}
|
||||
|
||||
class Boom {
|
||||
booms = [];
|
||||
x;
|
||||
y;
|
||||
r;
|
||||
color;
|
||||
shape;
|
||||
boomArea;
|
||||
theta;
|
||||
dead;
|
||||
ba;
|
||||
|
||||
constructor(x, r, color, boomArea, shape) {
|
||||
this.x = x;
|
||||
this.y = canvas.height + r;
|
||||
this.r = r;
|
||||
// console.log(this.x, this.y, this.r, boomArea)
|
||||
this.color = color;
|
||||
this.shape = shape || false;
|
||||
this.boomArea = boomArea;
|
||||
this.theta = 0;
|
||||
this.dead = false;
|
||||
this.ba = getRandom(80, 200);
|
||||
|
||||
// playShotFire()
|
||||
}
|
||||
|
||||
_move() {
|
||||
let dx = this.boomArea.x - this.x,
|
||||
dy = this.boomArea.y - this.y;
|
||||
this.x = this.x + dx * 0.01;
|
||||
this.y = this.y + dy * 0.01;
|
||||
// console.log(this.x, this.y, dx, this.ba)
|
||||
|
||||
if (Math.abs(dx) <= this.ba && Math.abs(dy) <= this.ba) {
|
||||
this._boom();
|
||||
this.dead = true;
|
||||
} else {
|
||||
this._paint();
|
||||
this._drawLight();
|
||||
}
|
||||
}
|
||||
|
||||
_paint() {
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
|
||||
ctx.fillStyle = this.color;
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
_drawLight() {
|
||||
ctx.save();
|
||||
ctx.fillStyle = "rgba(255,228,150,0.3)";
|
||||
ctx.beginPath();
|
||||
ctx.arc(
|
||||
this.x,
|
||||
this.y,
|
||||
this.r + 3 * Math.random() + 1,
|
||||
0,
|
||||
2 * Math.PI
|
||||
);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
_boom() {
|
||||
let fireNum = getRandom(100, 300);
|
||||
let style = getRandom(0, 10) >= 5 ? 1 : 2;
|
||||
let color;
|
||||
if (style === 1) {
|
||||
color = {
|
||||
a: getRandom(128, 255),
|
||||
b: getRandom(128, 255),
|
||||
c: getRandom(128, 255),
|
||||
};
|
||||
}
|
||||
|
||||
let fanwei = fireNum;
|
||||
// playBoom()
|
||||
|
||||
for (let i = 0; i < fireNum; i++) {
|
||||
if (style === 2) {
|
||||
color = {
|
||||
a: getRandom(128, 255),
|
||||
b: getRandom(128, 255),
|
||||
c: getRandom(128, 255),
|
||||
};
|
||||
}
|
||||
let a = getRandom(-Math.PI, Math.PI);
|
||||
let x = getRandom(0, fanwei) * Math.cos(a) + this.x;
|
||||
let y = getRandom(0, fanwei) * Math.sin(a) + this.y;
|
||||
let radius = getRandom(0, 2);
|
||||
let frag = new Firework(this.x, this.y, radius, color, x, y);
|
||||
this.booms.push(frag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Firework {
|
||||
tx = 0;
|
||||
ty = 0;
|
||||
x = 0;
|
||||
y = 0;
|
||||
dead = false;
|
||||
centerX = 0;
|
||||
centerY = 0;
|
||||
radius = 0;
|
||||
color = 0;
|
||||
|
||||
constructor(x, y, radius, color, tx, ty) {
|
||||
this.tx = tx;
|
||||
this.ty = ty;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.dead = false;
|
||||
this.centerX = x;
|
||||
this.centerY = y;
|
||||
this.radius = radius;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
paint() {
|
||||
ctx.fillStyle = `rgba(${this.color.a},${this.color.b},${this.color.c})`;
|
||||
ctx.fillRect(
|
||||
this.x - this.radius,
|
||||
this.y - this.radius,
|
||||
this.radius * 2,
|
||||
this.radius * 2
|
||||
);
|
||||
}
|
||||
|
||||
moveTo() {
|
||||
this.ty = this.ty + 0.3;
|
||||
let dx = this.tx - this.x,
|
||||
dy = this.ty - this.y;
|
||||
this.x = Math.abs(dx) < 0.1 ? this.tx : this.x + dx * 0.1;
|
||||
this.y = Math.abs(dy) < 0.1 ? this.ty : this.y + dy * 0.1;
|
||||
if (dx === 0 && Math.abs(dy) <= 80) {
|
||||
this.dead = true;
|
||||
}
|
||||
this.paint();
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
canvas {
|
||||
z-index: 99999;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: transparent;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -242,26 +242,8 @@ function importData(e) {
|
||||
</div>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<!-- <div class="row">-->
|
||||
<!-- <label class="item-title">释义发音</label>-->
|
||||
<!-- <div class="wrapper">-->
|
||||
<!-- <el-switch v-model="settingStore.translateSound"-->
|
||||
<!-- inline-prompt-->
|
||||
<!-- active-text="开"-->
|
||||
<!-- inactive-text="关"-->
|
||||
<!-- />-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="row">-->
|
||||
<!-- <label class="sub-title">音量</label>-->
|
||||
<!-- <div class="wrapper">-->
|
||||
<!-- <el-slider v-model="settingStore.translateSoundVolume"/>-->
|
||||
<!-- <span>{{ settingStore.translateSoundVolume }}%</span>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<div class="line"></div>
|
||||
<div class="row">
|
||||
<label class="item-title">效果音(章节结算页烟花音效)</label>
|
||||
<label class="item-title">效果音(输入错误、完成时的音效)</label>
|
||||
<div class="wrapper">
|
||||
<el-switch v-model="settingStore.effectSound"
|
||||
inline-prompt
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import {useBaseStore} from "@/stores/base.ts"
|
||||
import "vue-activity-calendar/style.css";
|
||||
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()
|
||||
|
||||
let show = $ref(false)
|
||||
|
||||
function showAllWordModal() {
|
||||
emitter.emit(EventKey.openWordListModal, {
|
||||
title: store.sdict.name,
|
||||
translateLanguage: store.sdict.translateLanguage,
|
||||
list: store.sdict.words
|
||||
})
|
||||
}
|
||||
|
||||
useEvent(EventKey.openDictModal, () => {
|
||||
show = true
|
||||
})
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
<Dialog
|
||||
v-model="show"
|
||||
:title="store.sdict.name "
|
||||
>
|
||||
<div id="DictDialog">
|
||||
<div class="detail">
|
||||
<div class="desc">{{ store.sdict.description }}</div>
|
||||
<div class="text flex items-center gap-2">
|
||||
<div>总词汇: {{ store.sdict.words.length }}词</div>
|
||||
<BaseIcon icon="circum:view-list"
|
||||
@click='showAllWordModal'
|
||||
title="单词列表"
|
||||
/>
|
||||
</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 }}%
|
||||
<span>{{ store.sdict.lastLearnIndex }} / {{
|
||||
store.sdict.words.length
|
||||
}}</span>
|
||||
</div>
|
||||
<el-progress class="mt-1" :percentage="store.currentStudyProgress" :show-text="false"></el-progress>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
<WordListDialog/>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
#DictDialog {
|
||||
width: 20rem;
|
||||
|
||||
.detail {
|
||||
color: var(--color-font-1);
|
||||
gap: .2rem;
|
||||
position: relative;
|
||||
font-size: .9rem;
|
||||
padding: var(--space);
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import MiniDialog from "@/pages/pc/components/dialog/MiniDialog.vue";
|
||||
import {Icon} from "@iconify/vue";
|
||||
import IconWrapper from "@/pages/pc/components/IconWrapper.vue";
|
||||
import Tooltip from "@/pages/pc/components/Tooltip.vue";
|
||||
import {useWindowClick} from "@/hooks/event.ts";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
|
||||
import {getAudioFileUrl, useChangeAllSound, usePlayAudio, useWatchAllSound} from "@/hooks/sound.ts";
|
||||
import {SoundFileOptions} from "@/utils/const.ts";
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
let show = $ref(false)
|
||||
|
||||
useWindowClick(() => show = false)
|
||||
useWatchAllSound()
|
||||
|
||||
let timer = 0
|
||||
|
||||
function toggle(val: boolean) {
|
||||
clearTimeout(timer)
|
||||
if (val) {
|
||||
emitter.emit(EventKey.closeOther)
|
||||
show = val
|
||||
} else {
|
||||
timer = setTimeout(() => {
|
||||
show = val
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function toggle2() {
|
||||
if (!show) {
|
||||
emitter.emit(EventKey.closeOther)
|
||||
}
|
||||
show = !show
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="setting"
|
||||
@click.stop="null"
|
||||
>
|
||||
<Tooltip title="音效设置">
|
||||
<IconWrapper>
|
||||
<Icon v-if="settingStore.allSound" icon="icon-park-outline:volume-notice"
|
||||
@click="toggle2()"
|
||||
/>
|
||||
<Icon v-else icon="icon-park-outline:volume-mute"
|
||||
@click="toggle2()"
|
||||
/>
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
<MiniDialog
|
||||
width="12rem"
|
||||
v-model="show">
|
||||
<div class="mini-row-title">
|
||||
音效设置
|
||||
</div>
|
||||
<div class="mini-row">
|
||||
<label class="item-title">所有音效</label>
|
||||
<div class="wrapper">
|
||||
<el-switch v-model="settingStore.allSound"
|
||||
@change="useChangeAllSound"
|
||||
inline-prompt
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mini-row">
|
||||
<label class="item-title">自动发音</label>
|
||||
<div class="wrapper">
|
||||
<el-switch v-model="settingStore.wordSound"
|
||||
inline-prompt
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mini-row">
|
||||
<label class="item-title">口音</label>
|
||||
<div class="wrapper">
|
||||
<el-select v-model="settingStore.wordSoundType"
|
||||
placeholder="请选择"
|
||||
size="small">
|
||||
<el-option label="美音" value="us"/>
|
||||
<el-option label="英音" value="uk"/>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mini-row">
|
||||
<label class="item-title">按键音</label>
|
||||
<div class="wrapper">
|
||||
<el-switch v-model="settingStore.keyboardSound"
|
||||
inline-prompt
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mini-row">
|
||||
<label class="item-title">按键音效</label>
|
||||
<div class="wrapper">
|
||||
<el-select v-model="settingStore.keyboardSoundFile"
|
||||
placeholder="请选择"
|
||||
size="small">
|
||||
<el-option
|
||||
v-for="item in SoundFileOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
>
|
||||
<div class="el-option-row">
|
||||
<span>{{ item.label }}</span>
|
||||
<VolumeIcon
|
||||
:time="100"
|
||||
@click="usePlayAudio(getAudioFileUrl(item.value)[0])"/>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mini-row">
|
||||
<label class="item-title">效果音</label>
|
||||
<div class="wrapper">
|
||||
<el-switch v-model="settingStore.effectSound"
|
||||
inline-prompt
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</MiniDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
|
||||
.wrapper {
|
||||
width: 100rem;
|
||||
position: relative;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.setting {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.el-option-row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.icon-wrapper {
|
||||
transform: translateX(10rem);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,189 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {useRouter} from "vue-router";
|
||||
import BasePage from "@/pages/pc/components/BasePage.vue";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import DictGroup from "@/pages/pc/components/list/DictGroup.vue";
|
||||
import enFlag from "@/assets/img/flags/en.png";
|
||||
import jaFlag from "@/assets/img/flags/ja.png";
|
||||
import deFlag from "@/assets/img/flags/de.png";
|
||||
import codeFlag from "@/assets/img/flags/code.png";
|
||||
import {getWordDictList, useNav} from "@/utils";
|
||||
import {getDefaultDict, Sort} from "@/types.ts";
|
||||
import {onMounted} from "vue";
|
||||
import {groupBy, uniq} from "lodash-es";
|
||||
|
||||
const base = useBaseStore()
|
||||
const router = useRouter()
|
||||
const store = useBaseStore()
|
||||
|
||||
const languageCategoryOptions = [
|
||||
{id: 'en', name: '英语', flag: enFlag},
|
||||
{id: 'ja', name: '日语', flag: jaFlag},
|
||||
{id: 'de', name: '德语', flag: deFlag},
|
||||
{id: 'code', name: 'Code', flag: codeFlag},
|
||||
]
|
||||
|
||||
const {nav, back} = useNav()
|
||||
|
||||
function change(e) {
|
||||
console.log('e', e.dict)
|
||||
store.changeWordDict(getDefaultDict(e.dict))
|
||||
ElMessage.success('切换成功')
|
||||
back()
|
||||
}
|
||||
|
||||
let dictData = $ref({})
|
||||
let currentTabIndex = $ref('0')
|
||||
let currentTranslateLanguage2 = $ref(1)
|
||||
const currentLangDictList = $computed(() => {
|
||||
return dictData[currentTabIndex] ?? {}
|
||||
})
|
||||
|
||||
const currentTranDictList = $computed(() => {
|
||||
return currentLangDictList[currentTranslateLanguage2] ?? {}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
let res = await getWordDictList()
|
||||
let d: any = groupBy(res, 'langType')
|
||||
for (let dKey in d) {
|
||||
d[dKey] = groupBy(d[dKey], 'tranType')
|
||||
for (const dKey2 in d[dKey]) {
|
||||
d[dKey][dKey2] = groupBy(d[dKey][dKey2], 'category')
|
||||
for (const dKey3 in d[dKey][dKey2]) {
|
||||
d[dKey][dKey2][dKey3] = {
|
||||
tags: uniq(d[dKey][dKey2][dKey3].sort((a, b) => a.id - b.id).map(v => v.tags).flat()),
|
||||
list: d[dKey][dKey2][dKey3],
|
||||
name: dKey3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dictData = d
|
||||
console.log('dict', d)
|
||||
})
|
||||
|
||||
function formatLangType(val) {
|
||||
switch (Number(val)) {
|
||||
case 0:
|
||||
return '英语'
|
||||
case 1:
|
||||
return '汉语'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasePage>
|
||||
<div class="dict-list-panel">
|
||||
<header class="flex justify-center pb-3">
|
||||
<div class="container2 flex justify-between items-center">
|
||||
<div class="flex items-center gap-5">
|
||||
<BaseIcon icon="ion:chevron-back" title="返回" @click="back"/>
|
||||
<div class="tabs">
|
||||
<div class="tab"
|
||||
:class="currentTabIndex === item && 'active'"
|
||||
@click="currentTabIndex = item"
|
||||
v-for="item in Object.keys(dictData)">
|
||||
<span>{{ formatLangType(item) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BaseIcon icon="lucide:search"/>
|
||||
</div>
|
||||
</header>
|
||||
<div class="page-content">
|
||||
<div class="dict-list-wrapper">
|
||||
<div class="translate ">
|
||||
<span>释义:</span>
|
||||
<el-radio-group v-model="currentTranslateLanguage2">
|
||||
<el-radio-button border v-for="i in Object.keys(currentLangDictList)" :value="i">{{
|
||||
formatLangType(i)
|
||||
}}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<DictGroup
|
||||
v-for="item in currentTranDictList"
|
||||
:select-id="store.currentDict.id"
|
||||
@selectDict="change"
|
||||
:item="item"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BasePage>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
|
||||
.dict-list-panel {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
$header-height: 4rem;
|
||||
//padding: var(--space);
|
||||
padding-top: 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
//left: var(--aside-width);
|
||||
//width: calc(100vw - var(--aside-width));
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
z-index: 9;
|
||||
background: var(--color-main-bg);
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
|
||||
.tab {
|
||||
color: var(--color-font-1);
|
||||
cursor: pointer;
|
||||
padding: .3rem;
|
||||
transition: all .5s;
|
||||
border-bottom: 2px solid transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
|
||||
&.active {
|
||||
$main: rgb(64, 158, 255);
|
||||
border-bottom: 2px solid $main;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-content {
|
||||
padding-top: 4rem;
|
||||
display: flex;
|
||||
|
||||
.dict-list-wrapper {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
|
||||
.translate {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--color-font-1);
|
||||
margin-bottom: 1rem;
|
||||
|
||||
& > span {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -9,8 +9,8 @@ import {Icon} from "@iconify/vue";
|
||||
import IconWrapper from "@/pages/pc/components/IconWrapper.vue";
|
||||
import Tooltip from "@/pages/pc/components/Tooltip.vue";
|
||||
import TranslateSetting from "@/pages/pc/components/toolbar/TranslateSetting.vue";
|
||||
import VolumeSetting from "@/pages/pc/components/toolbar/VolumeSetting.vue";
|
||||
import RepeatSetting from "@/pages/pc/components/toolbar/RepeatSetting.vue";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts";
|
||||
|
||||
const statisticsStore = usePracticeStore()
|
||||
const settingStore = useSettingStore()
|
||||
@@ -90,75 +90,64 @@ onUnmounted(() => {
|
||||
<div class="line"></div>
|
||||
<div class="name">错误数</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="num">{{ format(statisticsStore.correctRate, '%') }}</div>
|
||||
<div class="line"></div>
|
||||
<div class="name">正确率</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-center items-center">
|
||||
<div class="flex">
|
||||
<BaseIcon
|
||||
v-if="!isSimple"
|
||||
class="collect"
|
||||
@click="$emit('toggleSimple')"
|
||||
:title="`标记为简单词(${settingStore.shortcutKeyMap[ShortcutKey.ToggleSimple]})`"
|
||||
icon="material-symbols:check-circle-outline-rounded"/>
|
||||
<BaseIcon
|
||||
v-else
|
||||
class="fill"
|
||||
@click="$emit('toggleSimple')"
|
||||
:title="`取消标记简单词(${settingStore.shortcutKeyMap[ShortcutKey.ToggleSimple]})`"
|
||||
icon="material-symbols:check-circle-rounded"/>
|
||||
<div class="flex justify-center items-center">
|
||||
<BaseIcon
|
||||
v-if="!isSimple"
|
||||
class="collect"
|
||||
@click="$emit('toggleSimple')"
|
||||
:title="`标记为简单词(${settingStore.shortcutKeyMap[ShortcutKey.ToggleSimple]})`"
|
||||
icon="material-symbols:check-circle-outline-rounded"/>
|
||||
<BaseIcon
|
||||
v-else
|
||||
class="fill"
|
||||
@click="$emit('toggleSimple')"
|
||||
:title="`取消标记简单词(${settingStore.shortcutKeyMap[ShortcutKey.ToggleSimple]})`"
|
||||
icon="material-symbols:check-circle-rounded"/>
|
||||
|
||||
<BaseIcon
|
||||
v-if="!isCollect"
|
||||
class="collect"
|
||||
@click="$emit('toggleCollect')"
|
||||
:title="`收藏(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
|
||||
icon="ph:star"/>
|
||||
<BaseIcon
|
||||
v-else
|
||||
class="fill"
|
||||
@click="$emit('toggleCollect')"
|
||||
:title="`取消收藏(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
|
||||
icon="ph:star-fill"/>
|
||||
<BaseIcon
|
||||
v-if="!isCollect"
|
||||
class="collect"
|
||||
@click="$emit('toggleCollect')"
|
||||
:title="`收藏(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
|
||||
icon="ph:star"/>
|
||||
<BaseIcon
|
||||
v-else
|
||||
class="fill"
|
||||
@click="$emit('toggleCollect')"
|
||||
:title="`取消收藏(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
|
||||
icon="ph:star-fill"/>
|
||||
|
||||
<Tooltip
|
||||
:title="`跳过(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
|
||||
>
|
||||
<IconWrapper>
|
||||
<Icon icon="icon-park-outline:go-ahead" class="menu"
|
||||
@click="emit('skip')"/>
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Tooltip
|
||||
:title="`跳过(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
|
||||
>
|
||||
<IconWrapper>
|
||||
<Icon icon="icon-park-outline:go-ahead" class="menu"
|
||||
@click="emit('skip')"/>
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
|
||||
<div class="flex">
|
||||
<Tooltip
|
||||
:title="`开关默写模式(${settingStore.shortcutKeyMap[ShortcutKey.ToggleDictation]})`"
|
||||
>
|
||||
<IconWrapper>
|
||||
<Icon icon="majesticons:eye-off-line"
|
||||
v-if="settingStore.dictation"
|
||||
@click="settingStore.dictation = false"/>
|
||||
<Icon icon="mdi:eye-outline"
|
||||
v-else
|
||||
@click="settingStore.dictation = true"/>
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
:title="`开关默写模式(${settingStore.shortcutKeyMap[ShortcutKey.ToggleDictation]})`"
|
||||
>
|
||||
<IconWrapper>
|
||||
<Icon icon="majesticons:eye-off-line"
|
||||
v-if="settingStore.dictation"
|
||||
@click="settingStore.dictation = false"/>
|
||||
<Icon icon="mdi:eye-outline"
|
||||
v-else
|
||||
@click="settingStore.dictation = true"/>
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
|
||||
<TranslateSetting/>
|
||||
<TranslateSetting/>
|
||||
|
||||
<VolumeSetting/>
|
||||
<RepeatSetting/>
|
||||
|
||||
<RepeatSetting/>
|
||||
|
||||
<BaseIcon
|
||||
@click="settingStore.showPanel = !settingStore.showPanel"
|
||||
:title="`单词本(${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`"
|
||||
icon="tdesign:menu-unfold"/>
|
||||
</div>
|
||||
<BaseIcon
|
||||
@click="emitter.emit(EventKey.openStatModal, {})"
|
||||
:title="`单词本(${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`"
|
||||
icon="tdesign:menu-unfold"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -168,6 +157,8 @@ onUnmounted(() => {
|
||||
:show-text="false"/>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
@click="settingStore.showPanel = !settingStore.showPanel"-->
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import Tooltip from "@/pages/pc/components/Tooltip.vue";
|
||||
import IconWrapper from "@/pages/pc/components/IconWrapper.vue";
|
||||
import {Icon} from "@iconify/vue";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import {useWordOptions} from "@/hooks/dict.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {ShortcutKey} from "@/types.ts";
|
||||
|
||||
defineProps<{
|
||||
showEdit?: boolean,
|
||||
isCollect: boolean,
|
||||
isSimple: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
toggleCollect: [],
|
||||
toggleSimple: [],
|
||||
edit: [],
|
||||
skip: [],
|
||||
}>()
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="options">
|
||||
<BaseIcon
|
||||
v-if="!isSimple"
|
||||
class="collect"
|
||||
@click="$emit('toggleSimple')"
|
||||
:title="`标记为简单词(${settingStore.shortcutKeyMap[ShortcutKey.ToggleSimple]})`"
|
||||
icon="material-symbols:check-circle-outline-rounded"/>
|
||||
<BaseIcon
|
||||
v-else
|
||||
class="fill"
|
||||
@click="$emit('toggleSimple')"
|
||||
:title="`取消标记简单词(${settingStore.shortcutKeyMap[ShortcutKey.ToggleSimple]})`"
|
||||
icon="material-symbols:check-circle-rounded"/>
|
||||
|
||||
<BaseIcon
|
||||
v-if="!isCollect"
|
||||
class="collect"
|
||||
@click="$emit('toggleCollect')"
|
||||
:title="`收藏(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
|
||||
icon="ph:star"/>
|
||||
<BaseIcon
|
||||
v-else
|
||||
class="fill"
|
||||
@click="$emit('toggleCollect')"
|
||||
:title="`取消收藏(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
|
||||
icon="ph:star-fill"/>
|
||||
|
||||
<Tooltip
|
||||
:title="`跳过(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
|
||||
>
|
||||
<IconWrapper>
|
||||
<Icon icon="icon-park-outline:go-ahead" class="menu"
|
||||
@click="emit('skip')"/>
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.options {
|
||||
margin-top: 1.2rem;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
</style>
|
||||
@@ -3,11 +3,9 @@ import Dialog from "@/pages/pc/components/dialog/Dialog.vue";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import Ring from "@/pages/pc/components/Ring.vue";
|
||||
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, useEvents} from "@/utils/eventBus.ts";
|
||||
import {onMounted} from "vue";
|
||||
import {Icon} from '@iconify/vue';
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {usePracticeStore} from "@/stores/practice.ts";
|
||||
@@ -19,8 +17,6 @@ const statStore = usePracticeStore()
|
||||
let open = $ref(false)
|
||||
|
||||
useEvent(EventKey.openStatModal, () => {
|
||||
console.log('on')
|
||||
|
||||
let data = {
|
||||
speed: statStore.speed,
|
||||
startDate: statStore.startDate,
|
||||
@@ -61,21 +57,31 @@ const isEnd = $computed(() => {
|
||||
:header="false"
|
||||
v-model="open">
|
||||
<div class="statistics relative flex flex-col gap-6">
|
||||
<header>
|
||||
<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">
|
||||
<div class="font-bold">非常棒!</div>
|
||||
<div>坚持了 <span class="color-green font-bold text-2xl">{{ dayjs().diff(statStore.startDate, 'm') }}</span>
|
||||
分钟
|
||||
<div class="w-full flex flex-col justify-evenly">
|
||||
<div class="center text-xl mb-2">已完成今日任务</div>
|
||||
<div class="flex">
|
||||
<div class="flex-1 flex flex-col items-center">
|
||||
<div class="text-4xl font-bold">{{ statStore.newWordNumber }}</div>
|
||||
<div class="text">新词数</div>
|
||||
</div>
|
||||
<div class="flex-1 flex flex-col items-center">
|
||||
<div class="text-4xl font-bold">{{ statStore.newWordNumber }}</div>
|
||||
<div class="text">复习数</div>
|
||||
</div>
|
||||
<div class="flex-1 flex flex-col items-center">
|
||||
<div class="text-4xl font-bold">{{
|
||||
statStore.newWordNumber
|
||||
}}
|
||||
</div>
|
||||
<div class="text">默写数</div>
|
||||
</div>
|
||||
</div>
|
||||
<Ring
|
||||
:value="statStore.newWordNumber"
|
||||
desc="New"
|
||||
:percentage="40"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="text-xl text-center flex flex-col justify-around">
|
||||
<div>非常棒! 坚持了 <span class="color-green font-bold text-2xl">{{ dayjs().diff(statStore.startDate, 'm') }}</span>
|
||||
分钟
|
||||
</div>
|
||||
</div>
|
||||
<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"
|
||||
@@ -95,15 +101,7 @@ const isEnd = $computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute right-5 top-20 flex flex-col gap-4">
|
||||
<Tooltip title="分享给朋友">
|
||||
<Icon class="hvr-grow cursor-pointer" icon="ph:share-light" width="20" color="#929596"/>
|
||||
</Tooltip>
|
||||
<Tooltip title="请我喝杯咖啡">
|
||||
<Icon class="hvr-grow cursor-pointer" icon="twemoji:teacup-without-handle" width="20" color="#929596"/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="flex justify-center gap-4 ">
|
||||
<BaseButton
|
||||
:keyboard="settingStore.shortcutKeyMap[ShortcutKey.RepeatChapter]"
|
||||
@click="options('repeat')">
|
||||
@@ -114,59 +112,20 @@ const isEnd = $computed(() => {
|
||||
@click="options('next')">
|
||||
{{ isEnd ? '重新练习' : '再来一组' }}
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
type="primary"
|
||||
@click="options('next')">
|
||||
分享
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
<Fireworks v-if="open"/>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
|
||||
|
||||
$card-radius: .5rem;
|
||||
$dark-second-bg: rgb(60, 63, 65);
|
||||
$item-hover: rgb(75, 75, 75);
|
||||
|
||||
.statistics {
|
||||
padding: var(--space);
|
||||
width: 30rem;
|
||||
background: $dark-second-bg;
|
||||
border-radius: $card-radius;
|
||||
|
||||
$header-height: 2.5rem;
|
||||
$footer-height: 4rem;
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: $header-height;
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
gap: var(--space);
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.shares {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space);
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
height: $footer-height;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -9,28 +9,16 @@ import {emitter, EventKey, useEvents} from "@/utils/eventBus.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {useRuntimeStore} from "@/stores/runtime.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 TypingWord from "@/pages/pc/word/components/TypingWord.vue";
|
||||
import {getCurrentStudyWord} from "@/hooks/dict.ts";
|
||||
import {cloneDeep} from "lodash-es";
|
||||
|
||||
const statStore = usePracticeStore()
|
||||
const store = useBaseStore()
|
||||
const settingStore = useSettingStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const {toggleTheme} = useTheme()
|
||||
|
||||
watch(statStore, () => {
|
||||
if (statStore.inputWordNumber < 1) {
|
||||
return statStore.correctRate = -1
|
||||
}
|
||||
if (statStore.wrong > statStore.inputWordNumber) {
|
||||
return statStore.correctRate = 0
|
||||
}
|
||||
statStore.correctRate = 100 - Math.trunc(((statStore.wrong) / (statStore.inputWordNumber)) * 100)
|
||||
})
|
||||
|
||||
function next() {
|
||||
emitter.emit(EventKey.resetWord)
|
||||
@@ -58,10 +46,6 @@ function openSetting() {
|
||||
runtimeStore.showSettingModal = true
|
||||
}
|
||||
|
||||
function openDictDetail() {
|
||||
emitter.emit(EventKey.openDictModal, 'detail')
|
||||
}
|
||||
|
||||
function toggleConciseMode() {
|
||||
settingStore.showToolbar = !settingStore.showToolbar
|
||||
settingStore.showPanel = settingStore.showToolbar
|
||||
@@ -87,7 +71,6 @@ useEvents([
|
||||
[ShortcutKey.ToggleShowTranslate, toggleTranslate],
|
||||
[ShortcutKey.ToggleDictation, toggleDictation],
|
||||
[ShortcutKey.OpenSetting, openSetting],
|
||||
[ShortcutKey.OpenDictDetail, openDictDetail],
|
||||
[ShortcutKey.ToggleTheme, toggleTheme],
|
||||
[ShortcutKey.ToggleConciseMode, toggleConciseMode],
|
||||
[ShortcutKey.TogglePanel, togglePanel],
|
||||
@@ -116,7 +99,6 @@ useStartKeyboardEventListener()
|
||||
<TypingWord
|
||||
@complete="complete"
|
||||
:data="studyData"/>
|
||||
<DictModal/>
|
||||
<Statistics/>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import {onMounted, onUnmounted, watch} from "vue"
|
||||
import {watch} from "vue"
|
||||
import {useBaseStore} from "@/stores/base.ts"
|
||||
import {DefaultDisplayStatistics, DictType, getDefaultWord, ShortcutKey, Sort, Word} from "@/types.ts";
|
||||
import {getDefaultWord, ShortcutKey, Word} from "@/types.ts";
|
||||
import {emitter, EventKey, useEvents} from "@/utils/eventBus.ts"
|
||||
import {cloneDeep, reverse, shuffle} from "lodash-es"
|
||||
import {cloneDeep, shuffle} from "lodash-es"
|
||||
import {usePracticeStore} from "@/stores/practice.ts"
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {useOnKeyboardEventListener, useWindowClick} from "@/hooks/event.ts";
|
||||
import {useOnKeyboardEventListener} from "@/hooks/event.ts";
|
||||
import {Icon} from "@iconify/vue";
|
||||
import Tooltip from "@/pages/pc/components/Tooltip.vue";
|
||||
import Options from "@/pages/pc/word/Options.vue";
|
||||
import Typing from "@/pages/pc/components/Typing.vue";
|
||||
import Panel from "@/pages/pc/components/Panel.vue";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
@@ -17,8 +16,6 @@ import {useWordOptions} from "@/hooks/dict.ts";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import WordList from "@/pages/pc/components/list/WordList.vue";
|
||||
import Empty from "@/components/Empty.vue";
|
||||
import MiniDialog from "@/pages/pc/components/dialog/MiniDialog.vue";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import Footer from "@/pages/pc/word/Footer.vue";
|
||||
|
||||
interface IProps {
|
||||
@@ -71,7 +68,6 @@ watch(() => props.data, () => {
|
||||
|
||||
statStore.step = 0
|
||||
statStore.startDate = Date.now()
|
||||
statStore.correctRate = -1
|
||||
statStore.inputWordNumber = 0
|
||||
statStore.wrong = 0
|
||||
statStore.total = props.data.review.concat(props.data.new).concat(props.data.write).length
|
||||
@@ -214,10 +210,10 @@ const status = $computed(() => {
|
||||
str += `学习新单词`
|
||||
break
|
||||
case 1:
|
||||
str += '复习上次学习'
|
||||
str += '复习'
|
||||
break
|
||||
case 2:
|
||||
str += '默写所有单词'
|
||||
str += '默写'
|
||||
break
|
||||
}
|
||||
return str
|
||||
@@ -256,15 +252,6 @@ const status = $computed(() => {
|
||||
@wrong="wordWrong"
|
||||
@complete="next"
|
||||
/>
|
||||
<div class="options-wrapper" v-if="false">
|
||||
<Options
|
||||
:is-simple="isWordSimple(word)"
|
||||
@toggle-simple="toggleWordSimpleWrapper"
|
||||
:is-collect="isWordCollect(word)"
|
||||
@toggle-collect="toggleWordCollect(word)"
|
||||
@skip="next(false)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer
|
||||
@@ -4,7 +4,6 @@ import Test from "@/pages/test/test.vue";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import WordHomePage from "@/pages/pc/word/WordHomePage.vue";
|
||||
import PC from "@/pages/pc/index.vue";
|
||||
import Dict2 from '@/pages/pc/dict2/index.vue'
|
||||
import ArticleHomePage from "@/pages/pc/article/ArticleHomePage.vue";
|
||||
import HomeIndex from "@/pages/pc/home/HomeIndex.vue";
|
||||
import LearnArticle from "@/pages/pc/article/LearnArticle.vue";
|
||||
@@ -23,7 +22,6 @@ export const routes: RouteRecordRaw[] = [
|
||||
{path: 'word', component: WordHomePage},
|
||||
{path: 'study-word', component: StudyWord},
|
||||
{path: 'edit-word-dict', component: EditWordDict},
|
||||
{path: 'dict', component: Dict2},
|
||||
{path: 'article', component: ArticleHomePage},
|
||||
{path: 'edit-article', component: EditArticlePage},
|
||||
{path: 'batch-edit-article', component: BatchEditArticlePage},
|
||||
|
||||
@@ -9,9 +9,8 @@ export interface PracticeState {
|
||||
newWordNumber: number,
|
||||
inputWordNumber: number,//当前总输入了多少个单词(不包含跳过)
|
||||
wrong: number,
|
||||
correctRate: number,
|
||||
startIndex:number,
|
||||
endIndex:number,
|
||||
startIndex: number,
|
||||
endIndex: number,
|
||||
}
|
||||
|
||||
export const usePracticeStore = defineStore('practice', {
|
||||
@@ -20,7 +19,6 @@ export const usePracticeStore = defineStore('practice', {
|
||||
step: 0,
|
||||
speed: 0,
|
||||
startDate: Date.now(),
|
||||
correctRate: -1,
|
||||
total: 0,
|
||||
index: 0,
|
||||
startIndex: 0,
|
||||
@@ -30,4 +28,4 @@ export const usePracticeStore = defineStore('practice', {
|
||||
wrong: 0,
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@@ -16,8 +16,6 @@ export interface SettingState {
|
||||
keyboardSound: boolean,
|
||||
keyboardSoundVolume: number,
|
||||
keyboardSoundFile: string,
|
||||
translateSound: boolean,
|
||||
translateSoundVolume: number,
|
||||
effectSound: boolean,
|
||||
effectSoundVolume: number,
|
||||
repeatCount: number,
|
||||
@@ -59,8 +57,6 @@ export const DefaultSettingState = (): SettingState => ({
|
||||
keyboardSound: true,
|
||||
keyboardSoundVolume: 100,
|
||||
keyboardSoundFile: '机械键盘2',
|
||||
translateSound: true,
|
||||
translateSoundVolume: 100,
|
||||
effectSound: true,
|
||||
effectSoundVolume: 100,
|
||||
repeatCount: 1,
|
||||
@@ -103,7 +99,10 @@ export const useSettingStore = defineStore('setting', {
|
||||
if (!configStr) configStr = localStorage.getItem(SAVE_SETTING_KEY.oldKey)
|
||||
let data = checkAndUpgradeSaveSetting(configStr)
|
||||
this.setState(data)
|
||||
localStorage.setItem(SAVE_SETTING_KEY.key, JSON.stringify({val: this.$state, version: SAVE_SETTING_KEY.version}))
|
||||
localStorage.setItem(SAVE_SETTING_KEY.key, JSON.stringify({
|
||||
val: this.$state,
|
||||
version: SAVE_SETTING_KEY.version
|
||||
}))
|
||||
this.load = true
|
||||
resolve(true)
|
||||
})
|
||||
|
||||
@@ -239,6 +239,7 @@ export enum ShortcutKey {
|
||||
ToggleShowTranslate = 'ToggleShowTranslate',
|
||||
ToggleDictation = 'ToggleDictation',
|
||||
OpenSetting = 'OpenSetting',
|
||||
//todo 废弃
|
||||
OpenDictDetail = 'OpenDictDetail',
|
||||
ToggleTheme = 'ToggleTheme',
|
||||
ToggleConciseMode = 'ToggleConciseMode',
|
||||
|
||||
@@ -7,6 +7,7 @@ export const EventKey = {
|
||||
changeDict: 'changeDict',
|
||||
openStatModal: 'openStatModal',
|
||||
openWordListModal: 'openWordListModal',
|
||||
//todo feiqi
|
||||
openDictModal: 'openDictModal',
|
||||
openArticleListModal: 'openArticleListModal',
|
||||
closeOther: 'closeOther',
|
||||
@@ -39,4 +40,4 @@ export function useEvents(arrs: any[],) {
|
||||
onUnmounted(() => {
|
||||
arrs.map((arr) => emitter.off(arr[0], arr[1]))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user