feat:重构代码

This commit is contained in:
zyronon
2025-07-16 23:40:41 +08:00
parent d59bab48fc
commit 7bedaafcc0
24 changed files with 114 additions and 1092 deletions

View File

@@ -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
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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,

View File

@@ -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">

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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">

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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},

View File

@@ -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,
}
},
})
})

View File

@@ -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)
})

View File

@@ -239,6 +239,7 @@ export enum ShortcutKey {
ToggleShowTranslate = 'ToggleShowTranslate',
ToggleDictation = 'ToggleDictation',
OpenSetting = 'OpenSetting',
//todo 废弃
OpenDictDetail = 'OpenDictDetail',
ToggleTheme = 'ToggleTheme',
ToggleConciseMode = 'ToggleConciseMode',

View File

@@ -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]))
})
}
}