starwait/pages/home/blog/index.vue
2025-04-27 04:17:35 +08:00

329 lines
10 KiB
Vue

<script setup lang="ts">
import rfdc from "rfdc";
import type {blogMenu} from "~/drizzle/schema";
const clone = rfdc()
const niMessage = useNiMessage()
definePageMeta({
layout: 'home',
pageTransition: {
name: 'slide',
mode: 'out-in',
},
})
// 菜单数据类型
type InsertBlogMenu = typeof blogMenu.$inferInsert;
// 菜单数据
const originalBlogMenuLis = shallowRef([])
const blogMenuPidMap = ref(new Map)
const blogMenuList = ref<InsertBlogMenu[]>([]);
// blog菜单对象
const blogMenuObject = ref<InsertBlogMenu>(null);
// 菜单折叠
const menuCollapseStatus = ref(true)
// blog信息弹窗状态
const blogInfoPopupStatus = ref(false)
// 格式化菜单数据
const formatBlogMenuListToPidMap = (blogMenuList: Array<InsertBlogMenu>) => {
const list = clone(blogMenuList)
const pidObj: Record<string, InsertBlogMenu[]> = {};
for(const menuItem of list){
if(pidObj[menuItem.pid]){
pidObj[menuItem.pid].push(menuItem)
}else{
pidObj[menuItem.pid] = [menuItem]
}
}
Object.keys(pidObj).forEach(key => {
blogMenuPidMap.value.set(key, pidObj[key]);
})
}
// 获取blog菜单数据
const getMenuListFetch = async () => {
const { data, error } = await useFetch('/api/blog/blogMenu');
if (error.value) {
throw error.value;
}
originalBlogMenuLis.value = data.value;
formatBlogMenuListToPidMap(data.value);
blogMenuList.value = blogMenuPidMap.value.get('0')
}
// 新增blog事件
const handleCreateBlogMenuItemEvent = (pid: string) => {
blogMenuObject.value = {
pid,
name: '',
desc: ''
}
blogInfoPopupStatus.value = true;
}
// 确认新增
const handleCreateBlogMenuItemAck = async () => {
consola.info(blogMenuObject.value)
const resd= await $fetch('/api/blog/blogMenu',{
method: 'POST',
body: blogMenuObject.value
});
consola.info(resd);
}
// SSR运行
await getMenuListFetch()
onMounted(() => {
consola.log(blogMenuList.value)
})
</script>
<template>
<div class="homeBlog">
<ResizeContent class="left" :class="!menuCollapseStatus && 'menuCollapse'">
<div class="blogMenuContainer">
<header class="contentBox">
<div class="title">博客目录</div>
<div class="bar add star-blogIconFont awaitShow a11 allCenter" @click="handleCreateBlogMenuItemEvent('0')">&#xe608;</div>
<div class="bar star-blogIconFont awaitShow a11 allCenter" @click="menuCollapseStatus = !menuCollapseStatus">&#xe67f;</div>
</header>
<div class="line"/>
<div class="blogMenuContent">
<div class="contentBox blogMenuItem" v-for="(item, index) in blogMenuList" :key="item.id">
<div class="addIcon star-blogIconFont a11 allCenter">&#xe608;</div>
<div class="text"><div class="oneLineOverMore">{{item.name}}</div></div>
<div class="barBox" :class="item.pid === '0' && 'awaistShows'">
<div class="star-blogIconFont bar a11 allCenter" @click="handleCreateBlogMenuItemEvent(item.pid)">&#xe608;</div>
<div class="star-blogIconFont bar a11 allCenter">&#xe73a;</div>
</div>
</div>
</div>
<footer></footer>
</div>
</ResizeContent>
<div class="main">
<header>
<div class="menuCollapse a11 allCenter point" :class="menuCollapseStatus && 'hide'">
<div class="star-blogIconFont" @click="menuCollapseStatus = !menuCollapseStatus">&#xe67f;</div>
</div>
</header>
<main>
</main>
</div>
<div class="right"></div>
<NiPopup v-model:status="blogInfoPopupStatus">
<template #header>博客菜单</template>
<NiForm :data="blogMenuObject">
<NiFormItem label="博客名称" name="name" label-width="6">
<NiInput v-model="blogMenuObject.name"></NiInput>
</NiFormItem>
<NiFormItem label="博客描述" name="name" label-width="6">
<NiInput v-model="blogMenuObject.desc"></NiInput>
</NiFormItem>
<div>{{ blogMenuObject.name }}</div>
<div>{{ blogMenuObject.desc }}</div>
</NiForm>
<template #footer>
<NiButton @click="blogInfoPopupStatus = false">取消</NiButton>
<NiButton type="primary" @click="handleCreateBlogMenuItemAck">确认</NiButton>
</template>
</NiPopup>
</div>
</template>
<style scoped lang="scss">
.homeBlog {
position: relative;
display: flex;
width: 100%;
height: 100%;
overflow: hidden;
& > div{
position: relative;
height: 100%;
overflow: auto;
}
& > div.left{
flex-shrink: 0;
overflow: hidden;
background: var(--blog-menu-background-color);
border-right: 1px solid var(--bg-color-be);
transition: width .3s ease-in-out;
&.menuCollapse{
width: 0;
}
.bar{
position: relative;
flex-shrink: 0;
height: 100%;
font-size: 1rem;
opacity: 0;
color: var(--font-color-top3-h2);
transform: rotate(180deg);
transition: color .3s, opacity .3s, background-color .3s;
border-radius: .25rem;
&:hover{
background-color: #00000015;
}
}
.awaitShow{
height: 0;
width: 0;
opacity: 0;
transition: opacity .3s;
overflow: hidden;
}
&:hover{
.awaitShow{
height: 100%;
width: auto;
opacity: 1;
}
}
.blogMenuContainer{
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
font-family: sans-serif;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: .5rem;
& > *{
position: relative;
width: 100%;
}
& > header{
flex-shrink: 0;
display: flex;
width: 100%;
& > div.title{
position: relative;
flex: 1;
color: var(--font-color-top3-h1);
}
& > div.bar{
&:last-child{
font-size: 1.3rem;
}
&:hover{
color: var(--font-color-top3-h1);
opacity: 1;
}
}
}
& > footer{
flex-shrink: 0;
}
& > div.line{
position: relative;
width: 100%;
height: 1px;
margin: 0.5rem auto;
background-color: var(--bg-color-be);
}
& > div.blogMenuContent{
position: relative;
flex: 1;
overflow: hidden;
& > div.blogMenuItem{
position: relative;
font-size: 1rem;
display: flex;
& > div.addIcon{
position: relative;
font-size: 1rem;
margin-right: .5rem;
}
& > div.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;
}
}
}
.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);
}
}
}
}
& > div.right{
flex-shrink: 0;
}
& > div.main{
flex: 1;
& > header{
position: relative;
display: flex;
height: 3rem;
border-bottom: 1px solid var(--bg-color-be);
& > div.menuCollapse{
position: relative;
height: 100%;
transition: width .3s;
width: 3rem;
overflow: hidden;
&:hover & > div{
color: var(--font-color-top3-h1);
}
&.hide{
width: 0;
}
& > div{
font-size: 1.3rem;
color: var(--font-color-top3-h2);
transition: color .5s, opacity .5s;
}
}
}
}
}
@media (max-width: 767px){
}
@media (min-width: 768px){
}
</style>