Optimize UI interface

This commit is contained in:
zyronon
2023-10-31 11:24:01 +08:00
parent 10a4d70061
commit 11a1ed2d3c
9 changed files with 184 additions and 117 deletions

View File

@@ -1,18 +1,22 @@
<script setup lang="ts">
import BaseIcon from "@/components/BaseIcon.vue";
import Input from "@/components/Input.vue";
import {$computed, $ref} from "vue/macros";
import {cloneDeep, throttle} from "lodash-es";
import {Article} from "@/types.ts";
import {Article, Word} from "@/types.ts";
import ListItem from "@/components/ListItem.vue";
import {useSettingStore} from "@/stores/setting.ts";
import {watch} from "vue";
interface IProps {
list: Article[]
selectItem: Article,
}
const props = defineProps<IProps>()
const props = withDefaults(defineProps<{
list: Article[],
activeIndex?: number,
isActive?: boolean
}>(), {
activeIndex: -1,
isActive: false
})
const emit = defineEmits<{
selectItem: [val: Article],
delSelectItem: [],
@@ -21,7 +25,6 @@ const emit = defineEmits<{
}>()
let searchKey = $ref('')
let localList = $computed({
get() {
if (searchKey) {
@@ -40,50 +43,68 @@ let localList = $computed({
}
})
let el: HTMLDivElement = $ref()
const settingStore = useSettingStore()
function scrollBottom() {
el.scrollTo({
top: el.scrollHeight,
left: 0,
behavior: "smooth",
});
const listRef: HTMLElement = $ref(null as any)
function scrollViewToCenter(index: number) {
if (index === -1) return
listRef.children[index + 1]?.scrollIntoView({block: 'center', behavior: 'smooth'})
}
defineExpose({scrollBottom})
watch(() => props.activeIndex, (n: any) => {
if (settingStore.showPanel) {
scrollViewToCenter(n)
}
})
watch(() => props.isActive, (n: boolean) => {
setTimeout(() => {
if (n) scrollViewToCenter(props.activeIndex)
}, 300)
})
watch(() => props.list, () => {
listRef.scrollTo(0, 0)
})
</script>
<template>
<div class="list-wrapper"
ref="el"
<div class="list"
ref="listRef"
>
<div class="search">
<Input v-model="searchKey"/>
</div>
<ListItem @click="emit('selectItem',item)"
v-for="(item,index) in localList"
:key="item.id">
<div class="name"> {{ `${index + 1}. ${item.title}` }}</div>
<div class="translate-name"> {{ ` ${item.titleTranslate}` }}</div>
<ListItem
@click="emit('selectItem',item)"
v-for="(item,i) in localList"
:active="activeIndex === i"
:key="item.id">
<div class="name"> {{ `${i + 1}. ${item.title}` }}</div>
<div class="translate"> {{ ` ${item.titleTranslate}` }}</div>
</ListItem>
</div>
</template>
<style scoped lang="scss">
.list-wrapper {
transition: all .3s;
@import "@/assets/css/variable.scss";
.list {
display: flex;
flex-direction: column;
gap: 15rem;
flex: 1;
overflow: overlay;
padding-right: 5rem;
padding: 0 $space;
.search {
margin: 10rem 0;
width: 100%;
}
.list {
.translate {
font-size: 16rem;
}
}
</style>

View File

@@ -17,7 +17,7 @@ defineEmits<{
<template>
<div class="list-item"
:class="active"
:class="{active}"
>
<div class="left">
<slot></slot>
@@ -42,7 +42,6 @@ defineEmits<{
color: black;
font-size: 18rem;
border-radius: 8rem;
margin-bottom: 10rem;
display: flex;
justify-content: space-between;
transition: all .3s;
@@ -74,7 +73,7 @@ defineEmits<{
&.active {
background: var(--color-item-active);
color: white;
color: white !important;
}
}
</style>

View File

@@ -19,17 +19,7 @@ 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;
}
tabIndex = 0
}
})
@@ -42,11 +32,10 @@ function changeIndex(i: number, dict: Dict) {
})
}
</script>
<template>
<Transition name="fade">
<div class="panel" v-if="settingStore.showPanel">
<div class="panel" v-show="settingStore.showPanel">
<header>
<Transition name="fade">
<Close
@@ -63,7 +52,7 @@ function changeIndex(i: number, dict: Dict) {
<div class="slide">
<div class="slide-list" :class="`step${tabIndex}`">
<div class="slide-item">
<slot></slot>
<slot :active="tabIndex === 0 && settingStore.showPanel"></slot>
</div>
<div class="slide-item">
<div class="panel-page-item">

View File

@@ -242,30 +242,31 @@ function changePracticeArticle(val: Article) {
</div>
<div class="panel-wrapper">
<Panel
v-if="tabIndex === 0">
<div class="panel-page-item">
<header>
<div class="left">
<Tooltip title="切换词典">
<IconWrapper>
<Icon @click="runtimeStore.showDictModal = true" icon="basil:exchange-outline"/>
</IconWrapper>
</Tooltip>
<div class="title">
{{ store.dictTitle }}
<Panel v-if="tabIndex === 0">
<template v-slot="{active}">
<div class="panel-page-item">
<header>
<div class="left">
<Tooltip title="切换词典">
<IconWrapper>
<Icon @click="runtimeStore.showDictModal = true" icon="basil:exchange-outline"/>
</IconWrapper>
</Tooltip>
<div class="title">
{{ store.dictTitle }}
</div>
</div>
</div>
<div class="right">
{{ store.currentDict.articles.length }}篇文章
</div>
</header>
<ArticleList
style="padding: 0 20rem;"
@select-item="changePracticeArticle"
:select-item="articleData.article"
v-model:list="store.currentDict.articles"/>
</div>
<div class="right">
{{ store.currentDict.articles.length }}篇文章
</div>
</header>
<ArticleList
:isActive="active"
@select-item="changePracticeArticle"
:active-index="store.currentDict.chapterIndex"
v-model:list="store.currentDict.articles"/>
</div>
</template>
</Panel>
</div>

View File

@@ -187,6 +187,7 @@ defineExpose({del, showWord, hideWord})
flex-direction: column;
align-items: center;
justify-content: center;
word-break: break-word;
.phonetic, .translate {
font-size: 20rem;

View File

@@ -217,29 +217,31 @@ useOnKeyboardEventListener(onKeyDown, onKeyUp)
<Teleport to="body">
<div class="word-panel-wrapper">
<Panel>
<div class="panel-page-item">
<header>
<div class="left">
<Tooltip title="切换词典">
<IconWrapper>
<Icon @click="runtimeStore.showDictModal = true" icon="basil:exchange-outline"/>
</IconWrapper>
</Tooltip>
<div class="title">
{{ store.dictTitle }}
<template v-slot="{active}">
<div class="panel-page-item">
<header>
<div class="left">
<Tooltip title="切换词典">
<IconWrapper>
<Icon @click="runtimeStore.showDictModal = true" icon="basil:exchange-outline"/>
</IconWrapper>
</Tooltip>
<div class="title">
{{ store.dictTitle }}
</div>
</div>
</div>
<div class="right">
{{ 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>
<div class="right">
{{ data.words.length }}个单词
</div>
</header>
<WordList
class="word-list"
:is-active="active"
@change="(i:number) => data.index = i"
:list="data.words"
:activeIndex="data.index"/>
</div>
</template>
</Panel>
</div>
</Teleport>

View File

@@ -19,9 +19,9 @@ const emit = defineEmits<{
<template>
<ListItem
class="item"
:class="{active}"
:show-volume="true"
@play="playWordAudio(word.name)"
:active="active">
@play="playWordAudio(word.name)">
<div class="title">
<span class="word">{{ word.name }}</span>
<span class="phonetic">{{ word.usphone }}</span>
@@ -46,6 +46,12 @@ const emit = defineEmits<{
}
}
.active {
.phonetic {
color: white !important;
}
}
.title {
display: flex;
align-items: center;

View File

@@ -3,12 +3,11 @@ import {Word} from "../types";
import {watch} from "vue"
import {useSettingStore} from "@/stores/setting.ts";
import WordItem from "@/components/WordItem.vue";
import ListItem from "@/components/ListItem.vue";
import VolumeIcon from "@/components/VolumeIcon.vue";
import {usePlayWordAudio} from "@/hooks/sound.ts";
const settingStore = useSettingStore()
const emit = defineEmits<{
del: [i: number],
change: [i: number]
}>()
const props = withDefaults(defineProps<{
list: Word[],
activeIndex?: number,
@@ -18,6 +17,13 @@ const props = withDefaults(defineProps<{
isActive: false
})
const emit = defineEmits<{
del: [i: number],
change: [i: number]
}>()
const settingStore = useSettingStore()
const listRef: HTMLElement = $ref(null as any)
function scrollViewToCenter(index: number) {
@@ -41,19 +47,26 @@ watch(() => props.list, () => {
listRef.scrollTo(0, 0)
})
const playWordAudio = usePlayWordAudio()
</script>
<template>
<div class="list" ref="listRef">
<TransitionGroup name="list">
<template v-for="(item,i) in list" :key="i">
<WordItem
@click="emit('change',i)"
@del="emit('del',i)"
:active="activeIndex === i"
:word="item"/>
</template>
</TransitionGroup>
<ListItem
v-for="(word,i) in list" :key="i"
:active="activeIndex === i"
class="item"
:class="{active:activeIndex === i}"
:show-volume="true"
@play="playWordAudio(word.name)">
<div class="title">
<span class="word">{{ word.name }}</span>
<span class="phonetic">{{ word.usphone }}</span>
<VolumeIcon class="volume" @click="playWordAudio(word.name)"></VolumeIcon>
</div>
<div class="translate" v-if="word.trans.length">{{ word.trans.join('') }}</div>
</ListItem>
</div>
</template>
@@ -64,12 +77,47 @@ watch(() => props.list, () => {
.list {
display: flex;
flex-direction: column;
gap: 12rem;
width: 100%;
height: 100%;
box-sizing: border-box;
gap: 15rem;
flex: 1;
overflow: overlay;
padding: 0 $space;
overflow: auto;
.item {
.volume {
opacity: 0;
}
&:hover {
.volume {
opacity: 1;
}
}
&.active {
.phonetic {
color: white !important;
}
}
.title {
display: flex;
align-items: center;
gap: 8rem;
.word {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
display: flex;
}
.phonetic {
font-size: 14rem;
color: gray;
}
}
.translate {
font-size: 16rem;
}
}
}
</style>

View File

@@ -194,10 +194,10 @@ export const useBaseStore = defineStore('base', {
}
],
current: {
dictType: DictType.word,
index: 1,
// dictType: DictType.article,
// index: 0,
// dictType: DictType.word,
// index: 1,
dictType: DictType.article,
index: 0,
practiceType: DictType.word,
},
simpleWords: [