Optimize UI interface
This commit is contained in:
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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: [
|
||||
|
||||
Reference in New Issue
Block a user