316 lines
9.2 KiB
Vue
316 lines
9.2 KiB
Vue
<script setup lang="ts">
|
|
import rfdc from "rfdc";
|
|
import type {blogMenu} from "~/drizzle/schema";
|
|
import {useAsyncData} from "nuxt/app";
|
|
const clone = rfdc()
|
|
const niMessage = useNiMessage()
|
|
|
|
const {data: obm, error} = await useFetch('/api/blog/blogMenu');
|
|
if (error.value) {
|
|
throw error.value;
|
|
}
|
|
|
|
definePageMeta({
|
|
layout: 'home',
|
|
pageTransition: {
|
|
name: 'slide',
|
|
mode: 'out-in',
|
|
},
|
|
})
|
|
// 菜单数据类型
|
|
type BlogMenu = typeof blogMenu.$inferInsert;
|
|
type ShowBlogMenu = BlogMenu & {
|
|
rank?: number;
|
|
opne?: boolean;
|
|
openNumber?: number;
|
|
childrenLength?: number;
|
|
}
|
|
// 菜单原始数据
|
|
const originalBlogMenuList = shallowRef<ShowBlogMenu[]>(obm as ShowBlogMenu)
|
|
// 根据pid存储列表Map
|
|
const blogMenuPidMap = ref(new Map)
|
|
// 根据id存储对象Map
|
|
const blogMenuIdMap = ref(new Map)
|
|
// blog渲染列表
|
|
const blogMenuList = ref<ShowBlogMenu[]>([]);
|
|
// blog菜单对象
|
|
const blogMenuObject = ref<ShowBlogMenu>(null);
|
|
// 选中的blog菜单对象
|
|
const activeBlogMenuObject = ref<ShowBlogMenu>(null);
|
|
// 菜单折叠
|
|
const menuCollapseStatus = ref(true)
|
|
// blog信息弹窗状态
|
|
const blogInfoPopupStatus = ref(false)
|
|
// 新增的blog菜单Pid
|
|
const insertBlogMenuPid = ref<string>('');
|
|
|
|
// ---- 数据相关
|
|
// 格式化菜单数据
|
|
const formatBlogMenuListToPidMap = (blogMenuList: Array<ShowBlogMenu>) => {
|
|
const idMap = new Map()
|
|
const pidMap = new Map()
|
|
const list = clone(blogMenuList)
|
|
const pidObj: Record<string, ShowBlogMenu[]> = {};
|
|
for(const menuItem of list){
|
|
if(pidObj[menuItem.pid]){
|
|
pidObj[menuItem.pid].push(menuItem)
|
|
}else{
|
|
pidObj[menuItem.pid] = [menuItem]
|
|
}
|
|
if(menuItem.pid == 0){
|
|
menuItem.rank = 0;
|
|
}
|
|
menuItem.openNumber = 0;
|
|
}
|
|
for(const menuItem of list){
|
|
const id = menuItem.id;
|
|
if(pidObj[id]){
|
|
menuItem.childrenLength = pidObj[id].length
|
|
}
|
|
idMap.set(id, menuItem)
|
|
}
|
|
Object.keys(pidObj).forEach(key => {
|
|
pidMap.set(key, pidObj[key]);
|
|
})
|
|
blogMenuPidMap.value = pidMap
|
|
blogMenuIdMap.value = idMap
|
|
}
|
|
// 初始化blog菜单数据
|
|
const initMenuData = () => {
|
|
const blogMenuData = originalBlogMenuList.value
|
|
formatBlogMenuListToPidMap(blogMenuData);
|
|
blogMenuList.value = blogMenuPidMap.value.get('0')
|
|
}
|
|
// 客户端获取菜单数据
|
|
const getMenuListForClient = async () => {
|
|
const resd = await $fetch('/api/blog/blogMenu').catch(e => {
|
|
consola.warn('获取博客目录异常', e)
|
|
niMessage.warning('获取博客目录异常!')
|
|
});
|
|
if(resd){
|
|
originalBlogMenuList.value = resd;
|
|
initMenuData()
|
|
}
|
|
}
|
|
// 修改上级菜单的打开数量
|
|
const changeParentOpenNumber = (item: ShowBlogMenu, openNumber: number, type = '-') => {
|
|
if(item.pid === '0'){
|
|
item.openNumber = openNumber
|
|
return
|
|
}
|
|
let targetMenuItem = item;
|
|
while(true){
|
|
// 获取父元素
|
|
targetMenuItem = blogMenuIdMap.value.get(targetMenuItem.pid);
|
|
if(type == '-'){
|
|
targetMenuItem.openNumber -= openNumber
|
|
}else{
|
|
targetMenuItem.openNumber += openNumber
|
|
}
|
|
if(targetMenuItem.pid === '0'){
|
|
break;
|
|
}
|
|
}
|
|
if(type == '-'){
|
|
item.openNumber = 0
|
|
}else{
|
|
item.openNumber = openNumber
|
|
}
|
|
}
|
|
|
|
// --- 事件
|
|
// 展开/关闭子菜单事件
|
|
const handleCollapseChildrenEvent = (item: ShowBlogMenu, index: number) => {
|
|
if(item.open == true){
|
|
// 闭合
|
|
// 将状态关闭
|
|
blogMenuIdMap.value.get(item.id).open = false;
|
|
// 删除子孙元素
|
|
blogMenuList.value.splice(index + 1, item.openNumber);
|
|
// 降低父辈元素的打开数量
|
|
changeParentOpenNumber(item, item.openNumber, '-');
|
|
//
|
|
}else{
|
|
// 展开
|
|
// 将状态打开
|
|
blogMenuIdMap.value.get(item.id).open = true;
|
|
// 获取子元素
|
|
const childrenList = blogMenuPidMap.value.get(item.id)
|
|
blogMenuList.value.splice(index + 1, 0, ...childrenList.map(i => {
|
|
i.rank = item.rank + 1;
|
|
i.open = false;
|
|
return i
|
|
}));
|
|
changeParentOpenNumber(item, childrenList.length, '+');
|
|
}
|
|
|
|
}
|
|
// 新增blog事件
|
|
const handleCreateBlogMenuItemEvent = (pid: string) => {
|
|
blogMenuObject.value = {
|
|
pid,
|
|
name: '',
|
|
desc: ''
|
|
}
|
|
blogInfoPopupStatus.value = true;
|
|
insertBlogMenuPid.value = pid
|
|
}
|
|
// 确认新增
|
|
const handleCreateBlogMenuItemAck = async () => {
|
|
const resd= await $fetch('/api/blog/blogMenu',{
|
|
method: 'POST',
|
|
body: blogMenuObject.value
|
|
}).catch(e => {
|
|
consola.error(e);
|
|
niMessage.warning('创建博客失败!');
|
|
});
|
|
if(resd){
|
|
niMessage.success('创建博客成功!');
|
|
blogInfoPopupStatus.value = false;
|
|
const father = blogMenuIdMap.value.get(insertBlogMenuPid.value);
|
|
resd.opne = false;
|
|
resd.openNumber = 0;
|
|
resd.childrenLength = 0;
|
|
blogMenuIdMap.value.set(resd.id, resd)
|
|
let pidMap
|
|
if(insertBlogMenuPid.value == '0'){
|
|
resd.rank = 0;
|
|
pidMap = blogMenuPidMap.value.get('0')
|
|
}else{
|
|
resd.rank = father.rank + 1;
|
|
father.childrenLength = father.childrenLength + 1;
|
|
pidMap = blogMenuPidMap.value.get(father.id)
|
|
}
|
|
if(pidMap){
|
|
pidMap.push(resd)
|
|
}else{
|
|
blogMenuPidMap.value.set(father.id, [resd])
|
|
}
|
|
if(father?.open){
|
|
const openNumber = father.openNumber
|
|
changeParentOpenNumber(father, 1, '+');
|
|
const insertIndex = blogMenuList.value.findIndex(i => i.id === father.id)
|
|
father.openNumber = father.openNumber + openNumber
|
|
blogMenuList.value.splice(insertIndex + father.openNumber, 0, resd);
|
|
}else if(insertBlogMenuPid.value == '0'){
|
|
blogMenuList.value = blogMenuPidMap.value.get('0')
|
|
}
|
|
}
|
|
}
|
|
|
|
// SSR运行
|
|
initMenuData()
|
|
onMounted(() => {
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="homeBlog">
|
|
<HomeBlogMenu
|
|
class="left"
|
|
v-model:status="menuCollapseStatus"
|
|
v-model:active="activeBlogMenuObject"
|
|
:blogMenuList
|
|
@createBlogMenu="handleCreateBlogMenuItemEvent"
|
|
@collapseChildren="handleCollapseChildrenEvent"></HomeBlogMenu>
|
|
<div class="main">
|
|
<header>
|
|
<div class="menuCollapse a11 allCenter point" :class="menuCollapseStatus && 'hide'">
|
|
<div class="star-blogIconFont" @click="menuCollapseStatus = !menuCollapseStatus"></div>
|
|
</div>
|
|
</header>
|
|
<main>
|
|
<NuxtPage />
|
|
</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.left{
|
|
position: relative;
|
|
height: 100%;
|
|
flex-shrink: 0;
|
|
background: var(--blog-menu-background-color);
|
|
border-right: 1px solid var(--bg-color-be);
|
|
}
|
|
& > div.right{
|
|
position: relative;
|
|
height: 100%;
|
|
overflow: auto;
|
|
flex-shrink: 0;
|
|
}
|
|
& > div.main{
|
|
position: relative;
|
|
height: 100%;
|
|
overflow: auto;
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
& > header{
|
|
position: relative;
|
|
flex-shrink: 0;
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
& > main{
|
|
position: relative;
|
|
flex: 1;
|
|
overflow: hidden;
|
|
}
|
|
}
|
|
}
|
|
@media (max-width: 767px){
|
|
|
|
}
|
|
@media (min-width: 768px){
|
|
|
|
}
|
|
</style>
|