starwait/components/Home/Blog/Menu.vue

311 lines
8.6 KiB
Vue

<script setup lang="ts">
import type {blogMenu} from "~/drizzle/schema";
const props = defineProps({
status:{
type: Boolean,
default: false,
required: true
},
active: {
type: [Object, null],
default: () => {},
required: true
},
blogMenuList:{
type: Array,
default: () => [],
required: true
}
})
// 菜单数据类型
type BlogMenu = typeof blogMenu.$inferInsert;
type ShowBlogMenu = BlogMenu & {
rank?: number;
opne?: boolean;
openNumber?: number;
childrenLength?: number;
}
// 菜单折叠状态
const menuCollapseStatus = ref(props.status)
watchEffect(() => {
menuCollapseStatus.value = props.status
})
const emit = defineEmits(['createBlogMenu', "update:status", "collapseChildren", "update:active"])
// 改变菜单展开状态
const handleMenuCollapseStatusChange = () => {
emit('update:status', !props.status)
}
// 展开/关闭子菜单
const handleCollapseChildrenEvent = (item: ShowBlogMenu, index: number) => {
item.childrenLength && emit('collapseChildren', item, index)
}
// 创建blog菜单
const handleCreateBlogMenuItemEvent = (pid: string) => {
emit('createBlogMenu', pid)
}
// 选中blog菜单
const handleActiveBlogMenu = (blogObject: BlogMenu) => {
emit('update:active', blogObject)
}
</script>
<template>
<div class="homeBlogMenu">
<Transition name="blogmenu">
<ResizeContent v-if="menuCollapseStatus">
<div class="blogMenuContainer">
<header class="contentBox">
<div class="title"><div class="oneLineOverMore">博客目录</div></div>
<div class="headerBarContainer awaitShow">
<div class="bar star-blogIconFont a11 allCenter" @click="handleCreateBlogMenuItemEvent('0')">&#xe608;</div>
<div class="bar star-blogIconFont a11 allCenter" @click="handleMenuCollapseStatusChange">&#xe67f;</div>
</div>
</header>
<div class="line"/>
<div class="blogMenuContent">
<TransitionGroup name="list">
<div v-for="(item, index) in blogMenuList" :key="item.id">
<div class="contentBox blogMenuItem" :class="active?.id == item.id && 'active'">
<div :style="{width: item.rank * 1.4 + 'rem'}" @click.stop></div>
<div class="textIcon"
:class="item.childrenLength && 'haveChildren'"
@click.stop="handleCollapseChildrenEvent(item, index)"
>
<div class=" bar a11 allCenter star-blogIconFont">&#xe608;</div>
<div class=" bar a11 allCenter sxIconFont" :class="item.open && 'open'"><div>&#xe64a;</div></div>
</div>
<nuxt-link class="text" :to="'/home/blog/'+item.id" @click="handleActiveBlogMenu(item)">
<div class="oneLineOverMore">{{item.name}}</div>
</nuxt-link>
<div class="barBox" :class="item.pid === '0' && 'awaistShows'" @click.stop>
<div class="star-blogIconFont bar a11 allCenter" @click="handleCreateBlogMenuItemEvent(item.id)">&#xe608;</div>
<div class="star-blogIconFont bar a11 allCenter">&#xe73a;</div>
</div>
</div>
</div>
</TransitionGroup>
</div>
<footer></footer>
</div>
</ResizeContent>
</Transition>
<div :class="!menuCollapseStatus && 'menuCollapse'">
</div>
</div>
</template>
<style scoped lang="scss">
// 动画
.blogmenu-enter-active, .blogmenu-leave-active {
transition: width .3s ease;
}
.blogmenu-enter-from, .blogmenu-leave-to {
width: 0px;
}
.list-enter-active,
.list-leave-active {
transition: all .5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
background-color: var(--font-color-top3-active);
transform: translateX(30px);
}
.homeBlogMenu{
user-select: none;
}
@keyframes textIconEntry {
0%{
color: #00000000;
height: 100%;
}
50%{
color: #00000000;
height: 100%;
}
100%{
color: var(--font-color-top3-h2);
height: 100%;
}
}
@keyframes textIconLeave {
0%{
color: var(--font-color-top3-h2);
}
50%{
color: #00000000;
}
100%{
color: #00000000;
}
}
.blogMenuContainer{
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
font-family: sans-serif;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: .5rem;
// 行容器
.contentBox{
position: relative;
height: 2rem;
box-sizing: border-box;
padding: .2rem .5rem;
border-radius: .25rem;
color: var(--font-color-top3-h2);
user-select: none;
cursor: pointer;
transition: background-color .3s;
&:hover{
background: var(--font-color-top3-hover);
}
&.active{
color: var(--font-color-top3-h1);
background: var(--font-color-top3-active);
}
}
// 按钮
.bar{
position: relative;
flex-shrink: 0;
height: 100%;
font-size: 1rem;
color: var(--font-color-top3-h2);
transition: color .3s, background-color .3s;
border-radius: .25rem;
&:hover{
background-color: #00000015;
color: var(--font-color-top3-h1);
}
}
// 进入容器才显示
.awaitShow{
height: 0;
width: 0;
opacity: 0;
transition: opacity .3s;
overflow: hidden;
}
&:hover{
.awaitShow{
height: 100%;
width: auto;
opacity: 1;
}
}
}
.blogMenuContainer > header{
position: relative;
width: 100%;
flex-shrink: 0;
display: flex;
& > div.title{
position: relative;
flex: 1;
color: var(--font-color-top3-h1);
min-width: 0;
display: flex;
align-items: center;
}
& > div.headerBarContainer{
position: relative;
flex-shrink: 0;
display: flex;
& > div.bar:last-child{
transform: rotate(180deg);
font-size: 1.3rem;
}
}
}
.blogMenuContainer > div.line{
position: relative;
width: 100%;
height: 1px;
margin: 0.5rem auto;
background-color: var(--bg-color-be);
}
.blogMenuContainer > div.blogMenuContent{
position: relative;
flex: 1;
overflow: hidden;
.blogMenuItem{
position: relative;
font-size: 1rem;
display: flex;
& > div.textIcon{
position: relative;
font-size: 1rem;
margin-right: .5rem;
& > div:last-child{
position: absolute;
top:0;
left: 0;
height: 0;
overflow: hidden;
transform: rotate(0);
& > div{
transition: transform .3s ease-in-out;
}
&.open{
& > div{
transform: rotate(90deg);
}
}
}
}
& > .text{
position: relative;
flex: 1;
min-width: 0;// 关键
display: flex;
align-items: center;
overflow: hidden;
}
& > div.barBox{
margin-left: .2rem;
height: 0;
width: 0;
opacity: 0;
overflow: hidden;
display: flex;
transition: opacity .3s;
& > div.bar{
opacity: 1;
}
}
&:hover{
& > div.barBox{
height: 100% !important;
width: auto !important;
opacity: 1 !important;
}
& > div.textIcon.haveChildren> div{
&:first-child{
animation: textIconLeave .3s linear forwards;
}
&:last-child{
animation: textIconEntry .3s linear forwards;
}
}
}
}
}
</style>