封装左右俩边带有收起和展开的效果的容器布局

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 边框颜色

注意事项

  1. 高度自适应: 组件高度默认为 calc(100vh - 112px),适用于顶部有固定导航栏的场景
  2. 中间面板: 中间面板使用 flex: 1 自适应宽度,会自动填充剩余空间
  3. 动画过渡: 折叠/展开动画使用 CSS transition 实现,可通过 duration 属性调整
  4. 滚动条处理: 组件已处理水平滚动条问题,面板内容超出高度时需自行添加垂直滚动
  5. 插槽优先级: 自定义插槽(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>
最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容