Optimize UI interface
This commit is contained in:
2
components.d.ts
vendored
2
components.d.ts
vendored
@@ -37,6 +37,7 @@ declare module 'vue' {
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSlider: typeof import('element-plus/es')['ElSlider']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
Empty: typeof import('./src/components/Empty.vue')['default']
|
||||
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']
|
||||
@@ -66,6 +67,5 @@ declare module 'vue' {
|
||||
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']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ footer {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.current-practice-dict {
|
||||
.panel-page-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
@@ -143,18 +143,29 @@ footer {
|
||||
box-sizing: border-box;
|
||||
|
||||
header {
|
||||
padding: 8rem $space;
|
||||
min-height: 50rem;
|
||||
padding: 10rem $space;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 14rem;
|
||||
font-size: 16rem;
|
||||
color: black;
|
||||
|
||||
.left {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rem;
|
||||
}
|
||||
|
||||
.title{
|
||||
max-width: 70%;
|
||||
}
|
||||
|
||||
.right{
|
||||
word-break: keep-all;
|
||||
}
|
||||
}
|
||||
|
||||
.scroll {
|
||||
|
||||
1
src/assets/img/缺省页_空白页-通用.svg
Normal file
1
src/assets/img/缺省页_空白页-通用.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1698646880606" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1765" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M573.44 389.12V245.76H358.4c-28.16 0-51.2 23.04-51.2 51.2v460.8c0 28.16 23.04 51.2 51.2 51.2h307.2c28.16 0 51.2-23.04 51.2-51.2V389.12h-143.36z m43.52 101.376L502.784 629.76l-21.504-20.48-21.504 20.992v-41.984l133.12-85.504-147.456 71.68-38.4-35.84v-4.608l209.92-48.128v4.608z" fill="#4A68CC" opacity=".2" p-id="1766"></path><path d="M102.4 896a409.6 51.2 0 1 0 819.2 0 409.6 51.2 0 1 0-819.2 0Z" fill="#4A68CC" opacity=".1" p-id="1767"></path><path d="M96.256 525.312c0 8.704 6.656 15.36 15.36 15.36s15.36-6.656 15.36-15.36-6.656-15.36-15.36-15.36c-8.192 0-15.36 7.168-15.36 15.36zM147.456 822.272c0 8.704 6.656 15.36 15.36 15.36s15.36-6.656 15.36-15.36-6.656-15.36-15.36-15.36c-8.192 0-15.36 7.168-15.36 15.36zM926.72 847.872c-15.36 4.096-18.944 7.168-23.04 23.04-4.096-15.36-7.168-18.944-23.04-23.04 15.36-4.096 18.944-7.68 23.04-23.04 4.096 15.36 7.168 18.944 23.04 23.04zM199.68 467.456c-31.232 8.192-37.888 14.848-46.08 46.08-8.192-31.232-14.848-37.888-46.08-46.08 31.232-8.192 37.888-14.848 46.08-46.08 8.192 31.232 14.848 37.888 46.08 46.08zM821.76 214.528c-15.36 4.096-18.432 7.168-22.528 22.528-4.096-15.36-7.168-18.432-22.528-22.528 15.36-4.096 18.432-7.168 22.528-22.528 3.584 15.36 7.168 18.432 22.528 22.528zM882.688 135.68c-39.936 10.24-48.128 18.944-58.88 58.88-10.24-39.936-18.944-48.128-58.88-58.88 39.936-10.24 48.128-18.944 58.88-58.88 10.24 39.424 18.944 48.128 58.88 58.88zM783.36 337.408c0-1.024 0-2.048-0.512-2.56v-0.512c0-0.512-0.512-1.536-0.512-2.048 0-0.512 0-0.512-0.512-1.024s-0.512-1.024-1.024-1.536l-0.512-0.512c-0.512-0.512-1.024-1.536-1.536-2.048l-143.36-143.36c-0.512-0.512-1.024-1.024-2.048-1.536l-0.512-0.512c-0.512-0.512-1.024-0.512-1.536-1.024-0.512 0-0.512 0-1.024-0.512s-1.536-0.512-2.048-0.512h-0.512c-1.024 0-2.048-0.512-2.56-0.512H358.4c-65.024 0-117.76 52.736-117.76 117.76v460.8c0 65.024 52.736 117.76 117.76 117.76h307.2c65.024 0 117.76-52.736 117.76-117.76V337.408c0 0.512 0 0.512 0 0z m-143.36-105.984L731.136 322.56H640V231.424z m25.6 613.376H358.4c-48.128 0-87.04-38.912-87.04-87.04V296.96c0-48.128 38.912-87.04 87.04-87.04h250.88v128c0 8.704 6.656 15.36 15.36 15.36h128v404.48c0 48.128-38.912 87.04-87.04 87.04z" fill="#4A68CC" opacity=".5" p-id="1768"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -11,22 +11,19 @@ interface IProps {
|
||||
type?: 'primary' | 'link'
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
withDefaults(defineProps<IProps>(), {
|
||||
type: 'primary',
|
||||
size: 'normal',
|
||||
})
|
||||
|
||||
defineEmits(['click'])
|
||||
|
||||
function click() {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Tooltip :disabled="!keyboard" :title="`快捷键: ${keyboard}`">
|
||||
<div class="base-button"
|
||||
@click="(!disabled && !loading) && $emit('click')"
|
||||
@click="e => (!disabled && !loading) && $emit('click',e)"
|
||||
:class="[
|
||||
active && 'active',
|
||||
size,
|
||||
@@ -113,7 +110,8 @@ function click() {
|
||||
&.link {
|
||||
border-radius: 0;
|
||||
border-bottom: 2px solid transparent;
|
||||
&:hover{
|
||||
|
||||
&:hover {
|
||||
border-bottom: 2px solid black;
|
||||
}
|
||||
}
|
||||
|
||||
29
src/components/Empty.vue
Normal file
29
src/components/Empty.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
text?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="empty">
|
||||
<img src="@/assets/img/缺省页_空白页-通用.svg" alt="">
|
||||
<span>{{ text ?? '空荡荡的~'}}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.empty {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
font-size: 12rem;
|
||||
gap: 20rem;
|
||||
|
||||
img {
|
||||
width: 120rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -33,11 +33,18 @@ export default {
|
||||
methods: {
|
||||
showPop(e) {
|
||||
if (this.disabled) return
|
||||
e.stopPropagation()
|
||||
e?.stopPropagation()
|
||||
let rect = e.target.getBoundingClientRect()
|
||||
this.show = true
|
||||
nextTick(() => {
|
||||
this.$refs.tip.style.top = rect.top + 'px'
|
||||
let tip = this.$refs?.tip?.getBoundingClientRect()
|
||||
console.log('rect', rect, tip)
|
||||
if (!tip) return
|
||||
if (rect.top < 150) {
|
||||
this.$refs.tip.style.top = rect.top + rect.height + tip.height + 30 + 'px'
|
||||
} else {
|
||||
this.$refs.tip.style.top = rect.top - 10 + 'px'
|
||||
}
|
||||
this.$refs.tip.style.left = rect.left + rect.width / 2 - 50 + 'px'
|
||||
})
|
||||
},
|
||||
@@ -98,8 +105,8 @@ $bg-color: rgb(226, 226, 226);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 10rem;
|
||||
font-size: 10rem;
|
||||
gap: 12rem;
|
||||
font-size: 12rem;
|
||||
|
||||
div {
|
||||
cursor: pointer;
|
||||
@@ -108,8 +115,8 @@ $bg-color: rgb(226, 226, 226);
|
||||
.main {
|
||||
color: gray;
|
||||
background: $bg-color;
|
||||
padding: 3rem 8rem;
|
||||
border-radius: 2rem;
|
||||
padding: 3rem 10rem;
|
||||
border-radius: 4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import PopConfirm from "@/components/PopConfirm.vue"
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import Close from "@/components/Close.vue";
|
||||
import Empty from "@/components/Empty.vue";
|
||||
import ArticleList from "@/components/Article/ArticleList.vue";
|
||||
|
||||
const store = useBaseStore()
|
||||
const settingStore = useSettingStore()
|
||||
@@ -31,10 +33,13 @@ watch(() => settingStore.showPanel, n => {
|
||||
}
|
||||
})
|
||||
|
||||
let practiceType = $ref(DictType.word)
|
||||
|
||||
function changeIndex(i: number, dict: Dict) {
|
||||
store.changeDict(dict, dict.chapterIndex, i)
|
||||
store.changeDict(dict, dict.chapterIndex, i,practiceType)
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<Transition name="fade">
|
||||
@@ -58,69 +63,84 @@ function changeIndex(i: number, dict: Dict) {
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="slide-item">
|
||||
<header>
|
||||
<div class="dict-name">总词数:{{ store.collect.words.length }}</div>
|
||||
</header>
|
||||
<div class="content">
|
||||
<WordList
|
||||
class="word-list"
|
||||
@change="(i:number) => changeIndex(i,store.collect)"
|
||||
:isActive="settingStore.showPanel && tabIndex === 1"
|
||||
:list="store.collect.words"
|
||||
:activeIndex="-1"/>
|
||||
<div class="panel-page-item">
|
||||
<header>
|
||||
<div class="left">
|
||||
<el-radio-group v-model="practiceType">
|
||||
<el-radio-button border :label="DictType.word">单词</el-radio-button>
|
||||
<el-radio-button border :label="DictType.article">文章</el-radio-button>
|
||||
</el-radio-group>
|
||||
<div class="dict-name" v-if="practiceType === DictType.word">{{ store.collect.words.length }}个单词</div>
|
||||
<div class="dict-name" v-else> {{ store.collect.articles.length }}篇文章</div>
|
||||
</div>
|
||||
<template v-if="store.current.dictType !== DictType.collect &&
|
||||
(
|
||||
( practiceType === DictType.word && store.collect.words.length) ||
|
||||
( practiceType === DictType.article && store.collect.articles.length)
|
||||
)">
|
||||
<PopConfirm
|
||||
:title="`确认切换?`"
|
||||
@confirm="changeIndex(0,store.collect)"
|
||||
>
|
||||
<BaseButton size="small">切换</BaseButton>
|
||||
</PopConfirm>
|
||||
</template>
|
||||
</header>
|
||||
<template v-if="practiceType === DictType.word">
|
||||
<WordList
|
||||
v-if="store.collect.words.length"
|
||||
class="word-list"
|
||||
:list="store.collect.words"/>
|
||||
<Empty v-else/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<ArticleList
|
||||
v-if="store.collect.articles.length"
|
||||
style="padding: 0 20rem;"
|
||||
:select-item="{id: ''} as any"
|
||||
v-model:list="store.collect.articles"/>
|
||||
<Empty v-else/>
|
||||
</template>
|
||||
</div>
|
||||
<footer v-if="store.current.dictType !== DictType.collect && store.collect.words.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">总词数:{{ store.wrong.words.length }}</div>
|
||||
</header>
|
||||
<div class="content">
|
||||
<div class="panel-page-item" v-if="store.wrong.words.length">
|
||||
<header>
|
||||
<div class="dict-name">总词数:{{ store.wrong.words.length }}</div>
|
||||
<template
|
||||
v-if="store.current.dictType !== DictType.wrong && store.wrong.words.length">
|
||||
<PopConfirm
|
||||
:title="`确认切换?`"
|
||||
@confirm="changeIndex(0,store.wrong)"
|
||||
>
|
||||
<BaseButton size="small">切换</BaseButton>
|
||||
</PopConfirm>
|
||||
</template>
|
||||
</header>
|
||||
<WordList
|
||||
class="word-list"
|
||||
@change="(i:number) => changeIndex(i,store.wrong)"
|
||||
:isActive="settingStore.showPanel && tabIndex === 2"
|
||||
:list="store.wrong.words"
|
||||
:activeIndex="-1"/>
|
||||
:list="store.wrong.words"/>
|
||||
</div>
|
||||
<footer
|
||||
v-if="store.current.dictType !== DictType.wrong && store.wrong.words.length">
|
||||
<PopConfirm
|
||||
:title="`确认切换?`"
|
||||
@confirm="changeIndex(0,store.wrong)"
|
||||
>
|
||||
<BaseButton>切换</BaseButton>
|
||||
</PopConfirm>
|
||||
</footer>
|
||||
<Empty v-else/>
|
||||
</div>
|
||||
<div class="slide-item">
|
||||
<header>
|
||||
<div class="dict-name">总词数:{{ store.skip.words.length }}</div>
|
||||
</header>
|
||||
<div class="content">
|
||||
<div class="panel-page-item" v-if="store.skip.words.length">
|
||||
<header>
|
||||
<div class="dict-name">总词数:{{ store.skip.words.length }}</div>
|
||||
<template v-if="store.current.dictType !== DictType.skip && store.skip.words.length">
|
||||
<PopConfirm
|
||||
:title="`确认切换?`"
|
||||
@confirm="changeIndex(0,store.skip)"
|
||||
>
|
||||
<BaseButton size="small">切换</BaseButton>
|
||||
</PopConfirm>
|
||||
</template>
|
||||
</header>
|
||||
<WordList
|
||||
class="word-list"
|
||||
@change="(i:number) => changeIndex(i,store.skip)"
|
||||
:isActive="settingStore.showPanel && tabIndex === 3"
|
||||
:list="store.skip.words"
|
||||
:activeIndex="-1"/>
|
||||
:list="store.skip.words"/>
|
||||
</div>
|
||||
<footer v-if="store.current.dictType !== DictType.skip && store.skip.words.length">
|
||||
<PopConfirm
|
||||
:title="`确认切换?`"
|
||||
@confirm="changeIndex(0,store.skip)"
|
||||
>
|
||||
<BaseButton>切换</BaseButton>
|
||||
</PopConfirm>
|
||||
</footer>
|
||||
<Empty v-else/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -158,7 +178,7 @@ $header-height: 50rem;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 10rem;
|
||||
font-size: 18rem;
|
||||
font-size: 16rem;
|
||||
color: black;
|
||||
}
|
||||
|
||||
@@ -170,7 +190,7 @@ $header-height: 50rem;
|
||||
|
||||
footer {
|
||||
padding-right: $space;
|
||||
height: 50rem;
|
||||
margin-bottom: 10rem;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,16 +16,17 @@ import ArticleList from "@/components/Article/ArticleList.vue";
|
||||
import IconWrapper from "@/components/IconWrapper.vue";
|
||||
import {Icon} from "@iconify/vue";
|
||||
import Tooltip from "@/components/Tooltip.vue";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
|
||||
const store = useBaseStore()
|
||||
const practiceStore = usePracticeStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
|
||||
let tabIndex = $ref(0)
|
||||
let wordData = $ref({
|
||||
words: [],
|
||||
index: -1
|
||||
})
|
||||
let index = $ref(0)
|
||||
let articleData = $ref({
|
||||
article: cloneDeep(DefaultArticle),
|
||||
sectionIndex: 0,
|
||||
@@ -235,20 +236,20 @@ function nextWord(word: ArticleWord) {
|
||||
<div class="panel-wrapper">
|
||||
<Panel
|
||||
v-if="tabIndex === 0">
|
||||
<div class="current-practice-dict">
|
||||
<div class="panel-page-item">
|
||||
<header>
|
||||
<div class="left">
|
||||
<Tooltip title="切换词典">
|
||||
<IconWrapper>
|
||||
<Icon icon="basil:exchange-outline"/>
|
||||
<Icon @click="runtimeStore.showDictModal = true" icon="basil:exchange-outline"/>
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
<div class="title">
|
||||
{{ store.currentDict.name + ` 第${store.currentDict.chapterIndex + 1}章` }}
|
||||
{{ store.dictTitle }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
共{{ store.currentDict.articles.length }}章
|
||||
{{ store.currentDict.articles.length }}篇文章
|
||||
</div>
|
||||
</header>
|
||||
<ArticleList :select-item="articleData.article"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import WordPanel from "@/components/Practice/PracticeWord/WordPanel.vue";
|
||||
import TypingWord from "@/components/Practice/PracticeWord/TypingWord.vue";
|
||||
import {$ref} from "vue/macros";
|
||||
import {cloneDeep} from "lodash-es";
|
||||
|
||||
@@ -12,11 +12,10 @@ import {Icon} from "@iconify/vue";
|
||||
import Tooltip from "@/components/Tooltip.vue";
|
||||
import Options from "@/components/Practice/Options.vue";
|
||||
import Typing from "@/components/Practice/PracticeWord/Typing.vue";
|
||||
import WordPanel from "@/components/Practice/PracticeWord/WordPanel.vue";
|
||||
import ArticleList from "@/components/Article/ArticleList.vue";
|
||||
import Panel from "@/components/Practice/Panel.vue";
|
||||
import IconWrapper from "@/components/IconWrapper.vue";
|
||||
import WordList from "@/components/WordList.vue";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
|
||||
interface IProps {
|
||||
words: Word[],
|
||||
@@ -37,6 +36,7 @@ let data = $ref({
|
||||
|
||||
let typingRef: any = $ref()
|
||||
const store = useBaseStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const practiceStore = usePracticeStore()
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
@@ -200,7 +200,6 @@ useOnKeyboardEventListener(onKeyDown, onKeyUp)
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Typing
|
||||
v-if="false"
|
||||
ref="typingRef"
|
||||
:word="word"
|
||||
@wrong="wordWrong"
|
||||
@@ -213,25 +212,26 @@ useOnKeyboardEventListener(onKeyDown, onKeyUp)
|
||||
/>
|
||||
<div class="word-panel-wrapper">
|
||||
<Panel>
|
||||
<div class="current-practice-dict">
|
||||
<div class="panel-page-item">
|
||||
<header>
|
||||
<div class="left">
|
||||
<Tooltip title="切换词典">
|
||||
<IconWrapper>
|
||||
<Icon icon="basil:exchange-outline"/>
|
||||
<Icon @click="runtimeStore.showDictModal = true" icon="basil:exchange-outline"/>
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
<div class="title">
|
||||
{{ store.currentDict.name + ` 第${store.currentDict.chapterIndex + 1}章` }}
|
||||
{{ store.dictTitle }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
共{{ data.words.length }}词
|
||||
{{ data.words.length }}个单词
|
||||
</div>
|
||||
</header>
|
||||
<WordList
|
||||
class="word-list"
|
||||
:is-active="true"
|
||||
@change="(i:number) => data.index = i"
|
||||
:list="data.words"
|
||||
:activeIndex="data.index"/>
|
||||
</div>
|
||||
|
||||
@@ -1,312 +0,0 @@
|
||||
<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>
|
||||
@@ -179,7 +179,7 @@ const dictIsArticle = $computed(() => {
|
||||
<div class="translate">
|
||||
<span>翻译:</span>
|
||||
<el-radio-group v-model="currentTranslateLanguage">
|
||||
<el-radio border v-for="i in translateLanguageList" :label="i">{{ i }}</el-radio>
|
||||
<el-radio-button border v-for="i in translateLanguageList" :label="i">{{ i }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<DictGroup
|
||||
@@ -416,6 +416,8 @@ $header-height: 60rem;
|
||||
padding-right: $space;
|
||||
|
||||
.translate {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: black;
|
||||
margin-bottom: 30rem;
|
||||
|
||||
|
||||
@@ -17,15 +17,16 @@ import TranslateSetting from "@/components/Toolbar/TranslateSetting.vue";
|
||||
import Add from "@/components/Toolbar/Add.vue";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {usePracticeStore} from "@/stores/practice.ts";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
|
||||
const {toggle} = useTheme()
|
||||
const store = useBaseStore()
|
||||
const settingStore = useSettingStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const practiceStore = usePracticeStore()
|
||||
|
||||
const showFeedbackModal = $ref(false)
|
||||
const showSettingModal = $ref(false)
|
||||
const showDictModal = $ref(false)
|
||||
const headerRef = $ref<HTMLDivElement>(null)
|
||||
|
||||
watch(() => settingStore.showToolbar, n => {
|
||||
@@ -43,7 +44,7 @@ watch(() => settingStore.showToolbar, n => {
|
||||
<template>
|
||||
<header ref="headerRef">
|
||||
<div class="content">
|
||||
<div class="info" @click="showDictModal = true">
|
||||
<div class="info" @click="runtimeStore.showDictModal = true">
|
||||
{{ store.dictTitle }} {{ practiceStore.repeatNumber ? ' 复习错词' : '' }}
|
||||
</div>
|
||||
<div class="options">
|
||||
@@ -112,7 +113,7 @@ watch(() => settingStore.showToolbar, n => {
|
||||
color="#999"/>
|
||||
</Tooltip>
|
||||
</header>
|
||||
<DictModal :model-value="showDictModal" @close="showDictModal = false"/>
|
||||
<DictModal :model-value="runtimeStore.showDictModal" @close="runtimeStore.showDictModal = false"/>
|
||||
<SettingModal v-if="showSettingModal" @close="showSettingModal = false"/>
|
||||
<FeedbackModal v-if="showFeedbackModal" @close="showFeedbackModal = false"/>
|
||||
</template>
|
||||
|
||||
@@ -9,11 +9,14 @@ const emit = defineEmits<{
|
||||
del: [i: number],
|
||||
change: [i: number]
|
||||
}>()
|
||||
const props = defineProps<{
|
||||
const props = withDefaults(defineProps<{
|
||||
list: Word[],
|
||||
activeIndex: number,
|
||||
isActive: boolean
|
||||
}>()
|
||||
activeIndex?: number,
|
||||
isActive?: boolean
|
||||
}>(), {
|
||||
activeIndex: -1,
|
||||
isActive: false
|
||||
})
|
||||
|
||||
const listRef: HTMLElement = $ref(null as any)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {defineStore} from 'pinia'
|
||||
import {DefaultDict, Dict, DictType, DisplayStatistics, SaveDictKey, Sort, Statistics, Word} from "../types.ts"
|
||||
import {DefaultDict, Dict, DictType, DisplayStatistics, SaveDictKey, Word} from "../types.ts"
|
||||
import {chunk, cloneDeep} from "lodash-es";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts"
|
||||
import {v4 as uuidv4} from 'uuid';
|
||||
@@ -13,8 +13,7 @@ export interface State {
|
||||
current: {
|
||||
dictType: DictType,
|
||||
index: number,
|
||||
editIndex: number,
|
||||
repeatNumber: number,
|
||||
practiceType: DictType,//练习类型,目前仅词典为collect时判断是练单词还是文章使用
|
||||
},
|
||||
simpleWords: string[],
|
||||
load: boolean
|
||||
@@ -25,18 +24,150 @@ export const useBaseStore = defineStore('base', {
|
||||
return {
|
||||
collect: {
|
||||
...cloneDeep(DefaultDict),
|
||||
words: [
|
||||
{
|
||||
"name": "cancel",
|
||||
"trans": [],
|
||||
"usphone": "",
|
||||
"ukphone": ""
|
||||
},
|
||||
{
|
||||
"name": "prepare",
|
||||
"trans": [],
|
||||
"usphone": "",
|
||||
"ukphone": ""
|
||||
},
|
||||
{
|
||||
"name": "half",
|
||||
"trans": [],
|
||||
"usphone": "",
|
||||
"ukphone": ""
|
||||
},
|
||||
{
|
||||
"name": "spacious",
|
||||
"trans": [],
|
||||
"usphone": "",
|
||||
"ukphone": ""
|
||||
},
|
||||
{
|
||||
"name": "analyse",
|
||||
"trans": [],
|
||||
"usphone": "",
|
||||
"ukphone": ""
|
||||
},
|
||||
{
|
||||
"name": "costing",
|
||||
"trans": [],
|
||||
"usphone": "",
|
||||
"ukphone": ""
|
||||
},
|
||||
{
|
||||
"name": "nowadays",
|
||||
"trans": [],
|
||||
"usphone": "",
|
||||
"ukphone": ""
|
||||
},
|
||||
],
|
||||
id: 'collect',
|
||||
name: '收藏',
|
||||
type: DictType.collect,
|
||||
},
|
||||
skip: {
|
||||
...cloneDeep(DefaultDict),
|
||||
words: [
|
||||
{
|
||||
"name": "cancel",
|
||||
"trans": [],
|
||||
"usphone": "",
|
||||
"ukphone": ""
|
||||
},
|
||||
{
|
||||
"name": "prepare",
|
||||
"trans": [],
|
||||
"usphone": "",
|
||||
"ukphone": ""
|
||||
},
|
||||
{
|
||||
"name": "half",
|
||||
"trans": [],
|
||||
"usphone": "",
|
||||
"ukphone": ""
|
||||
},
|
||||
{
|
||||
"name": "spacious",
|
||||
"trans": [],
|
||||
"usphone": "",
|
||||
"ukphone": ""
|
||||
},
|
||||
{
|
||||
"name": "analyse",
|
||||
"trans": [],
|
||||
"usphone": "",
|
||||
"ukphone": ""
|
||||
},
|
||||
{
|
||||
"name": "costing",
|
||||
"trans": [],
|
||||
"usphone": "",
|
||||
"ukphone": ""
|
||||
},
|
||||
{
|
||||
"name": "nowadays",
|
||||
"trans": [],
|
||||
"usphone": "",
|
||||
"ukphone": ""
|
||||
},
|
||||
],
|
||||
id: 'skip',
|
||||
name: '简单词',
|
||||
type: DictType.skip,
|
||||
},
|
||||
wrong: {
|
||||
...cloneDeep(DefaultDict),
|
||||
words: [
|
||||
{
|
||||
"name": "cancel",
|
||||
"trans": [],
|
||||
"usphone": "",
|
||||
"ukphone": ""
|
||||
},
|
||||
{
|
||||
"name": "prepare",
|
||||
"trans": [],
|
||||
"usphone": "",
|
||||
"ukphone": ""
|
||||
},
|
||||
{
|
||||
"name": "half",
|
||||
"trans": [],
|
||||
"usphone": "",
|
||||
"ukphone": ""
|
||||
},
|
||||
{
|
||||
"name": "spacious",
|
||||
"trans": [],
|
||||
"usphone": "",
|
||||
"ukphone": ""
|
||||
},
|
||||
{
|
||||
"name": "analyse",
|
||||
"trans": [],
|
||||
"usphone": "",
|
||||
"ukphone": ""
|
||||
},
|
||||
{
|
||||
"name": "costing",
|
||||
"trans": [],
|
||||
"usphone": "",
|
||||
"ukphone": ""
|
||||
},
|
||||
{
|
||||
"name": "nowadays",
|
||||
"trans": [],
|
||||
"usphone": "",
|
||||
"ukphone": ""
|
||||
},
|
||||
],
|
||||
id: 'wrong',
|
||||
name: '错词本',
|
||||
type: DictType.wrong,
|
||||
@@ -67,8 +198,7 @@ export const useBaseStore = defineStore('base', {
|
||||
index: 1,
|
||||
// dictType: DictType.article,
|
||||
// index: 0,
|
||||
editIndex: 0,
|
||||
repeatNumber: 0,
|
||||
practiceType: DictType.word,
|
||||
},
|
||||
simpleWords: [
|
||||
'a', 'an',
|
||||
@@ -89,6 +219,10 @@ export const useBaseStore = defineStore('base', {
|
||||
return state.skip.originWords.map(v => v.name.toLowerCase()).concat(state.simpleWords)
|
||||
},
|
||||
isArticle(state: State): boolean {
|
||||
//如果是收藏时,特殊判断
|
||||
if (state.current.dictType === DictType.collect) {
|
||||
return state.current.practiceType === DictType.article
|
||||
}
|
||||
return [
|
||||
DictType.article,
|
||||
DictType.customArticle
|
||||
@@ -124,8 +258,18 @@ export const useBaseStore = defineStore('base', {
|
||||
},
|
||||
dictTitle(state: State) {
|
||||
let title = this.currentDict.name
|
||||
if ([DictType.word, DictType.customWord].includes(this.current.dictType)) {
|
||||
title += ` 第${this.currentDict.chapterIndex + 1}章`
|
||||
switch (state.current.dictType) {
|
||||
case DictType.collect:
|
||||
if (state.current.dictType === DictType.collect) {
|
||||
title += ` 第${this.currentDict.chapterIndex + 1}章`
|
||||
}
|
||||
break
|
||||
case DictType.word:
|
||||
case DictType.article:
|
||||
case DictType.customWord:
|
||||
case DictType.customArticle:
|
||||
title += ` 第${this.currentDict.chapterIndex + 1}章`
|
||||
break
|
||||
}
|
||||
return title
|
||||
}
|
||||
@@ -193,7 +337,7 @@ export const useBaseStore = defineStore('base', {
|
||||
DictType.article,
|
||||
DictType.customArticle,
|
||||
].includes(this.current.dictType)) {
|
||||
console.log(1,this.currentDict)
|
||||
console.log(1, this.currentDict)
|
||||
if (!this.currentDict.articles.length) {
|
||||
console.log(2)
|
||||
let r = await fetch(`./dicts/${this.currentDict.language}/${this.currentDict.type}/${this.currentDict.translateLanguage}/${this.currentDict.url}`)
|
||||
@@ -215,16 +359,18 @@ export const useBaseStore = defineStore('base', {
|
||||
this.currentDict.statistics.push(statistics)
|
||||
}
|
||||
},
|
||||
async changeDict(dict: Dict, chapterIndex: number = dict.chapterIndex, chapterWordIndex: number = dict.chapterWordNumber) {
|
||||
async changeDict(dict: Dict, chapterIndex: number = dict.chapterIndex, chapterWordIndex: number = dict.chapterWordNumber, practiceType: DictType) {
|
||||
//TODO 保存统计
|
||||
// this.saveStatistics()
|
||||
console.log('changeDict', cloneDeep(dict), chapterIndex, chapterWordIndex)
|
||||
this.current.dictType = dict.type
|
||||
this.current.practiceType = practiceType
|
||||
if ([DictType.collect,
|
||||
DictType.skip,
|
||||
DictType.wrong].includes(dict.type)) {
|
||||
this[dict.type].chapterIndex = chapterIndex
|
||||
this[dict.type].chapterIndex = 0
|
||||
this[dict.type].chapterWordIndex = chapterWordIndex
|
||||
this[dict.type].chapterWords = [this[dict.type].words]
|
||||
} else {
|
||||
let rIndex = this.myDicts.findIndex((v: Dict) => v.name === dict.name)
|
||||
if (rIndex > -1) {
|
||||
|
||||
@@ -6,6 +6,7 @@ export interface RuntimeState {
|
||||
modalList: Array<{ id: string | number, close: Function }>
|
||||
editDict: Dict
|
||||
translateWordList: Word[]
|
||||
showDictModal: boolean
|
||||
}
|
||||
|
||||
export const useRuntimeStore = defineStore('runtime', {
|
||||
@@ -15,6 +16,7 @@ export const useRuntimeStore = defineStore('runtime', {
|
||||
modalList: [],
|
||||
editDict: {...DefaultDict},
|
||||
translateWordList: [],
|
||||
showDictModal: false,
|
||||
}
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user