This commit is contained in:
zyronon
2023-10-24 18:58:33 +08:00
parent 37e2cfa154
commit a3e002b116
7 changed files with 382 additions and 12 deletions

15
components.d.ts vendored
View File

@@ -9,6 +9,7 @@ declare module 'vue' {
export interface GlobalComponents {
Add: typeof import('./src/components/Toolbar/Add.vue')['default']
AddDict: typeof import('./src/components/Add/AddDict.vue')['default']
ArticlePanel: typeof import('./src/components/Practice/PracticeArticle/ArticlePanel.vue')['default']
Backgorund: typeof import('./src/components/Backgorund.vue')['default']
BaseButton: typeof import('./src/components/BaseButton.vue')['default']
BaseIcon: typeof import('./src/components/BaseIcon.vue')['default']
@@ -39,13 +40,16 @@ declare module 'vue' {
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']
Index: typeof import('./src/components/Practice/PracticeArticle/Index.vue')['default']
Input: typeof import('./src/components/Input.vue')['default']
List: typeof import('./src/components/List.vue')['default']
MiniModal: typeof import('./src/components/MiniModal.vue')['default']
Modal: typeof import('./src/components/Modal/Modal.vue')['default']
Panel: typeof import('./src/components/Practice/Panel.vue')['default']
Panel: typeof import('./src/components/Practice/TypingArticle/Panel.vue')['default']
PopConfirm: typeof import('./src/components/PopConfirm.vue')['default']
Practice: typeof import('./src/components/Practice/Practice.vue')['default']
PracticeArticle: typeof import('./src/components/Practice/PracticeArticle/PracticeArticle.vue')['default']
PracticeWord: typeof import('./src/components/Practice/PracticeWord/PracticeWord.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']
@@ -53,12 +57,17 @@ declare module 'vue' {
Toolbar: typeof import('./src/components/Toolbar/Toolbar.vue')['default']
Tooltip: typeof import('./src/components/Tooltip.vue')['default']
TranslateSetting: typeof import('./src/components/Toolbar/TranslateSetting.vue')['default']
TypeArticle: typeof import('./src/components/Practice/TypeArticle.vue')['default']
TypeWord: typeof import('./src/components/Practice/TypeWord.vue')['default']
TypeArticle: typeof import('./src/components/Practice/TypingArticle/TypeArticle.vue')['default']
TypeWord: typeof import('./src/components/Practice/PracticeWord/TypeWord.vue')['default']
Typing: typeof import('./src/components/Practice/TypingArticle/Typing.vue')['default']
TypingArti: typeof import('./src/components/Practice/TypingArticle/TypingArti.vue')['default']
TypingArticle: typeof import('./src/components/Practice/PracticeArticle/TypingArticle.vue')['default']
TypingWord: typeof import('./src/components/Practice/PracticeWord/TypingWord.vue')['default']
VolumeIcon: typeof import('./src/components/VolumeIcon.vue')['default']
VolumeSetting: typeof import('./src/components/Toolbar/VolumeSetting.vue')['default']
WordItem: typeof import('./src/components/WordItem.vue')['default']
WordList: typeof import('./src/components/WordList.vue')['default']
WordListModal: typeof import('./src/components/WordListModal.vue')['default']
WordPanel: typeof import('./src/components/Practice/PracticeWord/WordPanel.vue')['default']
}
}

View File

@@ -252,14 +252,10 @@ $header-height: 50rem;
}
.panel {
position: fixed;
left: 0;
top: 10rem;
border-radius: 8rem;
margin-left: calc(50% + (var(--toolbar-width) / 2) + $space);
width: $width;
background: var(--color-second-bg);
height: calc(100% - 20rem);
height: 100%;
display: flex;
flex-direction: column;
transition: all .3s;

View File

@@ -0,0 +1,24 @@
<script setup lang="ts">
import {$ref} from "vue/macros";
import TypingArticle from "@/components/Practice/PracticeArticle/TypingArticle.vue";
let tabIndex = $ref(0)
</script>
<template>
<div class="swiper-wrapper content">
<div class="swiper-list" :class="`step${tabIndex}`">
<div class="swiper-item">
<TypingArticle/>
</div>
<div class="swiper-item">
<TypingArticle/>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
</style>

View File

@@ -13,6 +13,8 @@ import {emitter, EventKey} from "@/utils/eventBus.ts";
import Tooltip from "@/components/Tooltip.vue";
import IconWrapper from "@/components/IconWrapper.vue";
import {Icon} from "@iconify/vue";
import WordPanel from "@/components/Practice/WordPanel.vue";
import ArticlePanel from "@/components/Practice/ArticlePanel.vue";
interface IProps {
article: Article,
@@ -426,6 +428,7 @@ function toggleCollect() {
}
}
let index = $ref(0)
</script>
<template>
@@ -447,7 +450,7 @@ function toggleCollect() {
<Tooltip title="忽略(快捷键:`)">
<IconWrapper>
<Icon icon="fluent:delete-20-regular" class="menu"
@click="emit('ignore')"/>
@click="tabIndex = 1"/>
</IconWrapper>
</Tooltip>
<Tooltip title="收藏(快捷键Enter)">
@@ -530,6 +533,11 @@ function toggleCollect() {
</div>
</div>
</div>
<Teleport to="body">
<div class="panel-wrapper">
<ArticlePanel :list="[]" v-model:index="index"/>
</div>
</Teleport>
</div>
<div class="swiper-item">
<TypeWord
@@ -551,6 +559,7 @@ function toggleCollect() {
color: rgb(22, 163, 74);
}
$article-width: 1000px;
.typing-wrapper {
flex: 1;
overflow: hidden;
@@ -559,7 +568,7 @@ function toggleCollect() {
align-items: center;
.content {
width: 1000px;
width: $article-width;
}
.article-wrapper {
@@ -733,4 +742,13 @@ function toggleCollect() {
border-left: 1.3rem solid transparent;
}
}
.panel-wrapper {
position: fixed;
left: 0;
top: 10rem;
z-index: 1;
margin-left: calc(50% + ($article-width / 2) + $space);
height: calc(100% - 20rem);
}
</style>

View File

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

View File

@@ -13,7 +13,7 @@ import {useOnKeyboardEventListener} from "@/hooks/event.ts";
import {Icon} from "@iconify/vue";
import VolumeIcon from "@/components/VolumeIcon.vue";
import Tooltip from "@/components/Tooltip.vue";
import Panel from "@/components/Practice/Panel.vue";
import WordPanel from "@/components/Practice/WordPanel.vue";
import IconWrapper from "@/components/IconWrapper.vue";
interface IProps {
@@ -371,7 +371,7 @@ useOnKeyboardEventListener(onKeyDown, onKeyUp)
</IconWrapper>
</Tooltip>
</div>
<Panel :list="data.words" v-model:index="data.index"/>
<WordPanel :list="data.words" v-model:index="data.index"/>
</div>
</template>

View File

@@ -0,0 +1,312 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts"
import WordList from "@/components/WordList.vue"
import {$computed, $ref} from "vue/macros"
import {computed, provide, watch} from "vue"
import {Dict, DictType, Word} from "@/types.ts"
import PopConfirm from "@/components/PopConfirm.vue"
import BaseButton from "@/components/BaseButton.vue";
import {useSettingStore} from "@/stores/setting.ts";
import Close from "@/components/Close.vue";
const props = defineProps<{
list?: Word[],
index: number
}>()
const emit = defineEmits<{
'update:index': [i: number]
}>()
const store = useBaseStore()
const settingStore = useSettingStore()
let tabIndex = $ref(0)
provide('tabIndex', computed(() => tabIndex))
watch(() => settingStore.showPanel, n => {
if (n) {
switch (store.current.dictType) {
case DictType.collect:
return tabIndex = 1;
case DictType.skip:
return tabIndex = 3;
case DictType.wrong:
return tabIndex = 2;
case DictType.word:
case DictType.customWord:
return tabIndex = 0;
}
}
})
const currentDict: Dict = $computed(() => {
return store.myDicts[store.current.index]
})
const currentData = $computed(() => {
if (store.current.dictType !== currentDict.type) return {
list: currentDict.chapterWords[currentDict.chapterIndex] ?? [],
index: -1
}
else return props
})
const newData = $computed(() => {
if (store.current.dictType !== DictType.collect) return {list: store.collect.words ?? [], index: -1}
else return props
})
const wrongData = $computed(() => {
if (store.current.dictType !== DictType.wrong) return {list: store.wrong.words ?? [], index: -1}
else return props
})
const skipData = $computed(() => {
if (store.current.dictType !== DictType.skip) return {list: store.skip.words ?? [], index: -1}
else return props
})
function changeIndex(i: number, dict: Dict) {
dict.wordIndex = i
console.log('i', i, dict.type)
if (store.current.dictType === dict.type) {
emit('update:index', i)
} else {
store.changeDict(dict, dict.chapterIndex, i)
}
}
</script>
<template>
<Teleport to="body">
<Transition name="fade">
<div class="panel" v-if="settingStore.showPanel">
<header>
<Transition name="fade">
<Close
@click="settingStore.showPanel = false"
v-if="!settingStore.showToolbar"/>
</Transition>
<div class="tabs">
<div class="tab current" :class="tabIndex === 0 && 'active'" @click="tabIndex = 0">
{{ currentDict.name + ` ${currentDict.chapterIndex + 1}` }}
</div>
<div class="tab" :class="tabIndex === 1 && 'active'" @click="tabIndex = 1">{{ store.collect.name }}</div>
<div class="tab" :class="tabIndex === 2 && 'active'" @click="tabIndex = 2">
{{ store.wrong.name }}
</div>
<div class="tab" :class="tabIndex === 3 && 'active'" @click="tabIndex = 3">{{ store.skip.name }}</div>
</div>
</header>
<div class="slide">
<div class="slide-list" :class="`step${tabIndex}`">
<div class="slide-item">
<header>
<div class="dict-name">词数{{ currentData.list.length }}</div>
</header>
<div class="content">
<WordList
class="word-list"
@change="(i:number) => changeIndex(i,currentDict)"
:isActive="settingStore.showPanel && tabIndex === 0"
:list="currentData.list"
:activeIndex="currentData.index"/>
</div>
<footer v-if="![DictType.customWord,DictType.word].includes(store.current.dictType)">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex(0,currentDict)"
>
<BaseButton>切换</BaseButton>
</PopConfirm>
</footer>
</div>
<div class="slide-item">
<header>
<div class="dict-name">总词数{{ newData.list.length }}</div>
</header>
<div class="content">
<WordList
class="word-list"
@change="(i:number) => changeIndex(i,store.collect)"
:isActive="settingStore.showPanel && tabIndex === 1"
:list="newData.list"
:activeIndex="newData.index"/>
</div>
<footer v-if="store.current.dictType !== DictType.collect && newData.list.length">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex(0,store.collect)"
>
<BaseButton>切换</BaseButton>
</PopConfirm>
</footer>
</div>
<div class="slide-item">
<header>
<a href="" target="_blank"></a>
<div class="dict-name">总词数{{ wrongData.list.length }}</div>
</header>
<div class="content">
<WordList
class="word-list"
@change="(i:number) => changeIndex(i,store.wrong)"
:isActive="settingStore.showPanel && tabIndex === 2"
:list="wrongData.list"
:activeIndex="wrongData.index"/>
</div>
<footer
v-if="store.current.dictType !== DictType.wrong && wrongData.list.length">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex(0,store.wrong)"
>
<BaseButton>切换</BaseButton>
</PopConfirm>
</footer>
</div>
<div class="slide-item">
<header>
<div class="dict-name">总词数{{ skipData.list.length }}</div>
</header>
<div class="content">
<WordList
class="word-list"
@change="(i:number) => changeIndex(i,store.skip)"
:isActive="settingStore.showPanel && tabIndex === 3"
:list="skipData.list"
:activeIndex="skipData.index"/>
</div>
<footer v-if="store.current.dictType !== DictType.skip && skipData.list.length">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex(0,store.skip)"
>
<BaseButton>切换</BaseButton>
</PopConfirm>
</footer>
</div>
</div>
</div>
</div>
</Transition>
</Teleport>
</template>
<style scoped lang="scss">
@import "@/assets/css/variable.scss";
$width: 20vw;
$header-height: 50rem;
.slide {
width: 100%;
flex: 1;
overflow: hidden;
.slide-list {
width: 400%;
height: 100%;
display: flex;
transition: all .5s;
.slide-item {
width: $width;
height: 100%;
display: flex;
flex-direction: column;
> header {
padding: 0 $space;
height: $header-height;
position: relative;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10rem;
font-size: 18rem;
color: black;
}
.content {
flex: 1;
overflow: auto;
padding-bottom: $space;
}
footer {
padding-right: $space;
height: 50rem;
align-items: center;
}
}
}
.step1 {
transform: translate3d(-25%, 0, 0);
}
.step2 {
transform: translate3d(-50%, 0, 0);
}
.step3 {
transform: translate3d(-75%, 0, 0);
}
}
.panel {
position: fixed;
left: 0;
top: 10rem;
border-radius: 8rem;
margin-left: calc(50% + (var(--toolbar-width) / 2) + $space);
width: $width;
background: var(--color-second-bg);
height: calc(100% - 20rem);
display: flex;
flex-direction: column;
transition: all .3s;
z-index: 1;
& > header {
min-height: 50rem;
box-sizing: border-box;
position: relative;
display: flex;
align-items: center;
padding: 10rem 15rem;
border-bottom: 1px solid #e1e1e1;
gap: 15rem;
.close {
cursor: pointer;
}
.tabs {
justify-content: flex-end;
width: 100%;
display: flex;
align-items: center;
gap: 15rem;
font-size: 14rem;
color: gray;
.tab {
cursor: pointer;
word-break: keep-all;
font-size: 16rem;
&.active {
color: rgb(36, 127, 255);
font-weight: bold;
}
}
.current {
word-break: break-word;
}
}
}
}
</style>