feat(all): change icon import way,auto import js lib
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
@import "@icon-park/vue-next/styles/index.css";
|
||||
@import '/node_modules/element-plus/dist/index.css';
|
||||
//@import '/node_modules/element-plus/dist/index.css';
|
||||
@import "/node_modules/hover.css";
|
||||
@import "colors";
|
||||
@import "anim";
|
||||
@@ -88,4 +87,7 @@ footer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pointer{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,22 +9,18 @@
|
||||
|
||||
import {onMounted} from "vue"
|
||||
|
||||
const canvas = $ref()
|
||||
let ctx = null
|
||||
const canvas = $ref<HTMLCanvasElement>()
|
||||
|
||||
onMounted(() => {
|
||||
console.log('canvas;', canvas)
|
||||
// ctx = canvas.getContext('2d')
|
||||
let ocas = document.createElement("canvas");
|
||||
let octx = ocas.getContext("2d");
|
||||
let ctx = canvas.getContext("2d");
|
||||
ocas.width = canvas.width = window.innerWidth;
|
||||
ocas.height = canvas.height = window.innerHeight;
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
let maxRadius = 1,
|
||||
stars = [];
|
||||
|
||||
|
||||
let Star = function (x, y, r) {
|
||||
let Star = function (x: number, y: number, r: number) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.r = r;
|
||||
@@ -54,7 +50,7 @@ onMounted(() => {
|
||||
drawBg()
|
||||
|
||||
function drawMoon() {
|
||||
let moon = document.getElementById("moon");
|
||||
let moon: HTMLImageElement = document.getElementById("moon");
|
||||
let centerX = canvas.width - 200,
|
||||
centerY = 100,
|
||||
width = 80;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import {KeyboardOne} from "@icon-park/vue-next";
|
||||
import Tooltip from "@/components/Tooltip.vue";
|
||||
import {Icon} from "@iconify/vue";
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
keyboard?: string,
|
||||
active?: boolean
|
||||
}>()
|
||||
@@ -18,7 +18,7 @@ defineEmits(['click'])
|
||||
:class="active && 'active'">
|
||||
<span><slot></slot></span>
|
||||
<div class="key-notice" v-if="keyboard">
|
||||
<keyboard-one theme="outline" size="14" fill="#ffffff" :strokeWidth="2"/>
|
||||
<Icon icon="bi:keyboard" width="14" color="#ffffff"/>
|
||||
<span class="key">{{ keyboard }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import {ArrowLeft} from '@icon-park/vue-next'
|
||||
import {Icon} from "@iconify/vue";
|
||||
import {computed, inject} from "vue"
|
||||
import WordList from "@/components/WordList.vue"
|
||||
import {useBaseStore} from "@/stores/base.ts"
|
||||
|
||||
const store = useBaseStore()
|
||||
const back = inject('back')
|
||||
const stepIndex = inject('stepIndex')
|
||||
const tabIndex = inject('tabIndex')
|
||||
const back: () => void = inject('back')
|
||||
const stepIndex: any = inject('stepIndex')
|
||||
const tabIndex: any = inject('tabIndex')
|
||||
const isActive = computed(() => {
|
||||
return stepIndex.value === 2 && tabIndex.value === 0 && store.sideIsOpen
|
||||
})
|
||||
@@ -17,10 +17,10 @@ const isActive = computed(() => {
|
||||
<template>
|
||||
<div class="chapter-detail page">
|
||||
<header>
|
||||
<arrow-left @click="back" theme="outline" size="20" fill="#929596" :strokeWidth="2"/>
|
||||
<Icon icon="octicon:arrow-right-24" @click="back" width="20" color="#929596"/>
|
||||
<div class="dict-name">16.</div>
|
||||
</header>
|
||||
<WordList :isActive="isActive" :word-list="store.chapter" :activeIndex="store.wordIndex"></WordList>
|
||||
<WordList :isActive="isActive" :list="store.chapter" :activeIndex="store.wordIndex"></WordList>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import {Word} from "@/types.ts"
|
||||
|
||||
const props = defineProps<{
|
||||
list: Word[],
|
||||
defineProps<{
|
||||
list: Word[][],
|
||||
activeIndex: number
|
||||
}>()
|
||||
|
||||
@@ -18,7 +18,7 @@ function next() {
|
||||
<div class="list">
|
||||
<div class="item" :class="activeIndex === index && 'active'"
|
||||
v-for="(item,index) in list" @click="$emit('update:activeIndex', index)">
|
||||
<div class="title">第{{ index + 1 }}章 {{item.length}}词</div>
|
||||
<div class="title">第{{ index + 1 }}章 {{ item.length }}词</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import {inject} from "vue"
|
||||
|
||||
const next = inject('next')
|
||||
const next: () => void = inject('next')
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ export default {
|
||||
render() {
|
||||
let Vnode = this.$slots.default()[0]
|
||||
return (
|
||||
<div class="icon-wrapper">
|
||||
<div class="icon-wrapper hvr-grow">
|
||||
<Vnode
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import {Close} from "@icon-park/vue-next"
|
||||
import {onMounted} from "vue";
|
||||
import Tooltip from "@/components/Tooltip.vue";
|
||||
import {Icon} from '@iconify/vue';
|
||||
|
||||
interface IProps {
|
||||
modelValue: boolean,
|
||||
showClose?: boolean,
|
||||
title?: string,
|
||||
subTitle?: string,
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
modelValue: true,
|
||||
showClose: true,
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
@@ -39,10 +41,11 @@ onMounted(() => {
|
||||
<div class="modal-mask" @click="close"></div>
|
||||
<div class="modal">
|
||||
<Tooltip title="关闭">
|
||||
<Close @click="close"
|
||||
class="close"
|
||||
theme="outline" size="20" fill="#929596"
|
||||
:strokeWidth="2"/>
|
||||
<Icon @click="close"
|
||||
v-if="showClose"
|
||||
class="close hvr-grow pointer"
|
||||
width="20" color="#929596"
|
||||
icon="ion:close-outline"/>
|
||||
</Tooltip>
|
||||
<div class="modal-header" v-if="props.title">
|
||||
<div class="title">{{ props.title }}</div>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import Modal from "@/components/Modal/Modal.vue";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {Like, ShareThree, Tea} from '@icon-park/vue-next'
|
||||
import Ring from "@/components/Ring.vue";
|
||||
import Tooltip from "@/components/Tooltip.vue";
|
||||
import Fireworks from "@/components/Fireworks.vue";
|
||||
@@ -9,7 +8,8 @@ import BaseButton from "@/components/BaseButton.vue";
|
||||
import {DefaultStatistics, Statistics} from "@/types.ts";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts";
|
||||
import {onMounted, reactive} from "vue";
|
||||
import {cloneDeep} from "lodash";
|
||||
import {cloneDeep} from "lodash-es";
|
||||
import {Icon} from '@iconify/vue';
|
||||
|
||||
const store = useBaseStore()
|
||||
let statModalIsOpen = $ref(false)
|
||||
@@ -75,19 +75,16 @@ function next() {
|
||||
</div>
|
||||
<div class="notice" v-if="!store.current.originWrongWords.length">
|
||||
<!-- <div class="notice">-->
|
||||
<like theme="filled" size="20" fill="#ffffff" :strokeWidth="2"/>
|
||||
<Icon class="hvr-grow pointer" icon="flat-color-icons:like" width="20" color="#929596"/>
|
||||
表现不错,全对了!
|
||||
</div>
|
||||
</div>
|
||||
<div class="shares">
|
||||
<Tooltip title="分享给朋友">
|
||||
<share-three theme="outline" size="20" fill="#929596" :strokeWidth="2"/>
|
||||
<Icon class="hvr-grow pointer" icon="ph:share-light" width="20" color="#929596"/>
|
||||
</Tooltip>
|
||||
<Tooltip title="分享给朋友">
|
||||
<tea theme="outline" size="20" fill="#929596" :strokeWidth="2"/>
|
||||
</Tooltip>
|
||||
<Tooltip title="分享给朋友">
|
||||
<share-three theme="outline" size="20" fill="#929596" :strokeWidth="2"/>
|
||||
<Tooltip title="请我喝杯咖啡">
|
||||
<Icon class="hvr-grow pointer" icon="twemoji:teacup-without-handle" width="20" color="#929596"/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,8 +4,8 @@ import {$computed, $ref} from "vue/macros"
|
||||
import {onMounted, onUnmounted} from "vue"
|
||||
import {useBaseStore} from "@/stores/base.ts"
|
||||
import Tooltip from "@/components/Tooltip.vue"
|
||||
import {Down} from "@icon-park/vue-next"
|
||||
import {usePracticeStore} from "@/components/Practice/usePracticeStore.ts";
|
||||
import {Icon} from "@iconify/vue";
|
||||
|
||||
const practiceStore = usePracticeStore()
|
||||
const store = useBaseStore()
|
||||
@@ -16,7 +16,7 @@ function format(val: number, suffix: string = '', check: number = -1) {
|
||||
|
||||
const progress = $computed(() => {
|
||||
if (!practiceStore.total) return 0
|
||||
if (practiceStore.inputNumber>practiceStore.total) return 100
|
||||
if (practiceStore.inputNumber > practiceStore.total) return 100
|
||||
return ((practiceStore.inputNumber / practiceStore.total) * 100)
|
||||
})
|
||||
|
||||
@@ -37,11 +37,11 @@ onUnmounted(() => {
|
||||
<template>
|
||||
<div class="footer" :class="!store.setting.showToolbar && 'hide'">
|
||||
<Tooltip :title="store.setting.showToolbar?'收起':'展开'">
|
||||
<Down
|
||||
@click="store.setting.showToolbar = !store.setting.showToolbar"
|
||||
class="arrow"
|
||||
:class="!store.setting.showToolbar && 'down'"
|
||||
theme="outline" size="24" fill="#999"/>
|
||||
<Icon icon="icon-park-outline:down"
|
||||
@click="store.setting.showToolbar = !store.setting.showToolbar"
|
||||
class="arrow"
|
||||
:class="!store.setting.showToolbar && 'down'"
|
||||
width="24" color="#999"/>
|
||||
</Tooltip>
|
||||
<div class="bottom">
|
||||
<el-progress :percentage="progress"
|
||||
|
||||
@@ -1,273 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import {computed, onMounted, onUnmounted, provide, watch} from "vue"
|
||||
import 快速打字的机械键盘声音Mp3 from '../assets/sound/key-sounds/快速打字的机械键盘声音.mp3'
|
||||
import 键盘快速打字的声音Mp3 from '../assets/sound/key-sounds/键盘快速打字的声音.mp3'
|
||||
import 电话打字的声音Mp3 from '../assets/sound/key-sounds/电话打字的声音.mp3'
|
||||
import 老式机械 from '../assets/sound/key-sounds/老式机械.mp3'
|
||||
import 机械0 from '../assets/sound/key-sounds/jixie/机械0.mp3'
|
||||
import 机械1 from '../assets/sound/key-sounds/jixie/机械1.mp3'
|
||||
import 机械2 from '../assets/sound/key-sounds/jixie/机械2.mp3'
|
||||
import 机械3 from '../assets/sound/key-sounds/jixie/机械3.mp3'
|
||||
import beep from '../assets/sound/beep.wav'
|
||||
import correct from '../assets/sound/correct.wav'
|
||||
import {$computed, $ref} from "vue/macros"
|
||||
import {useSound} from "@/hooks/useSound.ts"
|
||||
import {useBaseStore} from "@/stores/base.ts"
|
||||
import {DictType, SaveKey, ShortKeyMap, Word} from "../types";
|
||||
import {usePlayWordAudio} from "@/hooks/usePlayWordAudio.ts"
|
||||
import useTheme from "@/hooks/useTheme.ts";
|
||||
import Tooltip from "@/components/Tooltip.vue";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import {
|
||||
Down,
|
||||
Delete,
|
||||
} from "@icon-park/vue-next"
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts"
|
||||
import {cloneDeep} from "lodash"
|
||||
|
||||
let input = $ref('')
|
||||
let wrong = $ref('')
|
||||
let showFullWord = $ref(false)
|
||||
let activeIndex = $ref(-1)
|
||||
const store = useBaseStore()
|
||||
|
||||
// const [playKeySound, setAudio] = useSound([机械0, 机械1, 机械2, 机械3], 1)
|
||||
// const [playKeySound, setAudio] = useSound([老式机械], 3)
|
||||
const [playKeySound, setAudio] = useSound([电话打字的声音Mp3], 3)
|
||||
const [playBeep] = useSound([beep], 1)
|
||||
const [playCorrect] = useSound([correct], 1)
|
||||
const [playAudio] = usePlayWordAudio()
|
||||
|
||||
|
||||
interface IProps {
|
||||
total: number,
|
||||
startDate: number
|
||||
inputNumber: number
|
||||
wrongNumber: number
|
||||
correctRate: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
total: 0,
|
||||
startDate: Date.now(),
|
||||
inputNumber: 0,
|
||||
wrongNumber: 0,
|
||||
correctRate: 0,
|
||||
})
|
||||
|
||||
const resetWord = $computed(() => {
|
||||
return store.word.name.slice(input.length + wrong.length)
|
||||
})
|
||||
onMounted(() => {
|
||||
window.addEventListener('keydown', onKeyDown)
|
||||
window.addEventListener('keyup', onKeyUp)
|
||||
|
||||
emitter.on(EventKey.resetWord, () => {
|
||||
input = ''
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// console.log('onUnmounted')
|
||||
window.removeEventListener('keydown', onKeyDown)
|
||||
window.removeEventListener('keyup', onKeyUp)
|
||||
})
|
||||
|
||||
function next() {
|
||||
if (store.current.index === store.current.words.length - 1) {
|
||||
if (store.current.wrongWords.length) {
|
||||
store.setCurrentWord(store.current.wrongWords)
|
||||
} else {
|
||||
if (store.currentDict.chapterIndex !== store.currentDict.chapterWords.length - 1) {
|
||||
console.log('这一章节完了')
|
||||
emitter.emit(EventKey.openStatModal)
|
||||
} else {
|
||||
console.log('这本书完了')
|
||||
emitter.emit(EventKey.openStatModal)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
store.current.index++
|
||||
|
||||
console.log('这个词完了')
|
||||
}
|
||||
if ([DictType.customDict, DictType.innerDict].includes(store.current.dictType) && store.skipWordNames.includes(store.word.name.toLowerCase())) {
|
||||
next()
|
||||
}
|
||||
|
||||
if (store.current.index) {
|
||||
store.current.statistics.wrongWordNumber = store.current.wrongWords.length
|
||||
store.current.statistics.correctRate = Math.trunc(((store.current.index - store.current.wrongWords.length) / (store.current.index)) * 100)
|
||||
} else {
|
||||
store.current.statistics.wrongWordNumber = -1
|
||||
store.current.statistics.correctRate = -1
|
||||
}
|
||||
wrong = input = ''
|
||||
}
|
||||
|
||||
function onKeyUp(e: KeyboardEvent) {
|
||||
showFullWord = false
|
||||
}
|
||||
|
||||
async function onKeyDown(e: KeyboardEvent) {
|
||||
//TODO 还有横杠
|
||||
if ((e.keyCode >= 65 && e.keyCode <= 90) || e.code === 'Space') {
|
||||
let letter = e.key
|
||||
if ((input + letter).toLowerCase() === store.word.name.toLowerCase().slice(0, input.length + 1)) {
|
||||
input += letter
|
||||
wrong = ''
|
||||
playKeySound()
|
||||
} else {
|
||||
if (!store.wrongWordDict.originWords.find((v: Word) => v.name.toLowerCase() === store.word.name.toLowerCase())) {
|
||||
store.wrongWordDict.originWords.push(store.word)
|
||||
store.wrongWordDict.words.push(store.word)
|
||||
store.wrongWordDict.chapterWords = [store.wrongWordDict.words]
|
||||
}
|
||||
if (!store.current.wrongWords.find((v: Word) => v.name.toLowerCase() === store.word.name.toLowerCase())) {
|
||||
store.current.wrongWords.push(store.word)
|
||||
}
|
||||
store.current.statistics.correctRate = Math.trunc(((store.current.index + 1 - store.current.wrongWords.length) / (store.current.index + 1)) * 100)
|
||||
wrong = letter
|
||||
playKeySound()
|
||||
playBeep()
|
||||
setTimeout(() => {
|
||||
wrong = ''
|
||||
// wrong = input = ''
|
||||
}, 500)
|
||||
}
|
||||
if (input.toLowerCase() === store.word.name.toLowerCase()) {
|
||||
playCorrect()
|
||||
setTimeout(next, 300)
|
||||
}
|
||||
} else {
|
||||
// console.log('e', e)
|
||||
switch (e.key) {
|
||||
case 'Backspace':
|
||||
if (wrong) {
|
||||
wrong = ''
|
||||
} else {
|
||||
input = input.slice(0, -1)
|
||||
}
|
||||
break
|
||||
case ShortKeyMap.Collect:
|
||||
if (!store.newWordDict.originWords.find((v: Word) => v.name === store.word.name)) {
|
||||
store.newWordDict.originWords.push(store.word)
|
||||
store.newWordDict.words.push(store.word)
|
||||
store.newWordDict.chapterWords = [store.newWordDict.words]
|
||||
}
|
||||
activeIndex = 1
|
||||
break
|
||||
case ShortKeyMap.Remove:
|
||||
if (!store.skipWordNames.includes(store.word.name)) {
|
||||
store.skipWordDict.originWords.push(store.word)
|
||||
store.skipWordDict.words.push(store.word)
|
||||
store.skipWordDict.chapterWords = [store.skipWordDict.words]
|
||||
}
|
||||
activeIndex = 0
|
||||
next()
|
||||
break
|
||||
case ShortKeyMap.Ignore:
|
||||
e.preventDefault()
|
||||
activeIndex = 2
|
||||
next()
|
||||
break
|
||||
case ShortKeyMap.Show:
|
||||
showFullWord = true
|
||||
break
|
||||
}
|
||||
setTimeout(() => {
|
||||
activeIndex = -1
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const {toggle} = useTheme()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="type-word">
|
||||
<div class="translate">{{ store.word.trans.join(';') }}</div>
|
||||
<div class="word-wrapper">
|
||||
<div class="word" :class="wrong && 'is-wrong'">
|
||||
<span class="input" v-if="input">{{ input }}</span>
|
||||
<span class="wrong" v-if="wrong">{{ wrong }}</span>
|
||||
<template v-if="store.isDictation">
|
||||
<span class="letter" v-if="!showFullWord"
|
||||
@mouseenter="showFullWord = true">{{ resetWord.split('').map(v => '_').join('') }}</span>
|
||||
<span class="letter" v-else @mouseleave="showFullWord = false">{{ resetWord }}</span>
|
||||
</template>
|
||||
<span class="letter" v-else>{{ resetWord }}</span>
|
||||
</div>
|
||||
<div class="audio" @click="playAudio(store.word.name)">播放</div>
|
||||
</div>
|
||||
<div class="phonetic">{{ store.word.usphone }}</div>
|
||||
<div class="options">
|
||||
<BaseButton keyboard="`" :active="activeIndex === 0">
|
||||
忽略
|
||||
</BaseButton>
|
||||
<BaseButton keyboard="Enter" :active="activeIndex === 1">
|
||||
收藏
|
||||
</BaseButton>
|
||||
<BaseButton keyboard="Tab" :active="activeIndex === 2">
|
||||
下一个
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/assets/css/colors.scss";
|
||||
|
||||
.type-word {
|
||||
display: flex;
|
||||
//display: none;
|
||||
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
font-size: 14rem;
|
||||
color: gray;
|
||||
gap: 2rem;
|
||||
|
||||
.options {
|
||||
margin-top: 10rem;
|
||||
display: flex;
|
||||
gap: 15rem;
|
||||
font-size: 18rem;
|
||||
}
|
||||
|
||||
.phonetic, .translate {
|
||||
font-size: 20rem;
|
||||
margin-left: -30rem;
|
||||
}
|
||||
|
||||
.word-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rem;
|
||||
|
||||
.word {
|
||||
font-size: 48rem;
|
||||
line-height: 1;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
|
||||
letter-spacing: 5rem;
|
||||
|
||||
.input {
|
||||
color: rgb(22, 163, 74);
|
||||
}
|
||||
|
||||
.wrong {
|
||||
color: rgba(red, 0.6);
|
||||
}
|
||||
|
||||
&.is-wrong {
|
||||
animation: shake 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import {usePlayWordAudio} from "@/hooks/usePlayWordAudio.ts"
|
||||
import {computed, nextTick, onMounted, reactive, watch} from "vue"
|
||||
import {cloneDeep} from "lodash"
|
||||
import {cloneDeep} from "lodash-es"
|
||||
import 快速打字的机械键盘声音Mp3 from '../..//assets/sound/key-sounds/快速打字的机械键盘声音.mp3'
|
||||
import 键盘快速打字的声音Mp3 from '../..//assets/sound/key-sounds/键盘快速打字的声音.mp3'
|
||||
import 电话打字的声音Mp3 from '../..//assets/sound/key-sounds/电话打字的声音.mp3'
|
||||
@@ -500,6 +500,7 @@ function otherWord(word: ArticleWord, i: number, i2: number, i3: number) {
|
||||
}
|
||||
|
||||
.article-wrapper {
|
||||
opacity: 0;
|
||||
|
||||
header {
|
||||
.title {
|
||||
@@ -508,6 +509,7 @@ function otherWord(word: ArticleWord, i: number, i2: number, i3: number) {
|
||||
font-size: 36rem;
|
||||
font-weight: 500;
|
||||
word-spacing: 3rem;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.options {
|
||||
@@ -528,6 +530,7 @@ function otherWord(word: ArticleWord, i: number, i2: number, i3: number) {
|
||||
|
||||
.article-content {
|
||||
position: relative;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
article {
|
||||
|
||||
@@ -18,12 +18,8 @@ import {usePlayWordAudio} from "@/hooks/usePlayWordAudio.ts"
|
||||
import useTheme from "@/hooks/useTheme.ts";
|
||||
import Tooltip from "@/components/Tooltip.vue";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import {
|
||||
Down,
|
||||
Delete,
|
||||
} from "@icon-park/vue-next"
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts"
|
||||
import {cloneDeep} from "lodash"
|
||||
import {cloneDeep} from "lodash-es"
|
||||
import {usePracticeStore} from "@/components/Practice/usePracticeStore.ts"
|
||||
import {useEventListener} from "@/hooks/useEvent.ts";
|
||||
|
||||
@@ -66,7 +62,7 @@ watchEffect(() => {
|
||||
practiceStore.inputNumber = 0
|
||||
practiceStore.wrongNumber = 0
|
||||
practiceStore.repeatNumber = 0
|
||||
practiceStore.total = props.words.length
|
||||
practiceStore.total = props.words.length
|
||||
practiceStore.wrongWords = []
|
||||
practiceStore.startDate = Date.now()
|
||||
})
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import {childrenEnglish} from '@/assets/dictionary.ts'
|
||||
import {ArrowLeft, ArrowRight, Close} from '@icon-park/vue-next'
|
||||
import {useBaseStore} from "@/stores/base.ts"
|
||||
import {watch} from "vue"
|
||||
import {Dict, DictType, Sort, Word} from "@/types.ts"
|
||||
import {chunk} from "lodash";
|
||||
import {chunk} from "lodash-es";
|
||||
import {$computed, $ref} from "vue/macros";
|
||||
import WordList from "@/components/WordList.vue";
|
||||
import ChapterList from "@/components/ChapterList.vue"
|
||||
import Modal from "@/components/Modal/Modal.vue";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import {Icon} from '@iconify/vue';
|
||||
|
||||
const store = useBaseStore()
|
||||
|
||||
@@ -29,7 +29,7 @@ let currentSelectDict: Dict = $ref(store.currentDict)
|
||||
let step = $ref(0)
|
||||
|
||||
const currentSelectChapter: Word[] = $computed(() => {
|
||||
return currentSelectDict.chapterList?.[currentSelectDict.chapterIndex] ?? []
|
||||
return currentSelectDict.chapterWords?.[currentSelectDict.chapterIndex] ?? []
|
||||
})
|
||||
|
||||
watch(() => props.modelValue, (n: boolean) => {
|
||||
@@ -39,19 +39,21 @@ watch(() => props.modelValue, (n: boolean) => {
|
||||
async function selectDict(item: Dict) {
|
||||
currentSelectDict = {
|
||||
...item,
|
||||
type: DictType.innerDict,
|
||||
sort: Sort.normal,
|
||||
wordList: [],
|
||||
chapterList: [],
|
||||
chapterIndex: 0,
|
||||
type: DictType.innerDict,
|
||||
originWords: [],
|
||||
words: [],
|
||||
chapterWordNumber: 15,
|
||||
wordIndex: 0,
|
||||
dictStatistics: []
|
||||
chapterWords: [],
|
||||
chapterIndex: 0,
|
||||
chapterWordIndex: 0,
|
||||
statistics: []
|
||||
}
|
||||
let r = await fetch(`/public/${item.url}`)
|
||||
r.json().then(v => {
|
||||
currentSelectDict.wordList = v
|
||||
currentSelectDict.chapterList = chunk(v, currentSelectDict.chapterWordNumber)
|
||||
currentSelectDict.originWords = v
|
||||
currentSelectDict.words = v
|
||||
currentSelectDict.chapterWords = chunk(v, currentSelectDict.chapterWordNumber)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -66,16 +68,17 @@ function close() {
|
||||
}
|
||||
|
||||
function resetChapterList() {
|
||||
currentSelectDict.chapterList = chunk(currentSelectDict.wordList, currentSelectDict.chapterWordNumber)
|
||||
currentSelectDict.chapterWords = chunk(currentSelectDict.words, currentSelectDict.chapterWordNumber)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :modelValue="props.modelValue"
|
||||
:show-close="false"
|
||||
@close="close">
|
||||
<div class="slide">
|
||||
<div class="slide-list" :class="`step${step}`">
|
||||
<div class="dict-page">
|
||||
<div class="slide-list" :class="`step${step}`">
|
||||
<div class="dict-page">
|
||||
<header>
|
||||
<div class="tabs">
|
||||
<div class="tab">
|
||||
@@ -88,7 +91,10 @@ function resetChapterList() {
|
||||
<span>德语</span>
|
||||
</div>
|
||||
</div>
|
||||
<Close @click="close" theme="outline" size="20" fill="#929596" :strokeWidth="2"/>
|
||||
<Icon @click="close"
|
||||
class="hvr-grow pointer"
|
||||
width="20" color="#929596"
|
||||
icon="ion:close-outline"/>
|
||||
</header>
|
||||
<div class="page-content">
|
||||
<div class="dict-list-wrapper">
|
||||
@@ -104,11 +110,9 @@ function resetChapterList() {
|
||||
<div class="desc">{{ i.description }}</div>
|
||||
<div class="num">{{ i.length }}词</div>
|
||||
|
||||
<arrow-right v-if="currentSelectDict.name === i.name"
|
||||
@click.stop="step = 1"
|
||||
class="go"
|
||||
theme="outline" size="20" fill="#ffffff"
|
||||
:strokeWidth="2"/>
|
||||
<Icon icon="octicon:arrow-right-24" v-if="currentSelectDict.name === i.name"
|
||||
@click.stop="step = 1"
|
||||
class="go" width="20" color="#929596"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -125,7 +129,7 @@ function resetChapterList() {
|
||||
</div>
|
||||
<ChapterList
|
||||
class="chapter-list"
|
||||
:list="currentSelectDict.chapterList"
|
||||
:list="currentSelectDict.chapterWords"
|
||||
v-model:active-index="currentSelectDict.chapterIndex"
|
||||
/>
|
||||
<div class="footer">
|
||||
@@ -134,19 +138,20 @@ function resetChapterList() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dict-detail-page">
|
||||
<div class="dict-detail-page">
|
||||
<header>
|
||||
<div class="left">
|
||||
<arrow-left
|
||||
@click="step = 0"
|
||||
class="go" theme="outline" size="20" fill="#ffffff"
|
||||
:strokeWidth="2"/>
|
||||
<Icon icon="octicon:arrow-left-24"
|
||||
@click.stop="step = 0"
|
||||
class="go" width="20" color="#ffffff"/>
|
||||
<div class="title">
|
||||
词典详情
|
||||
</div>
|
||||
</div>
|
||||
<Close @click="close" theme="outline" size="20" fill="#929596"
|
||||
:strokeWidth="2"/>
|
||||
<Icon @click="close"
|
||||
class="hvr-grow pointer"
|
||||
width="20" color="#929596"
|
||||
icon="ion:close-outline"/>
|
||||
</header>
|
||||
<div class="page-content">
|
||||
<div class="dict-info">
|
||||
@@ -157,7 +162,7 @@ function resetChapterList() {
|
||||
</div>
|
||||
</div>
|
||||
<div class="chapter-wrapper">
|
||||
<ChapterList :list="currentSelectDict.chapterList"
|
||||
<ChapterList :list="currentSelectDict.chapterWords"
|
||||
v-model:active-index="currentSelectDict.chapterIndex"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import Modal from "@/components/Modal/Modal.vue"
|
||||
import {HeadphoneSound, SettingConfig} from "@icon-park/vue-next"
|
||||
import {useBaseStore} from "@/stores/base.ts"
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import {GITHUB} from "@/config/ENV.ts";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import Modal from "@/components/Modal/Modal.vue"
|
||||
import {HeadphoneSound, SettingConfig} from "@icon-park/vue-next"
|
||||
import {useBaseStore} from "@/stores/base.ts"
|
||||
import {Icon} from '@iconify/vue';
|
||||
|
||||
const tabIndex = $ref(0)
|
||||
const store = useBaseStore()
|
||||
@@ -27,11 +27,11 @@ const emit = defineEmits([
|
||||
<div class="setting-modal">
|
||||
<div class="tabs">
|
||||
<div class="tab" :class="tabIndex === 0 && 'active'" @click="tabIndex = 0">
|
||||
<headphone-sound theme="filled" size="20" fill="#0C8CE9" :strokeWidth="2"/>
|
||||
<Icon icon="bx:headphone" width="20" color="#0C8CE9"/>
|
||||
<span>音效设置</span>
|
||||
</div>
|
||||
<div class="tab" :class="tabIndex === 1 && 'active'" @click="tabIndex = 1">
|
||||
<setting-config theme="filled" size="20" fill="#0C8CE9" :strokeWidth="2"/>
|
||||
<Icon icon="icon-park-outline:setting-config" width="20" color="#0C8CE9"/>
|
||||
<span>其他设置</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -239,6 +239,9 @@ const emit = defineEmits([
|
||||
cursor: pointer;
|
||||
padding: 10rem 15rem;
|
||||
border-radius: 8rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rem;
|
||||
|
||||
&.active {
|
||||
background: whitesmoke;
|
||||
|
||||
@@ -1,36 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import Tooltip from "@/components/Tooltip.vue"
|
||||
import {
|
||||
DatabaseFail,
|
||||
Down,
|
||||
MenuFold,
|
||||
Moon,
|
||||
PreviewCloseOne,
|
||||
PreviewOpen,
|
||||
SettingTwo,
|
||||
SunOne,
|
||||
VolumeNotice,
|
||||
Bug,
|
||||
UploadOne
|
||||
} from "@icon-park/vue-next"
|
||||
import useTheme from "@/hooks/useTheme.ts"
|
||||
import {useBaseStore} from "@/stores/base.ts"
|
||||
import SettingModal from "@/components/Toolbar/SettingModal.vue"
|
||||
import FeedbackModal from "@/components/Toolbar/FeedbackModal.vue"
|
||||
import DictModal from "@/components/Toolbar/DictModal.vue"
|
||||
|
||||
import {Icon} from '@iconify/vue';
|
||||
|
||||
import IconWrapper from "@/components/IconWrapper.vue";
|
||||
import IconCog6Tooth from '~icons/heroicons/cog-6-tooth-solid'
|
||||
|
||||
import IconLanguage from '~icons/tabler/language'
|
||||
import IconLanguageOff from '~icons/tabler/language-off'
|
||||
|
||||
import IconEye from '~icons/heroicons/eye-solid'
|
||||
import IconCheck from '~icons/tabler/check'
|
||||
import IconEyeSlash from '~icons/heroicons/eye-slash-solid'
|
||||
|
||||
import IconRepeat from '~icons/tabler/repeat'
|
||||
import IconRepeatOff from '~icons/tabler/repeat-off'
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts"
|
||||
import {watch} from "vue"
|
||||
|
||||
@@ -56,69 +34,69 @@ watch(() => store.setting.showToolbar, n => {
|
||||
<template>
|
||||
<header ref="headerRef">
|
||||
<div class="info" @click="showDictModal = true">
|
||||
{{ store.dictTitle }}
|
||||
{{ store.dictTitle }}
|
||||
</div>
|
||||
<div class="options">
|
||||
<Tooltip title="切换主题">
|
||||
<IconWrapper>
|
||||
<moon v-if="store.theme === 'dark'"
|
||||
<Icon icon="ep:moon" v-if="store.theme === 'dark'"
|
||||
@click="toggle"/>
|
||||
<sun-one v-else @click="toggle"/>
|
||||
<Icon icon="tabler:sun" v-else @click="toggle"/>
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="音效设置">
|
||||
<IconWrapper>
|
||||
<volume-notice/>
|
||||
<Icon icon="icon-park-outline:volume-notice"/>
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="设置单词循环">
|
||||
<IconWrapper>
|
||||
<IconRepeat></IconRepeat>
|
||||
<Icon icon="tabler:repeat"/>
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
<Tooltip title="开关默写模式">
|
||||
<IconWrapper>
|
||||
<IconEyeSlash v-if="store.isDictation" @click="store.isDictation = false"></IconEyeSlash>
|
||||
<IconEye v-else @click="store.isDictation = true"></IconEye>
|
||||
<Icon icon="majesticons:eye-off-line" v-if="store.isDictation" @click="store.isDictation = false"/>
|
||||
<Icon icon="mdi:eye-outline" v-else @click="store.isDictation = true"/>
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
<Tooltip title="开关释义显示">
|
||||
<IconWrapper>
|
||||
<IconLanguage></IconLanguage>
|
||||
<!-- <IconLanguageOff></IconLanguageOff>-->
|
||||
<Icon icon="heroicons-outline:translate"/>
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="反馈">
|
||||
<IconWrapper>
|
||||
<upload-one/>
|
||||
<Icon icon="ic:outline-cloud-upload"/>
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
<Tooltip title="反馈">
|
||||
<IconWrapper>
|
||||
<bug @click="showFeedbackModal = true"/>
|
||||
<Icon icon="octicon:bug-24" @click="showFeedbackModal = true"/>
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
<Tooltip title="设置">
|
||||
<IconWrapper>
|
||||
<IconCog6Tooth @click="showSettingModal = true"></IconCog6Tooth>
|
||||
<Icon icon="uil:setting" @click="showSettingModal = true"/>
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
<div class="my-button" @click="emitter.emit(EventKey.openStatModal)">ok</div>
|
||||
|
||||
<Tooltip title="单词本">
|
||||
<IconWrapper>
|
||||
<menu-fold class="menu" @click="emitter.emit(EventKey.openSide)"/>
|
||||
<Icon icon="tdesign:menu-unfold" class="menu" @click="emitter.emit(EventKey.openSide)"/>
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Tooltip :title="store.setting.showToolbar?'收起':'展开'">
|
||||
<down
|
||||
@click="store.setting.showToolbar = !store.setting.showToolbar"
|
||||
class="arrow"
|
||||
:class="!store.setting.showToolbar && 'down'"
|
||||
theme="outline" size="24" fill="#999"/>
|
||||
<Icon icon="icon-park-outline:down"
|
||||
@click="store.setting.showToolbar = !store.setting.showToolbar"
|
||||
class="arrow"
|
||||
:class="!store.setting.showToolbar && 'down'"
|
||||
width="24" color="#999"/>
|
||||
</Tooltip>
|
||||
</header>
|
||||
<DictModal v-model="showDictModal"/>
|
||||
|
||||
@@ -3,7 +3,7 @@ import {Word} from "../types";
|
||||
import {usePlayWordAudio} from "../hooks/usePlayWordAudio";
|
||||
import {watch} from "vue"
|
||||
import {useBaseStore} from "@/stores/base.ts"
|
||||
import {Delete} from "@icon-park/vue-next"
|
||||
import {Icon} from '@iconify/vue';
|
||||
|
||||
const store = useBaseStore()
|
||||
const emit = defineEmits(['change'])
|
||||
@@ -53,7 +53,7 @@ watch(() => props.list, () => {
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="audio" @click="playAudio(item.name)">播放</div>
|
||||
<delete theme="outline" size="20" fill="#929596" :strokeWidth="2"/>
|
||||
<Icon icon="fluent:delete-28-regular" width="20" color="#929596"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {DefaultArticleWord, Sentence, Word} from "@/types.ts";
|
||||
import {cloneDeep} from "lodash";
|
||||
import {cloneDeep} from "lodash-es";
|
||||
|
||||
interface KeyboardMap {
|
||||
Period: string,
|
||||
|
||||
@@ -3,12 +3,12 @@ import './assets/css/style.scss'
|
||||
import App from './App.vue'
|
||||
// import Mobile from './Mobile.vue'
|
||||
import {createPinia} from "pinia"
|
||||
import ElementPlus from 'element-plus'
|
||||
// import ElementPlus from 'element-plus'
|
||||
|
||||
const pinia = createPinia()
|
||||
// const app = createApp(Mobile)
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(ElementPlus)
|
||||
// app.use(ElementPlus)
|
||||
app.use(pinia)
|
||||
app.mount('#app')
|
||||
@@ -1,6 +1,6 @@
|
||||
import {defineStore} from 'pinia'
|
||||
import {Dict, DictType, Sort, State, Word} from "../types.ts"
|
||||
import {chunk, cloneDeep} from "lodash";
|
||||
import {chunk, cloneDeep} from "lodash-es";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts"
|
||||
|
||||
export const useBaseStore = defineStore('base', {
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
import {
|
||||
Language,
|
||||
Translator,
|
||||
TranslateError,
|
||||
TranslateQueryResult
|
||||
} from "@opentranslate/translator";
|
||||
import md5 from "md5";
|
||||
import qs from "qs";
|
||||
|
||||
const langMap: [Language, string][] = [
|
||||
["auto", "auto"],
|
||||
["zh-CN", "zh"],
|
||||
["en", "en"],
|
||||
["yue", "yue"],
|
||||
["wyw", "wyw"],
|
||||
["ja", "jp"],
|
||||
["ko", "kor"],
|
||||
["fr", "fra"],
|
||||
["es", "spa"],
|
||||
["th", "th"],
|
||||
["ar", "ara"],
|
||||
["ru", "ru"],
|
||||
["pt", "pt"],
|
||||
["de", "de"],
|
||||
["it", "it"],
|
||||
["el", "el"],
|
||||
["nl", "nl"],
|
||||
["pl", "pl"],
|
||||
["bg", "bul"],
|
||||
["et", "est"],
|
||||
["da", "dan"],
|
||||
["fi", "fin"],
|
||||
["cs", "cs"],
|
||||
["ro", "rom"],
|
||||
["sl", "slo"],
|
||||
["sv", "swe"],
|
||||
["hu", "hu"],
|
||||
["zh-TW", "cht"],
|
||||
["vi", "vie"]
|
||||
];
|
||||
|
||||
export interface BaiduConfig {
|
||||
placeholder?: string;
|
||||
appid: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export class BaiduTranslate {
|
||||
readonly name = "baidu";
|
||||
apiUrl = "https://api.fanyi.baidu.com/api/trans/vip/translate";
|
||||
|
||||
readonly endpoint = "https://api.fanyi.baidu.com/api/trans/vip/translate";
|
||||
|
||||
protected async query(
|
||||
text: string,
|
||||
from: Language,
|
||||
to: Language,
|
||||
config: BaiduConfig
|
||||
): Promise<TranslateQueryResult> {
|
||||
type BaiduTranslateError = {
|
||||
error_code: "54001" | string;
|
||||
error_msg: "Invalid Sign" | string;
|
||||
};
|
||||
|
||||
type BaiduTranslateResult = {
|
||||
from: string;
|
||||
to: string;
|
||||
trans_result: Array<{
|
||||
dst: string;
|
||||
src: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
const salt = Date.now();
|
||||
const {endpoint} = this;
|
||||
const {appid, key} = config;
|
||||
|
||||
const res = await this.request<BaiduTranslateResult | BaiduTranslateError>(
|
||||
endpoint,
|
||||
{
|
||||
params: {
|
||||
from: Baidu.langMap.get(from),
|
||||
to: Baidu.langMap.get(to),
|
||||
q: text,
|
||||
salt,
|
||||
appid,
|
||||
sign: md5(appid + text + salt + key)
|
||||
}
|
||||
}
|
||||
).catch(() => {
|
||||
throw new TranslateError("NETWORK_ERROR");
|
||||
});
|
||||
|
||||
const {data} = res;
|
||||
|
||||
if ((data as BaiduTranslateError).error_code) {
|
||||
console.error(
|
||||
new Error("[Baidu service]" + (data as BaiduTranslateError).error_msg)
|
||||
);
|
||||
throw new TranslateError("API_SERVER_ERROR");
|
||||
}
|
||||
|
||||
const {
|
||||
trans_result: transResult,
|
||||
from: langDetected
|
||||
} = data as BaiduTranslateResult;
|
||||
const transParagraphs = transResult.map(({dst}) => dst);
|
||||
const detectedFrom = Baidu.langMapReverse.get(langDetected) as Language;
|
||||
|
||||
return {
|
||||
text,
|
||||
from: detectedFrom,
|
||||
to,
|
||||
origin: {
|
||||
paragraphs: transResult.map(({src}) => src),
|
||||
tts: await this.textToSpeech(text, detectedFrom)
|
||||
},
|
||||
trans: {
|
||||
paragraphs: transParagraphs,
|
||||
tts: await this.textToSpeech(transParagraphs.join(" "), to)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Translator lang to custom lang */
|
||||
private static readonly langMap = new Map(langMap);
|
||||
|
||||
/** Custom lang to translator lang */
|
||||
private static readonly langMapReverse = new Map(
|
||||
langMap.map(([translatorLang, lang]) => [lang, translatorLang])
|
||||
);
|
||||
|
||||
getSupportLanguages(): Language[] {
|
||||
return [...Baidu.langMap.keys()];
|
||||
}
|
||||
|
||||
async textToSpeech(text: string, lang: Language): Promise<string> {
|
||||
return `https://fanyi.baidu.com/gettts?${qs.stringify({
|
||||
lan: Baidu.langMap.get(lang !== "auto" ? lang : "zh-CN") || "zh",
|
||||
text,
|
||||
spd: 5,
|
||||
})}`;
|
||||
}
|
||||
}
|
||||
|
||||
export default Baidu;
|
||||
Reference in New Issue
Block a user