feat(all): add sub-setting

This commit is contained in:
zyronon
2023-09-12 18:52:11 +08:00
parent a2b1d7b492
commit df22b4229e
15 changed files with 346 additions and 121 deletions

8
components.d.ts vendored
View File

@@ -13,24 +13,32 @@ declare module 'vue' {
ChapterList: typeof import('./src/components/ChapterList.vue')['default']
DictList: typeof import('./src/components/DictList.vue')['default']
DictModal: typeof import('./src/components/Toolbar/DictModal.vue')['default']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElProgress: typeof import('element-plus/es')['ElProgress']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElSlider: typeof import('element-plus/es')['ElSlider']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
FeedbackModal: typeof import('./src/components/Toolbar/FeedbackModal.vue')['default']
Fireworks: typeof import('./src/components/Fireworks.vue')['default']
Footer: typeof import('./src/components/Practice/Footer.vue')['default']
IconWrapper: typeof import('./src/components/IconWrapper.vue')['default']
MiniModal: typeof import('./src/components/MiniModal.vue')['default']
Modal: typeof import('./src/components/Modal/Modal.vue')['default']
PopConfirm: typeof import('./src/components/PopConfirm.vue')['default']
Practice: typeof import('./src/components/Practice/Practice.vue')['default']
RepeatSetting: typeof import('./src/components/Toolbar/RepeatSetting.vue')['default']
Ring: typeof import('./src/components/Ring.vue')['default']
SettingModal: typeof import('./src/components/Toolbar/SettingModal.vue')['default']
Side: typeof import('./src/components/Side.vue')['default']
Statistics: typeof import('./src/components/Modal/Statistics.vue')['default']
Toolbar: typeof import('./src/components/Toolbar/Toolbar.vue')['default']
Tooltip: typeof import('./src/components/Tooltip.vue')['default']
TrabslateSetting: typeof import('./src/components/Toolbar/TrabslateSetting.vue')['default']
TypeArticle: typeof import('./src/components/Practice/TypeArticle.vue')['default']
TypeWord: typeof import('./src/components/Practice/TypeWord.vue')['default']
VolumeSetting: typeof import('./src/components/Toolbar/VolumeSetting.vue')['default']
WordList: typeof import('./src/components/WordList.vue')['default']
}
}

View File

