1 使用说明
TjfCollapseLayout 使用说明
概述
TjfCollapseLayout 是一个支持左右面板折叠的三栏布局组件,基于 Vue 3 + TypeScript 开发,采用深色主题设计。该组件提供了灵活的布局方案,适用于需要侧边栏可折叠的后台管理系统或数据可视化界面。
核心特性
- 三栏布局: 左侧面板 + 中间自适应面板 + 右侧面板
- 独立折叠: 左右面板支持独立折叠/展开,互不影响
- 自定义插槽: 支持自定义折叠按钮内容,暴露折叠状态
- 可配置参数: 支持自定义面板宽度、折叠文字、动画时长
- 深色主题: 预设深色背景配色,适配后台管理系统风格
组件结构
┌─────────────────────────────────────────────────────────────────┐
│ collapse-layout-container │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ layout-content │ │
│ │ ┌─────────────┐ ┌───────────────────────────────┐ │ │
│ │ │ left-panel │ │ │ │ │
│ │ │ (可折叠) │ │ center-panel │ │ │
│ │ │ │ │ (自适应宽度) │ │ │
│ │ └─────────────┘ │ │ │ │
│ │ ↕ └───────────────────────────────┘ │ │
│ │ left-btn │ │
│ │ │ │
│ │ ┌───────────────────────────────┐ ┌─────────────┐ │ │
│ │ │ │ │ right-panel │ │ │
│ │ │ center-panel │ │ (可折叠) │ │ │
│ │ │ (自适应宽度) │ │ │ │ │
│ │ └───────────────────────────────┘ └─────────────┘ │ │
│ │ ↕ │ │
│ │ right-btn │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Props 配置
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| leftWidth | string | '280px' |
左侧面板宽度 |
| rightWidth | string | '320px' |
右侧面板宽度 |
| leftText | string | '选择点位' |
左侧面板折叠时显示的文字 |
| rightText | string | '实时告警' |
右侧面板折叠时显示的文字 |
| duration | string | '0.3s' |
折叠/展开动画时长 |
插槽说明
| 插槽名称 | 说明 | 暴露参数 |
|---|---|---|
| left | 左侧面板内容 | - |
| center | 中间面板内容 | - |
| right | 右侧面板内容 | - |
| left-toggle | 左侧折叠按钮自定义内容 |
isCollapsed: 当前折叠状态 |
| right-toggle | 右侧折叠按钮自定义内容 |
isCollapsed: 当前折叠状态 |
基本用法
<template>
<TjfCollapseLayout >
<!-- 左侧面板 -->
<template #left>
<div class="left-content">
<h3>左侧菜单</h3>
<ul>
<li>菜单项1</li>
<li>菜单项2</li>
<li>菜单项3</li>
</ul>
</div>
</template>
<!-- 中间面板 -->
<template #center>
<div class="center-content">
<h2>主内容区域</h2>
<p>这是自适应宽度的中间面板</p>
</div>
</template>
<!-- 右侧面板 -->
<template #right>
<div class="right-content">
<h3>右侧面板</h3>
<p>右侧内容区域</p>
</div>
</template>
</TjfCollapseLayout >
</template>
<script setup lang="ts">
import TjfCollapseLayout from '@/components/Layout/TjfCollapseLayout.vue'
</script>
<style scoped>
.left-content, .right-content {
padding: 16px;
}
.center-content {
height: 100%;
padding: 16px;
}
</style>
自定义折叠按钮
通过插槽可以自定义折叠按钮的外观:
<template>
<TjfCollapseLayout
left-width="250px"
right-width="300px"
duration="0.4s"
>
<!-- 左侧面板 -->
<template #left>
<div class="sidebar">
<!-- 侧边栏内容 -->
</div>
</template>
<!-- 自定义左侧折叠按钮 -->
<template #left-toggle="{ isCollapsed }">
<div class="custom-toggle">
<MenuOutlined v-if="!isCollapsed" />
<ChevronRightOutlined v-else />
</div>
</template>
<!-- 中间面板 -->
<template #center>
<div class="main-content">
<!-- 主内容 -->
</div>
</template>
<!-- 右侧面板 -->
<template #right>
<div class="panel">
<!-- 右侧内容 -->
</div>
</template>
<!-- 自定义右侧折叠按钮 -->
<template #right-toggle="{ isCollapsed }">
<div class="custom-toggle">
<ChevronLeftOutlined v-if="!isCollapsed" />
<BellOutlined v-else />
</div>
</template>
</TjfCollapseLayout >
</template>
<script setup lang="ts">
import TjfCollapseLayout from '@/components/Layout/TjfCollapseLayout.vue'
import { MenuOutlined, ChevronRightOutlined, ChevronLeftOutlined, BellOutlined } from '@ant-design/icons-vue'
</script>
<style scoped>
.custom-toggle {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background-color: #30363d;
border-radius: 4px;
color: #c9d1d9;
font-size: 14px;
}
</style>
在 Dashboard 中使用
结合地图组件和其他面板使用:
<template>
<TjfCollapseLayout
left-width="280px"
right-width="320px"
left-text="监控点位"
right-text="实时告警"
>
<!-- 左侧:设备树 -->
<template #left>
<div class="device-tree">
<div class="tree-header">选择点位</div>
<div class="tree-content">
<!-- 设备树内容 -->
</div>
</div>
</template>
<!-- 中间:地图 -->
<template #center>
<div class="map-container">
<MapProvider :zoom="12" :center="[111.75, 40.84]">
<!-- 地图组件 -->
</MapProvider>
</div>
</template>
<!-- 右侧:告警面板 -->
<template #right>
<div class="alarm-panel">
<div class="panel-header">实时告警记录</div>
<div class="alarm-list">
<!-- 告警列表 -->
</div>
</div>
</template>
</TjfCollapseLayout >
</template>
<script setup lang="ts">
import BglCollapseLayout from '@/components/Layout/TjfCollapseLayout.vue'
import MapProvider from '@/components/MapProvider.vue'
</script>
<style scoped>
.device-tree {
height: 100%;
display: flex;
flex-direction: column;
}
.tree-header {
padding: 10px 12px;
color: #8b949e;
font-size: 13px;
border-bottom: 1px solid #30363d;
}
.tree-content {
flex: 1;
overflow-y: auto;
padding: 8px;
}
.map-container {
height: 100%;
position: relative;
}
.alarm-panel {
height: 100%;
display: flex;
flex-direction: column;
}
.panel-header {
padding: 10px 12px;
color: #c9d1d9;
font-size: 13px;
font-weight: 600;
border-bottom: 1px solid #30363d;
}
.alarm-list {
flex: 1;
overflow-y: auto;
padding: 8px;
}
</style>
样式变量
组件内部定义了以下 CSS 变量,可通过 CSS 变量覆盖:
| 变量名 | 默认值 | 说明 |
|---|---|---|
| --online-color | #3fb950 |
在线状态颜色(折叠按钮展开文字背景) |
| --offline-color | #94A3B8 |
离线状态颜色(折叠按钮图标背景) |
| --alert-color | #EF4444 |
告警颜色 |
| --color-bg-container | #0d1117 |
容器背景色 |
| --color-border | #30363d |
边框颜色 |
注意事项
-
高度自适应: 组件高度默认为
calc(100vh - 112px),适用于顶部有固定导航栏的场景 -
中间面板: 中间面板使用
flex: 1自适应宽度,会自动填充剩余空间 -
动画过渡: 折叠/展开动画使用 CSS transition 实现,可通过
duration属性调整 - 滚动条处理: 组件已处理水平滚动条问题,面板内容超出高度时需自行添加垂直滚动
-
插槽优先级: 自定义插槽(
left-toggle、right-toggle)的优先级高于默认内容
响应式布局建议
配合媒体查询实现响应式布局:
@media (max-width: 768px) {
:deep(.left-panel),
:deep(.right-panel) {
width: 0 !important;
border: none !important;
}
:deep(.left-btn) {
left: 6px !important;
}
:deep(.right-btn) {
right: 6px !important;
}
}
1 组件代码 TjfCollapseLayout.vue
<template>
<div class="collapse-layout-container">
<div class="layout-content">
<!-- 左侧面板 -->
<div class="left-panel" :class="{ collapsed: isLeftCollapsed }">
<slot name="left"></slot>
</div>
<!-- 左侧折叠按钮 - 支持自定义插槽 -->
<div class="expand-div left-btn" @click="toggleLeft">
<!-- 自定义插槽:left-toggle,暴露折叠状态 isCollapsed -->
<slot name="left-toggle" :isCollapsed="isLeftCollapsed">
<!-- 默认视图(你原来的效果) -->
<LeftOutlined class="expand-icon" v-if="!isLeftCollapsed" />
<span v-else>{{ leftText }}</span>
</slot>
</div>
<!-- 中间自适应面板 -->
<div class="center-panel">
<slot name="center"></slot>
</div>
<!-- 右侧面板 -->
<div class="right-panel" :class="{ collapsed: isRightCollapsed }">
<slot name="right"></slot>
</div>
<!-- 右侧折叠按钮 - 支持自定义插槽 -->
<div class="expand-div right-btn" @click="toggleRight">
<!-- 自定义插槽:right-toggle,暴露折叠状态 isCollapsed -->
<slot name="right-toggle" :isCollapsed="isRightCollapsed">
<!-- 默认视图(你原来的效果) -->
<RightOutlined class="expand-icon" v-if="!isRightCollapsed" />
<span v-else>{{ rightText }}</span>
</slot>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue'
// 配置参数
interface Props {
leftWidth?: string; // 左侧宽度
rightWidth?: string; // 右侧宽度
leftText?: string; // 左侧默认收起文字
rightText?: string; // 右侧默认收起文字
duration?: string; // 动画时长
}
const props = withDefaults(defineProps<Props>(), {
leftWidth: '280px',
rightWidth: '320px',
leftText: '选择点位',
rightText: '实时告警',
duration: '0.3s'
})
// 折叠状态
const isLeftCollapsed = ref(false)
const isRightCollapsed = ref(false)
// 切换左侧
const toggleLeft = () => {
isLeftCollapsed.value = !isLeftCollapsed.value
}
// 切换右侧
const toggleRight = () => {
isRightCollapsed.value = !isRightCollapsed.value
}
</script>
<style lang="less" scoped>
.collapse-layout-container {
height: calc(100vh - 112px);
width: 100%;
background-color: #0d1117;
display: flex;
// 全局变量
--online-color: #3fb950;
--offline-color: #94A3B8;
--alert-color: #EF4444;
--color-bg-container: #0d1117;
--color-border: #30363d;
}
.layout-content {
width: 100%;
height: 100%;
display: flex;
position: relative;
overflow-x: hidden;
/* 永久修复水平滚动条 */
}
// ==================== 折叠按钮通用样式 ====================
.expand-div {
position: absolute;
top: 30%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 10;
width: 28px;
height: 28px;
// 默认图标样式
.expand-icon {
background-color: var(--offline-color);
padding: 6px;
font-size: 12px;
color: white;
border-radius: 10%;
display: flex;
align-items: center;
justify-content: center;
}
// 默认文字样式
span {
background-color: var(--online-color);
width: 1.6em;
word-break: break-all;
white-space: normal;
text-align: center;
line-height: 1.5;
color: #fff;
}
// 左侧按钮定位
&.left-btn {
left: v-bind("props.leftWidth");
transition: left v-bind("props.duration") ease;
}
.left-panel.collapsed+&.left-btn {
left: 6px;
width: 40px;
span {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
}
// 右侧按钮定位
&.right-btn {
right: v-bind("props.rightWidth");
transition: right v-bind("props.duration") ease;
}
.right-panel.collapsed+&.right-btn {
right: 6px;
width: 40px;
span {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
}
}
// ==================== 左侧面板 ====================
.left-panel {
width: v-bind("props.leftWidth");
flex-shrink: 0;
background-color: var(--color-bg-container);
display: flex;
flex-direction: column;
border: 1px solid var(--color-border);
transition: all v-bind("props.duration") ease;
overflow: hidden;
}
.left-panel.collapsed {
width: 0;
border: none;
}
// ==================== 中间面板(核心自适应) ====================
.center-panel {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
margin: 0 8px;
padding: 6px;
background-color: var(--color-bg-container);
border-radius: 8px;
border: 1px solid var(--color-border);
transition: all v-bind("props.duration") ease;
min-width: 0;
/* 强制自适应,彻底解决滚动条 */
}
// ==================== 右侧面板 ====================
.right-panel {
width: v-bind("props.rightWidth");
flex-shrink: 0;
background-color: var(--color-bg-container);
display: flex;
flex-direction: column;
border: 1px solid var(--color-border);
transition: all v-bind("props.duration") ease;
overflow: hidden;
}
.right-panel.collapsed {
width: 0;
border: none;
}
</style>