Optimize UI interface

This commit is contained in:
zyronon
2023-10-30 18:33:05 +08:00
parent 00d7a3ff0b
commit 1e201a429e
16 changed files with 322 additions and 414 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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