@@ -3,7 +3,7 @@ export default {
render() {
let Vnode = this.$slots.default()[0]
return (
<div class="icon-wrapper hvr-grow">
<div class="icon-wrapper ">
<Vnode
/>
</div>
@@ -17,9 +17,9 @@ export default {
$w: 20rem;
.icon-wrapper {
padding: 2rem;
//width: 24rem;
//height: 24rem;
//padding: 2rem;
width: 26rem;
height: 26rem;
display: inline-flex;
align-items: center;
justify-content: center;

View File

@@ -0,0 +1,44 @@
<script setup lang="ts">
interface IProps {
modelValue?: boolean,
}
withDefaults(defineProps<IProps>(), {
modelValue: true,
})
</script>
<template>
<Transition name="fade">
<div v-if="modelValue" class="mini-modal">
<slot></slot>
</div>
</Transition>
</template>
<style lang="scss">
@import "@/assets/css/style";
.mini-row {
display: flex;
justify-content: space-between;
align-items: center;
gap: $space;
color: black;
word-break: keep-all;
}
.mini-modal {
position: absolute;
z-index: 9;
width: 180rem;
background: white;
border-radius: 8rem;
box-shadow: 1px 1px 6px #bbbbbb;
padding: 10rem $space;
left: 50%;
transform: translateX(-50%);
margin-top: 10rem;
}
</style>

View File

@@ -23,7 +23,7 @@ onMounted(() => {
})
function write() {
store.isDictation = true
store.setting.dictation = true
repeat()
}

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import {Icon} from '@iconify/vue'
import {usePlayWordAudio} from "@/hooks/usePlayWordAudio.ts"
import {computed, nextTick, onMounted, reactive, watch} from "vue"
import {cloneDeep} from "lodash-es"
@@ -28,16 +28,13 @@ import {
Word
} from "@/types";
import {useBaseStore} from "@/stores/base";
import Footer from "@/components/Practice/Footer.vue"
import {usePracticeStore} from "@/components/Practice/usePracticeStore.ts";
import {useEventListener} from "@/hooks/useEvent.ts";
import TypeWord from "@/components/Practice/TypeWord.vue";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import Baidu from "@opentranslate/baidu";
import {axiosInstance} from "@/utils/http";
import {translate} from "element-plus";
import useSleep from "@/hooks/useSleep.ts";
import {useLocalTranslate, useNetworkTranslate} from "@/hooks/translate.ts";
import IconWrapper from "@/components/IconWrapper.vue";
import Tooltip from "@/components/Tooltip.vue";
import MiniModal from "@/components/MiniModal.vue";
let article1 = `How does the older investor differ in his approach to investment from the younger investor?
There is no shortage of tipsters around offering 'get-rich-quick' opportunities. But if you are a serious private investor, leave the Las Vegas mentality to those with money to fritter. The serious investor needs a proper 'portfolio' -- a well-planned selection of investments, with a definite structure and a clear aim. But exactly how does a newcomer to the stock market go about achieving that?
@@ -85,9 +82,7 @@ let stringIndex = $ref(0)
let input = $ref('')
let wrong = $ref('')
let isSpace = $ref(false)
let isDictation = $ref(true)
let showTranslate = $ref(true)
let showFullWord = $ref(false)
let hoverIndex = $ref({
sectionIndex: -1,
sentenceIndex: -1,
@@ -204,7 +199,7 @@ function onKeyDown(e: KeyboardEvent) {
sentenceIndex = 0
sectionIndex++
} else {
if (isDictation) {
if (store.setting.dictation) {
calcTranslateLocation()
}
playAudio(currentSection[sentenceIndex].sentence)
@@ -328,8 +323,8 @@ function onKeyUp() {
}
}
useEventListener('keydown', onKeyDown)
useEventListener('keyup', onKeyUp)
// useEventListener('keydown', onKeyDown)
// useEventListener('keyup', onKeyUp)
function playWord(word: ArticleWord) {
playAudio(word.name)
@@ -344,7 +339,7 @@ function currentWordInput(word: ArticleWord, i: number, i2: number,) {
return str
}
if (isDictation) {
if (store.setting.dictation) {
return '_'
}
return str
@@ -356,7 +351,7 @@ function currentWordEnd(word: ArticleWord, i: number, i2: number,) {
return str
}
if (isDictation) {
if (store.setting.dictation) {
return str.split('').map(v => '_').join('')
}
return str
@@ -374,7 +369,7 @@ function otherWord(word: ArticleWord, i: number, i2: number, i3: number) {
//剩100是因为可能存在特殊情况比如003,010这种0 12 24100
if (sectionIndex * 10000 + sentenceIndex * 100 + wordIndex < i * 10000 + i2 * 100 + i3
&& isDictation
&& store.setting.dictation
) {
return str.split('').map(v => '_').join('')
}
@@ -391,17 +386,6 @@ function otherWord(word: ArticleWord, i: number, i2: number, i3: number) {
<div class="article-wrapper">
<header>
<div class="title">A private conversation!</div>
<div class="options">
<el-progress :percentage="80"
:stroke-width="8"
:show-text="false"/>
<div class="translate-btn">
<div>翻译</div>
<div>本地</div>
</div>
</div>
</header>
<div class="article-content" ref="articleWrapperRef">
<article>
@@ -409,7 +393,7 @@ function otherWord(word: ArticleWord, i: number, i2: number, i3: number) {
v-for="(section,indexI) in article.sections">
<span class="sentence"
:class="[
sectionIndex === indexI && sentenceIndex === indexJ && isDictation
sectionIndex === indexI && sentenceIndex === indexJ && store.setting.dictation
?'dictation':''
]"
@mouseenter="hoverIndex = {sectionIndex : indexI,sentenceIndex :indexJ}"
@@ -445,9 +429,11 @@ function otherWord(word: ArticleWord, i: number, i2: number, i3: number) {
`${indexI}${indexJ}${indexW}`,
(`${indexI}${indexJ}${indexW}` === currentIndex && isSpace && wrong) && 'bg-wrong',
(`${indexI}${indexJ}${indexW}` === currentIndex && isSpace && !wrong) && 'bottom-border',
(`${indexI}${indexJ}${indexW}` === currentIndex && isSpace && !wrong && isDictation) && 'word-space',
(`${indexI}${indexJ}${indexW}` === currentIndex && isSpace && !wrong && store.setting.dictation) && 'word-space',
]">
{{ (`${indexI}${indexJ}${indexW}` === currentIndex && isSpace && isDictation) ? '_' : ' ' }}
{{
(`${indexI}${indexJ}${indexW}` === currentIndex && isSpace && store.setting.dictation) ? '_' : ' '
}}
</span>
</span>
</span>
@@ -500,7 +486,6 @@ function otherWord(word: ArticleWord, i: number, i2: number, i3: number) {
}
.article-wrapper {
opacity: 0;
header {
.title {
@@ -511,21 +496,6 @@ function otherWord(word: ArticleWord, i: number, i2: number, i3: number) {
word-spacing: 3rem;
opacity: 0;
}
.options {
display: flex;
gap:20rem;
.el-progress {
flex: 1;
}
.translate-btn {
color:black;
font-size: 20rem;
}
}
}
.article-content {

View File

@@ -204,7 +204,7 @@ async function onKeyDown(e: KeyboardEvent) {
<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">
<template v-if="store.setting.dictation">
<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>

View File

@@ -0,0 +1,74 @@
<script setup lang="ts">
import MiniModal from "@/components/MiniModal.vue";
import {Icon} from "@iconify/vue";
import IconWrapper from "@/components/IconWrapper.vue";
import Tooltip from "@/components/Tooltip.vue";
import {useBaseStore} from "@/stores/base.ts";
import {useWindowClick} from "@/hooks/event.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import {onMounted} from "vue";
const store = useBaseStore()
let show = $ref(false)
let radio1 = $ref('1')
useWindowClick(() => show = false)
function toggle() {
if (!show) emitter.emit(EventKey.closeOther)
show = !show
}
onMounted(() => {
})
</script>
<template>
<div class="setting" @click.stop="null">
<Tooltip title="单词循环设置">
<IconWrapper>
<Icon icon="tabler:repeat"
@click="toggle"/>
</IconWrapper>
</Tooltip>
<MiniModal
v-model="show"
style="width: 230rem;"
>
<div class="title">选择单词的循环次数</div>
<el-radio-group v-model="store.setting.repeatCount">
<el-radio :label="1" size="default">1</el-radio>
<el-radio :label="2" size="default">2</el-radio>
<el-radio :label="3" size="default">3</el-radio>
<el-radio :label="5" size="default">5</el-radio>
<el-radio :label="100" size="default">自定义</el-radio>
</el-radio-group>
<div class="mini-row" v-if="store.setting.repeatCount === 100">
<label class="item-title">自定义循环次数</label>
<el-input-number v-model="store.setting.repeatCustomCount"
:min="6"
:max="100"
type="number"
/>
</div>
</MiniModal>
</div>
</template>
<style scoped lang="scss">
@import "@/assets/css/style";
.setting {
position: relative;
.title {
color: black;
}
.el-radio-group {
display: flex;
flex-direction: column;
align-items: flex-start;
}
}
</style>

View File

@@ -11,6 +11,8 @@ import {Icon} from '@iconify/vue';
import IconWrapper from "@/components/IconWrapper.vue";
import {emitter, EventKey} from "@/utils/eventBus.ts"
import {watch} from "vue"
import VolumeSetting from "@/components/Toolbar/VolumeSetting.vue";
import RepeatSetting from "@/components/Toolbar/RepeatSetting.vue";
const {toggle} = useTheme()
const store = useBaseStore()
@@ -33,70 +35,77 @@ watch(() => store.setting.showToolbar, n => {
<template>
<header ref="headerRef">
<div class="info" @click="showDictModal = true">
{{ store.dictTitle }}
<div class="content">
<div class="info" @click="showDictModal = true">
{{ store.dictTitle }}
</div>
<div class="options">
<Tooltip title="切换主题">
<IconWrapper>
<Icon icon="ep:moon" v-if="store.theme === 'dark'"
@click="toggle"/>
<Icon icon="tabler:sun" v-else @click="toggle"/>
</IconWrapper>
</Tooltip>
<VolumeSetting/>
<RepeatSetting/>
<Tooltip title="开关默写模式">
<IconWrapper>
<Icon icon="majesticons:eye-off-line"
v-if="store.setting.dictation"
@click="store.setting.dictation = false"/>
<Icon icon="mdi:eye-outline"
v-else
@click="store.setting.dictation = true"/>
</IconWrapper>
</Tooltip>
<Tooltip title="开关释义显示">
<IconWrapper>
<Icon icon="heroicons-outline:translate"/>
</IconWrapper>
</Tooltip>
<Tooltip title="反馈">
<IconWrapper>
<Icon icon="ic:outline-cloud-upload"/>
</IconWrapper>
</Tooltip>
<Tooltip title="反馈">
<IconWrapper>
<Icon icon="octicon:bug-24" @click="showFeedbackModal = true"/>
</IconWrapper>
</Tooltip>
<Tooltip title="设置">
<IconWrapper>
<Icon icon="uil:setting" @click="showSettingModal = true"/>
</IconWrapper>
</Tooltip>
<div class="my-button" @click="emitter.emit(EventKey.openStatModal)">ok</div>
<Tooltip title="单词本">
<IconWrapper>
<Icon icon="tdesign:menu-unfold" class="menu" @click="emitter.emit(EventKey.openSide)"/>
</IconWrapper>
</Tooltip>
</div>
</div>
<div class="options">
<Tooltip title="切换主题">
<IconWrapper>
<Icon icon="ep:moon" v-if="store.theme === 'dark'"
@click="toggle"/>
<Icon icon="tabler:sun" v-else @click="toggle"/>
</IconWrapper>
</Tooltip>
<Tooltip title="音效设置">
<IconWrapper>
<Icon icon="icon-park-outline:volume-notice"/>
</IconWrapper>
</Tooltip>
<Tooltip title="设置单词循环">
<IconWrapper>
<Icon icon="tabler:repeat"/>
</IconWrapper>
</Tooltip>
<Tooltip title="开关默写模式">
<IconWrapper>
<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>
<Icon icon="heroicons-outline:translate"/>
</IconWrapper>
</Tooltip>
<Tooltip title="反馈">
<IconWrapper>
<Icon icon="ic:outline-cloud-upload"/>
</IconWrapper>
</Tooltip>
<Tooltip title="反馈">
<IconWrapper>
<Icon icon="octicon:bug-24" @click="showFeedbackModal = true"/>
</IconWrapper>
</Tooltip>
<Tooltip title="设置">
<IconWrapper>
<Icon icon="uil:setting" @click="showSettingModal = true"/>
</IconWrapper>
</Tooltip>
<div class="my-button" @click="emitter.emit(EventKey.openStatModal)">ok</div>
<Tooltip title="单词本">
<IconWrapper>
<Icon icon="tdesign:menu-unfold" class="menu" @click="emitter.emit(EventKey.openSide)"/>
</IconWrapper>
</Tooltip>
<div class="translate-progress">
<div>翻译进度:</div>
<el-progress :percentage="80"
:stroke-width="8"
:show-text="false"/>
</div>
<Tooltip :title="store.setting.showToolbar?'收起':'展开'">
<Icon icon="icon-park-outline:down"
@click="store.setting.showToolbar = !store.setting.showToolbar"
class="arrow"
:class="!store.setting.showToolbar && 'down'"
width="24" color="#999"/>
width="24"
color="#999"/>
</Tooltip>
</header>
<DictModal v-model="showDictModal"/>
@@ -110,10 +119,8 @@ watch(() => store.setting.showToolbar, n => {
header {
width: var(--toolbar-width);
margin-top: 10rem;
min-height: 60rem;
background: var(--color-header-bg);
display: flex;
justify-content: space-between;
border-radius: 8rem;
margin-bottom: 30rem;
//position: absolute;
@@ -125,21 +132,37 @@ header {
gap: 10rem;
//opacity: 0;
.info {
font-size: 16rem;
color: var(--color-font-1);
.content {
min-height: 60rem;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
justify-content: space-between;
.info {
font-size: 16rem;
color: var(--color-font-1);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.options {
display: flex;
align-items: center;
gap: 10rem;
}
}
.options {
.translate-progress {
display: flex;
align-items: center;
gap: 10rem;
gap: $space;
.el-progress {
flex: 1;
}
}
.arrow {
position: absolute;
bottom: 0;

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
</template>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,67 @@
<script setup lang="ts">
import MiniModal from "@/components/MiniModal.vue";
import {Icon} from "@iconify/vue";
import IconWrapper from "@/components/IconWrapper.vue";
import Tooltip from "@/components/Tooltip.vue";
import {useBaseStore} from "@/stores/base.ts";
import {useWindowClick} from "@/hooks/event.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
const store = useBaseStore()
let show = $ref(false)
useWindowClick(() => show = false)
function toggle() {
if (!show) emitter.emit(EventKey.closeOther)
show = !show
}
</script>
<template>
<div class="setting" @click.stop="null">
<Tooltip title="音效设置">
<IconWrapper>
<Icon icon="icon-park-outline:volume-notice"
@click="toggle"
/>
</IconWrapper>
</Tooltip>
<MiniModal v-model="show">
<div class="mini-row">
<label class="item-title">按键音</label>
<div class="wrapper">
<el-switch v-model="store.setting.keybroadVolume"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="mini-row">
<label class="item-title">按键音</label>
<div class="wrapper">
<el-switch v-model="store.setting.effectVolume"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
</MiniModal>
</div>
</template>
<style scoped lang="scss">
@import "@/assets/css/style";
.wrapper {
position: relative;
}
.setting {
position: relative;
}
</style>

View File

@@ -77,6 +77,6 @@ export default {
padding: 10rem;
color: var(--color-font-1);
background: var(--color-tooltip-bg);
//box-shadow: 1px 1px 6px #bbbbbb;
box-shadow: 1px 1px 6px #bbbbbb;
}
</style>

12
src/hooks/event.ts Normal file
View File

@@ -0,0 +1,12 @@
import {onMounted, onUnmounted} from "vue";
import {emitter, EventKey} from "@/utils/eventBus.ts";
export function useWindowClick(cb: () => void) {
onMounted(() => {
emitter.on(EventKey.closeOther, cb)
window.addEventListener('click', cb)
})
onUnmounted(() => {
window.removeEventListener('click', cb)
})
}

View File

@@ -80,6 +80,14 @@ export const useBaseStore = defineStore('base', {
setting: {
showToolbar: true,
show: false,
keyboardVolume: true,
effectVolume: true,
repeatCount: 1,
repeatCustomCount: null,
dictation: true,
showTranslate: true,
value1: false,
value2: 50,
value3: 1,

View File

@@ -183,6 +183,13 @@ export interface State {
setting: {
showToolbar: boolean,
show: boolean,
keyboardVolume: boolean,
effectVolume: boolean,
repeatCount: number,
repeatCustomCount?: number,
dictation: boolean,
value1: boolean,
value2: number,
value3: number,

View File

@@ -5,4 +5,5 @@ export const EventKey = {
resetWord: 'resetWord',
openSide: 'openSide',
openStatModal: 'openStatModal',
closeOther: 'closeOther',
}