优化列表,下一步,优化接口操作!

This commit is contained in:
expressgy 2025-04-27 18:42:37 +08:00
parent 3d5b72aea1
commit f4196e3e88
6 changed files with 369 additions and 191 deletions

View File

@ -7,6 +7,9 @@ html, body {
overflow: hidden;
font-size: 14px;
}
:root{
--resizable-content-width: 300px; /* 默认值,会被 JS 覆盖 */
}
a {
/*去除默认下划线*/
text-decoration: none;
@ -54,4 +57,4 @@ a {
/*手指*/
.point{
cursor: pointer;
}
}

View File

@ -1,12 +1,12 @@
@font-face {
font-family: "star-blog"; /* Project id 4905744 */
font-family: "star-blogIconFont"; /* Project id 4905744 */
src: url('iconfont.woff2?t=1745569316996') format('woff2'),
url('iconfont.woff?t=1745569316996') format('woff'),
url('iconfont.ttf?t=1745569316996') format('truetype');
}
.star-blogIconFont {
font-family: "star-blog" !important;
font-family: "star-blogIconFont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;

View File

@ -0,0 +1,272 @@
<script setup lang="ts">
const props = defineProps({
status:{
type: Boolean,
default: false,
required: true
},
blogMenuList:{
type: Array,
default: () => [],
required: true
}
})
//
const menuCollapseStatus = ref(props.status)
watchEffect(() => {
menuCollapseStatus.value = props.status
})
const emit = defineEmits(['createBlogMenu', "update:status", "collapseChildren"])
//
const handleMenuCollapseStatusChange = () => {
emit('update:status', !props.status)
}
// /
const handleCollapseChildrenEvent = (item, index) => {
item.childrenLength && emit('collapseChildren', item, index)
}
// blog
const handleCreateBlogMenuItemEvent = (pid: string) => {
emit('createBlogMenu', pid)
}
</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">
<div class="contentBox blogMenuItem" v-for="(item, index) in blogMenuList" :key="item.id">
<div :style="{width: item.rank * 1.4 + 'rem'}"></div>
<div class="textIcon"
:class="item.childrenLength && 'haveChildren'"
@click="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'">
<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>
<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;
}
.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;
& > div.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);
}
}
}
}
& > 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;
}
& > div.textIcon.haveChildren> div{
&:first-child{
animation: textIconLeave .3s linear forwards;
}
&:last-child{
animation: textIconEntry .3s linear forwards;
}
}
}
}
}
</style>

View File

