列表插入操作,目前缺陷缺少排序,思路,折叠而目标菜单,重新排序再展开。下一步,博客实体,富文本?MarkDown?MongoDB!

This commit is contained in:
expressgy 2025-04-28 00:12:56 +08:00
parent f4196e3e88
commit d2d5dfa650
8 changed files with 196 additions and 44 deletions

View File

@ -1,36 +1,55 @@
<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"])
const emit = defineEmits(['createBlogMenu', "update:status", "collapseChildren", "update:active"])
//
const handleMenuCollapseStatusChange = () => {
emit('update:status', !props.status)
}
// /
const handleCollapseChildrenEvent = (item, index) => {
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>
@ -47,21 +66,28 @@ const handleCreateBlogMenuItemEvent = (pid: string) => {
</header>
<div class="line"/>
<div class="blogMenuContent">
<div class="contentBox blogMenuItem" v-for="(item, index) in blogMenuList" :key="item.id">
<div :style="{width: item.rank * 1.4 + 'rem'}"></div>
<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="handleCollapseChildrenEvent(item, index)"
@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>
<div class="text"><div class="oneLineOverMore">{{item.name}}</div></div>
<div class="barBox" :class="item.pid === '0' && 'awaistShows'">
<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>
@ -81,6 +107,18 @@ const handleCreateBlogMenuItemEvent = (pid: string) => {
.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;
}
@ -206,7 +244,7 @@ const handleCreateBlogMenuItemEvent = (pid: string) => {
position: relative;
flex: 1;
overflow: hidden;
& > div.blogMenuItem{
.blogMenuItem{
position: relative;
font-size: 1rem;
display: flex;
@ -232,7 +270,7 @@ const handleCreateBlogMenuItemEvent = (pid: string) => {
}
}
}
& > div.text{
& > .text{
position: relative;
flex: 1;
min-width: 0;//

View File

@ -133,6 +133,7 @@ onMounted(() => {
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.2);
z-index: 999;
}
//
@ -150,6 +151,7 @@ onMounted(() => {
background-color: var(--Ni-content-bg-color);
display: flex;
flex-direction: column;
z-index: 999;
@media (min-width: 768px){
&{

View File

@ -1,9 +1,15 @@
<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: {
@ -12,28 +18,39 @@ definePageMeta({
},
})
//
type InsertBlogMenu = typeof blogMenu.$inferInsert;
type BlogMenu = typeof blogMenu.$inferInsert;
type ShowBlogMenu = BlogMenu & {
rank?: number;
opne?: boolean;
openNumber?: number;
childrenLength?: number;
}
//
const originalBlogMenuLis = shallowRef([])
const originalBlogMenuList = shallowRef<ShowBlogMenu[]>(obm as ShowBlogMenu)
// pidMap
const blogMenuPidMap = ref(new Map)
// idMap
const blogMenuIdMap = ref(new Map)
// blog
const blogMenuList = ref<InsertBlogMenu[]>([]);
const blogMenuList = ref<ShowBlogMenu[]>([]);
// blog
const blogMenuObject = ref<InsertBlogMenu>(null);
const blogMenuObject = ref<ShowBlogMenu>(null);
// blog
const activeBlogMenuObject = ref<ShowBlogMenu>(null);
//
const menuCollapseStatus = ref(true)
// blog
const blogInfoPopupStatus = ref(false)
// blogPid
const insertBlogMenuPid = ref<string>('');
// ----
//
const formatBlogMenuListToPidMap = (blogMenuList: Array<InsertBlogMenu>) => {
const formatBlogMenuListToPidMap = (blogMenuList: Array<ShowBlogMenu>) => {
const idMap = new Map()
const pidMap = new Map()
const list = clone(blogMenuList)
const pidObj: Record<string, InsertBlogMenu[]> = {};
const pidObj: Record<string, ShowBlogMenu[]> = {};
for(const menuItem of list){
if(pidObj[menuItem.pid]){
pidObj[menuItem.pid].push(menuItem)
@ -43,11 +60,13 @@ const formatBlogMenuListToPidMap = (blogMenuList: Array<InsertBlogMenu>) => {
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
consola.info(menuItem.name, menuItem.childrenLength)
}
idMap.set(id, menuItem)
}
@ -57,29 +76,75 @@ const formatBlogMenuListToPidMap = (blogMenuList: Array<InsertBlogMenu>) => {
blogMenuPidMap.value = pidMap
blogMenuIdMap.value = idMap
}
// 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);
// blog
const initMenuData = () => {
const blogMenuData = originalBlogMenuList.value
formatBlogMenuListToPidMap(blogMenuData);
blogMenuList.value = blogMenuPidMap.value.get('0')
}
// /
const handleCollapseChildrenEvent = (item, index) => {
if(item.open == true){
blogMenuIdMap.value.get(item.id).open = false;
blogMenuList.value.splice(index + 1, item.childrenLength);
//
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) => {
@ -89,21 +154,42 @@ const handleCreateBlogMenuItemEvent = (pid: string) => {
desc: ''
}
blogInfoPopupStatus.value = true;
insertBlogMenuPid.value = pid
}
//
const handleCreateBlogMenuItemAck = async () => {
consola.info(blogMenuObject.value)
const resd= await $fetch('/api/blog/blogMenu',{
method: 'POST',
body: blogMenuObject.value
}).catch(e => {
consola.error(e);
niMessage.warning('创建博客失败!');
});
consola.info(resd);
if(resd){
niMessage.success('创建博客成功!');
blogInfoPopupStatus.value = false;
const father = blogMenuIdMap.value.get(insertBlogMenuPid.value);
resd.rank = father.rank + 1;
resd.opne = false;
resd.openNumber = 0;
resd.childrenLength = 0;
father.childrenLength ++;
blogMenuIdMap.value.set(resd.id, resd)
const pidMap = blogMenuPidMap.value.get(father.id)
pidMap.push(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);
}
}
}
// SSR
await getMenuListFetch()
initMenuData()
onMounted(() => {
consola.log(blogMenuList.value)
})
</script>
@ -112,6 +198,7 @@ onMounted(() => {
<HomeBlogMenu
class="left"
v-model:status="menuCollapseStatus"
v-model:active="activeBlogMenuObject"
:blogMenuList
@createBlogMenu="handleCreateBlogMenuItemEvent"
@collapseChildren="handleCollapseChildrenEvent"></HomeBlogMenu>
@ -122,6 +209,7 @@ onMounted(() => {
</div>
</header>
<main>
<NuxtPage />
</main>
</div>
<div class="right"></div>

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
</script>
<template>
<div>
ID {{$route.params.blogId}}
</div>
</template>
<style scoped lang="scss">
</style>

View File

@ -7,6 +7,6 @@ export default defineEventHandler(async event => {
// 初始化DB
const blogMenuDB = new BlogMenuDB(event)
const resd = await blogMenuDB.getBlogMenu(headerAuth)
consola.info(resd)
// consola.info(resd)
return resd
})

View File

@ -11,9 +11,10 @@ export default defineEventHandler(async event => {
const blogMenuDB = new BlogMenuDB(event)
const resd = await blogMenuDB.insertBlogMenu(body, headerAuth)
consola.info(body, resd)
const result = await blogMenuDB.getBlogMenuForBlogId(body.id, headerAuth)
// if(!requAuth.isTrue) {
// // 判断为正常登录
// }
return resd
return result[0]
})

View File

@ -9,7 +9,7 @@ export default defineEventHandler(event => {
...defaultToken,
isTrue: false
})
consola.info(`Token ${t}`);
// consola.info(`Token ${t}`);
if (!t) {
throw createError({

View File

@ -37,4 +37,14 @@ export class BlogMenuDB {
and(eq(blogMenu.createdBy, headerAuth.userId), eq(blogMenu.deleted, 0), headerAuth.isTrue ? undefined : eq(blogMenu.public, 1))
).orderBy(asc(blogMenu.sort), asc(blogMenu.createdAt));
}
// 获取指定目录id
async getBlogMenuForBlogId(blogId: string, headerAuth: HeaderAuth):Promise<InsertBlogMenu> {
const result = await this.db
.select()
.from(blogMenu)
.where(
and(eq(blogMenu.createdBy, headerAuth.userId), eq(blogMenu.deleted, 0), headerAuth.isTrue ? undefined : eq(blogMenu.public, 1), eq(blogMenu.id, blogId))
).limit(1);
return result
}
}