311 lines
8.6 KiB
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')"></div>
|
|
<div class="bar star-blogIconFont a11 allCenter" @click="handleMenuCollapseStatusChange"></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"></div>
|
|
<div class=" bar a11 allCenter sxIconFont" :class="item.open && 'open'"><div></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)"></div>
|
|
<div class="star-blogIconFont bar a11 allCenter"></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>
|