@ -6,7 +6,7 @@ import { ref, onMounted, onBeforeUnmount } from 'vue'
//
// =====================
const resizableElement = ref(null) //
const width = ref(240) //
const width = ref(0) //
const isDragging = ref(false) //
const startX = ref(0) // X
const startWidth = ref(0) //
@ -45,12 +45,16 @@ const handleDrag = (e) => {
const clampedWidth = Math.max(100, newWidth)
// CSS
resizableElement.value.style.setProperty('--width', `${clampedWidth}px`)
setRootPropertyValue(clampedWidth)
//
width.value = clampedWidth
})
}
function setRootPropertyValue(value){
const root = document.documentElement; //
root.style.setProperty('--resizable-content-width', `${value}px`)
}
const stopDrag = () => {
isDragging.value = false
@ -67,7 +71,10 @@ const stopDrag = () => {
// =====================
onMounted(() => {
// CSS
resizableElement.value.style.setProperty('--width', `${width.value}px`)
// CSS
const root = document.documentElement; //
const resizableContentWidth = getComputedStyle(root).getPropertyValue('--resizable-content-width').trim();
width.value = Number(resizableContentWidth.split('px')[0])
})
onBeforeUnmount(() => {
@ -77,41 +84,41 @@ onBeforeUnmount(() => {
</script>
<template>
<div class="resizable-element" :style="{ '--width': width + 'px' }" ref="resizableElement">
<div class="resizableContent" ref="resizableElement">
<slot/>
<!-- 拖拽手柄支持触摸事件 -->
<div class="dargContainer">
<div class="dargBar" @mousedown="startDrag" @touchstart.passive="startDrag"/>
<div class="dargContainer" @mousedown="startDrag" @touchstart.passive="startDrag">
<div class="dargBar"/>
</div>
</div>
</template>
<style scoped>
.resizable-element {
--width: 300px; /* 默认值,会被 JS 覆盖 */
.resizableContent {
position: relative;
height: 100%;
background: #f8f9fa;
width: var(--width);
width: var(--resizable-content-width);
transition: width 0.15s ease; /* 平滑过渡效果 */
will-change: width; /* 提示浏览器优化重绘 */
}
.dargContainer{
position: absolute;
width: 6px;
width: 10px;
height: calc(100% - 10px);
right: 0;
right: -5px;
top: 0;
bottom: 0;
margin: auto 0;
z-index: 1;
cursor: col-resize;
}
.dargBar {
position: relative;
margin: 0 auto;
width: 2px;
height: 100%;
cursor: col-resize;
border-radius: 2px;
opacity: 0;
background: #00000000;

View File

@ -13,9 +13,13 @@ definePageMeta({
})
//
type InsertBlogMenu = typeof blogMenu.$inferInsert;
//
//
const originalBlogMenuLis = shallowRef([])
// pidMap
const blogMenuPidMap = ref(new Map)
// idMap
const blogMenuIdMap = ref(new Map)
// blog
const blogMenuList = ref<InsertBlogMenu[]>([]);
// blog
const blogMenuObject = ref<InsertBlogMenu>(null);
@ -26,6 +30,8 @@ const blogInfoPopupStatus = ref(false)
//
const formatBlogMenuListToPidMap = (blogMenuList: Array<InsertBlogMenu>) => {
const idMap = new Map()
const pidMap = new Map()
const list = clone(blogMenuList)
const pidObj: Record<string, InsertBlogMenu[]> = {};
for(const menuItem of list){
@ -34,10 +40,22 @@ const formatBlogMenuListToPidMap = (blogMenuList: Array<InsertBlogMenu>) => {
}else{
pidObj[menuItem.pid] = [menuItem]
}
if(menuItem.pid == 0){
menuItem.rank = 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 => {
blogMenuPidMap.value.set(key, pidObj[key]);
pidMap.set(key, pidObj[key]);
})
blogMenuPidMap.value = pidMap
blogMenuIdMap.value = idMap
}
// blog
const getMenuListFetch = async () => {
@ -49,6 +67,20 @@ const getMenuListFetch = async () => {
formatBlogMenuListToPidMap(data.value);
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);
}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;
return i
}));
}
}
// blog
const handleCreateBlogMenuItemEvent = (pid: string) => {
blogMenuObject.value = {
@ -77,27 +109,12 @@ onMounted(() => {
<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>
<HomeBlogMenu
class="left"
v-model:status="menuCollapseStatus"
:blogMenuList
@createBlogMenu="handleCreateBlogMenuItemEvent"
@collapseChildren="handleCollapseChildrenEvent"></HomeBlogMenu>
<div class="main">
<header>
<div class="menuCollapse a11 allCenter point" :class="menuCollapseStatus && 'hide'">
@ -135,161 +152,23 @@ onMounted(() => {
width: 100%;
height: 100%;
overflow: hidden;
& > div{
& > 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;
}
& > 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{
position: relative;
height: 100%;
overflow: auto;
flex: 1;
& > header{
position: relative;

View File

@ -9,9 +9,26 @@ definePageMeta({
</script>
<template>
<div>book</div>
<div class="book">
<div class="left">
<ResizeContent>AA</ResizeContent>
</div>
<div class="right"></div>
</div>
</template>
<style scoped>
</style>
<style scoped lang="scss">
.book{
position: relative;
display: flex;
height: 100%;
&>div.left{
flex-shrink: 1;
}
& > div.right{
flex: 1;
background-color: #00dc82;
}
}
</style>