跳转至

布局系统

Django Admin Dashboards 中的布局系统提供了一种灵活的、基于响应式网格的方法来在您的数据看板上排列组件。本指南涵盖从基础布局到高级响应式设计的所有内容。

目录

布局基础

什么是布局?

布局定义了组件在数据看板上的排列方式。它包含:

  1. 网格系统: 12-列 响应式 网格
  2. : 组件的水平容器
  3. : 行内的垂直划分
  4. 组件: 放置在网格单元格中的可视化元素

创建基础布局

    from django_admin_dashboards.base import Dashboard, Layout, CardComponent

    class BasicDashboard(Dashboard):
        def get_layout(self):
            # 创建 12 列布局

            layout = Layout(columns=12)

            # 添加一行,包含一个全宽度组件

            layout.add_row([
                (CardComponent(title="Welcome", value="Dashboard"), 12)
            ], height="auto")

            return layout

网格系统

12-列 网格

布局系统使用 12 列网格,可自动适应不同的屏幕尺寸:

    layout = Layout(columns=12)  # Always 12 columns

    # 组件可以跨越1-12列

    # 每行的列跨度总和应等于12

    # 有效:4 + 4 + 4 = 12

    layout.add_row([(c1, 4), (c2, 4), (c3, 4)], height="auto")

    # 有效:8 + 4 = 12

    layout.add_row([(c1, 8), (c2, 4)], height="auto")

    # 有效:6 + 3 + 3 = 12

    layout.add_row([(c1, 6), (c2, 3), (c3, 3)], height="auto")

    # 无效:6 + 6 + 6 = 18(会导致布局问题)

列跨度示例

    def get_layout(self):
        layout = Layout(columns=12)

        # 单列(很少使用)

        layout.add_row([(component, 1)], height="auto")

        # 两列(6 + 6)

        layout.add_row([(left, 6), (right, 6)], height="auto")

        # 三列(4 + 4 + 4)

        layout.add_row([(col1, 4), (col2, 4), (col3, 4)], height="auto")

        # 四列(3 + 3 + 3 + 3)

        layout.add_row([(c1, 3), (c2, 3), (c3, 3), (c4, 3)], height="auto")

        # 六列(2 + 2 + 2 + 2 + 2 + 2)

        layout.add_row([(c1, 2), (c2, 2), (c3, 2), (c4, 2), (c5, 2), (c6, 2)], height="auto")

        # 混合布局(8 + 4)

        layout.add_row([
            (main_content, 8),   # Main content area

            (sidebar, 4),        # Sidebar

        ], height="auto")

        # 混合布局(9 + 3)

        layout.add_row([
            (content, 9),        # Content

            (widgets, 3),        # Widget sidebar

        ], height="auto")

        return layout

网格可视化

    12列网格分解

    [ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ][ 7 ][ 8 ][ 9 ][10][11][12]

    常见布局

    全宽度           [                   12                   ]
    两列          [        6        ][        6        ]
    三列        [    4    ][    4    ][    4    ]
    四列         [ 3 ][ 3 ][ 3 ][ 3 ]
    侧边栏布局       [        8        ][    4    ]
    主内容 + 侧边栏       [          9          ][  3  ]

行管理

创建行

行使用 add_row() 方法创建:

    def get_layout(self):
        layout = Layout(columns=12)

        # 第1行:标题

        layout.add_row([
            (self.get_header_component(), 12)
        ], height="auto")

        # 第2行:主要指标

        layout.add_row([
            (self.get_metric_card("Revenue"), 4),
            (self.get_metric_card("Users"), 4),
            (self.get_metric_card("Orders"), 4),
        ], height="auto")

        # 第3行:图表

        layout.add_row([
            (self.get_sales_chart(), 8),
            (self.get_conversion_chart(), 4),
        ], height="400px")

        # 第4行:表格

        layout.add_row([
            (self.get_recent_orders(), 6),
            (self.get_top_products(), 6),
        ], height="auto")

        return layout

行高度选项

    # 自动高度(内容决定高度)

    layout.add_row([...], height="auto")

    # 固定高度(像素)

    layout.add_row([...], height="300px")
    layout.add_row([...], height="500px")

    # 视口高度(响应式)

    layout.add_row([...], height="50vh")   # 视口高度的50%

    layout.add_row([...], height="75vh")   # 视口高度的75%

    # 最小高度

    layout.add_row([...], height="min-height: 200px")

    # 最大高度

    layout.add_row([...], height="max-height: 600px")

    # Clamp函数(带限制的响应式)

    layout.add_row([...], height="clamp(300px, 50vh, 800px)")

    # Calc函数(计算高度)

    layout.add_row([...], height="calc(100vh - 200px)")

动态行高度

    def get_dynamic_row_height(self, component_count):
        """基于组件数量计算行高度"""
        if component_count >= 4:
            return "250px"  # 多个组件时较小

        elif component_count == 3:
            return "300px"  # 中等高度

        else:
            return "400px"  # 较少组件时较大

    def get_layout(self):
        layout = Layout(columns=12)

        charts = self.get_charts()
        chart_count = len(charts)

        # 根据数量将图表分布到行中

        if chart_count == 1:
            layout.add_row([(charts[0], 12)], height=self.get_dynamic_row_height(1))
        elif chart_count == 2:
            layout.add_row([
                (charts[0], 6),
                (charts[1], 6),
            ], height=self.get_dynamic_row_height(2))
        elif chart_count == 3:
            layout.add_row([
                (charts[0], 4),
                (charts[1], 4),
                (charts[2], 4),
            ], height=self.get_dynamic_row_height(3))

        return layout

行间距和边距

    def get_layout_with_spacing(self):
        layout = Layout(columns=12)

        # 添加自定义间距的CSS类

        layout.add_row([
            (self.get_header(), 12)
        ], height="auto", css_class="header-row")

        layout.add_row([
            (self.get_metrics(), 12)
        ], height="auto", css_class="metrics-row")

        return layout

自定义CSS间距:

    /* 自定义行间距 */
    .header-row {
        margin-bottom: 2rem;
    }

    .metrics-row {
        margin-bottom: 1.5rem;
        gap: 1rem;  /* 行内组件之间的间隙 */
    }

    /* 移除特定行的默认边距 */
    .no-margin-row {
        margin: 0;
        padding: 0;
    }

响应式设计

内置响应式

网格系统默认是响应式的。组件会根据屏幕尺寸自动调整布局:

    # 这一行将显示为:

    # - 大屏幕上并排显示3列

    # - 中等屏幕上2列 + 1列

    # - 小屏幕上每行1列

    layout.add_row([
        (card1, 4),
        (card2, 4),
        (card3, 4),
    ], height="auto")

基于断点的布局

    def get_responsive_layout(self):
        layout = Layout(columns=12)

        # Desktop: 4 columns

        # Tablet: 2 columns

        # Mobile: 1 column

        layout.add_row([
            (self.get_card("Card 1"), {"default": 4, "md": 6, "sm": 12}),
            (self.get_card("Card 2"), {"default": 4, "md": 6, "sm": 12}),
            (self.get_card("Card 3"), {"default": 4, "md": 12, "sm": 12}),
        ], height="auto")

        return layout

基于屏幕尺寸的条件布局

    def get_adaptive_layout(self, request):
        layout = Layout(columns=12)

        # 检查移动设备的用户代理

        user_agent = request.META.get('HTTP_USER_AGENT', '').lower()
        is_mobile = any(mobile in user_agent for mobile in ['mobile', 'android', 'iphone'])

        if is_mobile:
            # 移动布局:单列

            layout.add_row([(self.get_mobile_header(), 12)], height="auto")
            layout.add_row([(self.get_mobile_metrics(), 12)], height="auto")
            layout.add_row([(self.get_mobile_chart(), 12)], height="300px")
        else:
            # 桌面布局:多列

            layout.add_row([
                (self.get_desktop_header(), 12)
            ], height="auto")
            layout.add_row([
                (self.get_metric_card("Revenue"), 4),
                (self.get_metric_card("Users"), 4),
                (self.get_metric_card("Orders"), 4),
            ], height="auto")
            layout.add_row([
                (self.get_main_chart(), 8),
                (self.get_sidebar_charts(), 4),
            ], height="400px")

        return layout

响应式高度调整

    def get_responsive_height_layout(self):
        layout = Layout(columns=12)

        # 不同屏幕尺寸的不同高度

        layout.add_row([
            (self.get_main_chart(), 12)
        ], height={
            "default": "500px",   # 桌面

            "md": "400px",        # 平板

            "sm": "300px",        # 移动

        })

        # 移动设备上自动高度,桌面设备上固定高度

        layout.add_row([
            (self.get_data_table(), 12)
        ], height={
            "default": "400px",
            "sm": "auto",  # 小屏幕上自动高度

        })

        return layout

高级布局模式

嵌套布局

    def get_nested_layout(self):
        """创建具有嵌套行的复杂布局"""
        layout = Layout(columns=12)

        # 主标题

        layout.add_row([(self.get_header(), 12)], height="auto")

        # 具有嵌套布局的主要内容区域

        main_content = Layout(columns=12)

        # 嵌套:顶部区域

        main_content.add_row([
            (self.get_stats_summary(), 8),
            (self.get_quick_actions(), 4),
        ], height="auto")

        # 嵌套:中间区域

        main_content.add_row([
            (self.get_primary_chart(), 12)
        ], height="400px")

        # 嵌套:底部区域

        main_content.add_row([
            (self.get_data_table(), 12)
        ], height="auto")

        # 将嵌套布局添加为组件

        layout.add_row([(main_content, 12)], height="auto")

        return layout

选项卡布局

    def get_tabbed_layout(self):
        """带有选项卡界面的布局"""
        layout = Layout(columns=12)

        # 选项卡导航

        layout.add_row([
            (self.get_tab_navigation(), 12)
        ], height="auto")

        # 选项卡内容(基于活动选项卡的条件显示)

        active_tab = self.request.GET.get('tab', 'overview')

        if active_tab == 'overview':
            layout.add_row([
                (self.get_overview_content(), 12)
            ], height="auto")
        elif active_tab == 'analytics':
            layout.add_row([
                (self.get_analytics_content(), 12)
            ], height="auto")
        elif active_tab == 'reports':
            layout.add_row([
                (self.get_reports_content(), 12)
            ], height="auto")

        return layout

手风琴布局

    def get_accordion_layout(self):
        """带有可折叠区域的布局"""
        layout = Layout(columns=12)

        # 区域1(默认展开)

        layout.add_row([
            (self.get_accordion_header("Section 1", expanded=True), 12)
        ], height="auto")

        layout.add_row([
            (self.get_section1_content(), 12)
        ], height="auto", css_class="accordion-content", id="section1")

        # 区域2(默认折叠)

        layout.add_row([
            (self.get_accordion_header("Section 2", expanded=False), 12)
        ], height="auto")

        layout.add_row([
            (self.get_section2_content(), 12)
        ], height="auto", css_class="accordion-content hidden", id="section2")

        # 区域3

        layout.add_row([
            (self.get_accordion_header("Section 3", expanded=False), 12)
        ], height="auto")

        layout.add_row([
            (self.get_section3_content(), 12)
        ], height="auto", css_class="accordion-content hidden", id="section3")

        return layout

瀑布流布局

    def get_masonry_layout(self):
        """具有不同组件高度的瀑布流式布局"""
        layout = Layout(columns=12)

        # 创建瀑布流网格

        # 组件将流入可用空间

        masonry_grid = []

        # 添加不同高度的组件

        masonry_grid.append((self.get_tall_card(), 6))      # 2列,高

        masonry_grid.append((self.get_short_card(), 3))     # 1列,矮

        masonry_grid.append((self.get_medium_card(), 3))    # 1列,中等

        masonry_grid.append((self.get_short_card(), 3))     # 1列,矮

        masonry_grid.append((self.get_tall_card(), 6))      # 2列,高

        masonry_grid.append((self.get_medium_card(), 3))    # 1列,中等

        # 将所有组件添加在一行中

        # CSS将处理瀑布流布局

        layout.add_row(masonry_grid, height="auto", css_class="masonry-grid")

        return layout

带侧边栏的数据看板

    def get_sidebar_layout(self):
        """经典侧边栏布局"""
        layout = Layout(columns=12)

        # 侧边栏(固定宽度)

        sidebar_components = [
            self.get_user_profile(),
            self.get_quick_stats(),
            self.get_navigation(),
            self.get_filters(),
        ]

        # 主要内容(流动宽度)

        main_components = [
            self.get_header(),
            self.get_cards(),
            self.get_main_chart(),
            self.get_data_table(),
        ]

        # 创建两列布局

        layout.add_row([
            # 侧边栏列

            (self.create_vertical_stack(sidebar_components), 3),

            # 主要内容列

            (self.create_vertical_stack(main_components), 9),
        ], height="auto")

        return layout

    def create_vertical_stack(self, components):
        """垂直堆叠组件的辅助函数"""
        stack = Layout(columns=12)
        for component in components:
            stack.add_row([(component, 12)], height="auto")
        return stack

布局性能

优化布局性能

    def get_optimized_layout(self):
        """具有性能优化的布局"""
        layout = Layout(columns=12)

        # 1. 懒加载重型组件

        layout.add_row([
            (self.get_lazy_loaded_chart(), 12)
        ], height="400px")

        # 2. 缓存静态布局

        cache_key = f"layout_{self.__class__.__name__}"
        cached_layout = cache.get(cache_key)

        if cached_layout:
            return cached_layout

        # 3. 高效构建布局

        # 从快速渲染的组件开始

        layout.add_row([
            (self.get_quick_cards(), 12)
        ], height="auto")

        # 然后添加较重的组件

        layout.add_row([
            (self.get_complex_chart(), 12)
        ], height="500px")

        # 缓存布局

        cache.set(cache_key, layout, timeout=300)  # 5分钟

        return layout

渐进增强

    def get_progressive_layout(self):
        """渐进加载的布局"""
        layout = Layout(columns=12)

        # 阶段1:立即加载的内容(快速)

        layout.add_row([
            (self.get_loading_indicator(), 12)
        ], height="auto")

        layout.add_row([
            (self.get_basic_cards(), 12)
        ], height="auto")

        # 阶段2:延迟内容(初始渲染后加载)

        deferred_chart = self.get_main_chart()
        deferred_chart.deferred = True
        deferred_chart.load_timeout = 1000  # 1秒后加载

        layout.add_row([
            (deferred_chart, 12)
        ], height="400px")

        # 阶段3:懒加载内容(可见时加载)

        lazy_table = self.get_data_table()
        lazy_table.lazy = True

        layout.add_row([
            (lazy_table, 12)
        ], height="auto")

        return layout

组件优先级

    def get_prioritized_layout(self):
        """具有组件加载优先级的布局"""
        layout = Layout(columns=12)

        # 优先级1:首屏内容(立即加载)

        layout.add_row([
            (self.get_critical_metrics(), 12)
        ], height="auto", priority=1)

        # 优先级2:重要但不关键

        layout.add_row([
            (self.get_main_chart(), 12)
        ], height="400px", priority=2)

        # 优先级3:首屏以下内容(可以懒加载)

        layout.add_row([
            (self.get_detailed_table(), 12)
        ], height="auto", priority=3)

        # 优先级4:可选内容

        layout.add_row([
            (self.get_secondary_charts(), 12)
        ], height="300px", priority=4)

        return layout

常见布局示例

高管数据看板

    def get_executive_dashboard(self):
        """简洁的高层高管数据看板"""
        layout = Layout(columns=12)

        # 1. 关键指标(顶部行)

        layout.add_row([
            (self.get_metric_card("Revenue", "primary"), 3),
            (self.get_metric_card("Profit", "success"), 3),
            (self.get_metric_card("Growth", "warning"), 3),
            (self.get_metric_card("ROI", "info"), 3),
        ], height="auto")

        # 2. 主要性能图表

        layout.add_row([
            (self.get_performance_chart(), 12)
        ], height="500px")

        # 3. 部门指标

        layout.add_row([
            (self.get_department_card("Sales"), 4),
            (self.get_department_card("Marketing"), 4),
            (self.get_department_card("Operations"), 4),
        ], height="auto")

        # 4. 快速洞察

        layout.add_row([
            (self.get_insights_panel(), 12)
        ], height="auto")

        return layout

数据看板

    def get_operational_dashboard(self):
        """详细的运营数据看板"""
        layout = Layout(columns=12)

        # 1. 实时指标

        layout.add_row([
            (self.get_realtime_card("Active Users"), 3),
            (self.get_realtime_card("Orders/Hour"), 3),
            (self.get_realtime_card("Error Rate"), 3),
            (self.get_realtime_card("Response Time"), 3),
        ], height="auto")

        # 2. 系统状态

        layout.add_row([
            (self.get_system_status(), 12)
        ], height="auto")

        # 3. 监控图表

        layout.add_row([
            (self.get_traffic_chart(), 6),
            (self.get_error_chart(), 6),
        ], height="300px")

        # 4. 最近事件

        layout.add_row([
            (self.get_event_log(), 8),
            (self.get_alerts_panel(), 4),
        ], height="400px")

        # 5. 性能指标

        layout.add_row([
            (self.get_performance_table(), 12)
        ], height="auto")

        return layout

数据看板

    def get_analytics_dashboard(self):
        """数据数据看板"""
        layout = Layout(columns=12)

        # 1. 过滤器

        layout.add_row([
            (self.get_analytics_filters(), 12)
        ], height="auto")

        # 2. 摘要卡片

        layout.add_row([
            (self.get_summary_card("Sessions"), 3),
            (self.get_summary_card("Users"), 3),
            (self.get_summary_card("Pageviews"), 3),
            (self.get_summary_card("Bounce Rate"), 3),
        ], height="auto")

        # 3. 趋势分析

        layout.add_row([
            (self.get_trend_chart(), 12)
        ], height="400px")

        # 4. 细分

        layout.add_row([
            (self.get_segmentation_chart(), 6),
            (self.get_demographics_chart(), 6),
        ], height="300px")

        # 5. 转化漏斗

        layout.add_row([
            (self.get_conversion_funnel(), 12)
        ], height="300px")

        # 6. 详细报告

        layout.add_row([
            (self.get_top_pages(), 6),
            (self.get_traffic_sources(), 6),
        ], height="auto")

        return layout

移动优化数据看板

    def get_mobile_dashboard(self):
        """为移动设备优化的数据看板"""
        layout = Layout(columns=12)

        # 移动设备的单列布局

        layout.add_row([(self.get_mobile_header(), 12)], height="auto")

        # 堆叠卡片(每行一个)

        for card in self.get_mobile_cards():
            layout.add_row([(card, 12)], height="auto")

        # 单一图表(为移动设备简化)

        layout.add_row([(self.get_mobile_chart(), 12)], height="250px")

        # 可折叠区域用于附加数据

        layout.add_row([(self.get_collapsible_table(), 12)], height="auto")

        # 底部导航

        layout.add_row([(self.get_mobile_navigation(), 12)], height="auto")

        return layout

打印优化布局

    def get_print_layout(self):
        """为打印优化的布局"""
        layout = Layout(columns=12)

        # 打印专用样式

        layout.print_css = """
            @media print
                .page-break { page-break-after: always; }
                .dashboard { padding: 0; margin: 0; }
                .card { break-inside: avoid; }
            }
        """

        # 带有打印信息的标题

        layout.add_row([
            (self.get_print_header(), 12)
        ], height="auto", css_class="no-print")

        # 关键指标(避免分页)

        layout.add_row([
            (self.get_print_metric("Revenue"), 4),
            (self.get_print_metric("Cost"), 4),
            (self.get_print_metric("Profit"), 4),
        ], height="auto", css_class="avoid-break")

        # 图表前分页

        layout.add_row([
            (self.get_print_chart(), 12)
        ], height="300px", css_class="page-break")

        # 表格(确保它们不会跨页分割)

        layout.add_row([
            (self.get_print_table(), 12)
        ], height="auto", css_class="avoid-break")

        return layout

布局工具

布局辅助函数

    class LayoutHelpers:
        """常见布局模式的辅助函数"""

        @staticmethod
        def create_grid(components, columns_per_row=3):
            """创建响应式组件网格"""
            layout = Layout(columns=12)
            column_width = 12 // columns_per_row

            for i in range(0, len(components), columns_per_row):
                row_components = components[i:i + columns_per_row]
                row_items = [(comp, column_width) for comp in row_components]

                # 如果最后一行不完整,则调整

                if len(row_items) < columns_per_row:
                    last_item_span = 12 - (column_width * (len(row_items) - 1))
                    row_items[-1] = (row_items[-1][0], last_item_span)

                layout.add_row(row_items, height="auto")

            return layout

        @staticmethod
        def create_two_column(left_component, right_component, left_width=8):
            """创建两列布局"""
            layout = Layout(columns=12)
            right_width = 12 - left_width

            layout.add_row([
                (left_component, left_width),
                (right_component, right_width),
            ], height="auto")

            return layout

        @staticmethod
        def create_sidebar(main_content, sidebar_content, sidebar_width=3):
            """创建侧边栏布局"""
            layout = Layout(columns=12)
            main_width = 12 - sidebar_width

            layout.add_row([
                (main_content, main_width),
                (sidebar_content, sidebar_width),
            ], height="auto")

            return layout

        @staticmethod
        def create_centered(component, width=8):
            """创建居中对齐布局"""
            layout = Layout(columns=12)
            offset = (12 - width) // 2

            # 添加空列以实现偏移

            if offset > 0:
                layout.add_row([
                    (Component(component_type="spacer"), offset),
                    (component, width),
                    (Component(component_type="spacer"), offset),
                ], height="auto")
            else:
                layout.add_row([(component, width)], height="auto")

            return layout

布局验证

    def validate_layout(layout):
        """验证布局结构"""
        errors = []

        for i, row in enumerate(layout.rows):
            total_columns = sum(width for _, width in row["components"])

            if total_columns != 12:
                errors.append(f"Row {i + 1}: Total columns ({total_columns}) != 12")

            for j, (component, width) in enumerate(row["components"]):
                if not component:
                    errors.append(f"Row {i + 1}, Component {j + 1}: Component is None")

                if width < 1 or width > 12:
                    errors.append(f"Row {i + 1}, Component {j + 1}: Invalid width ({width})")

        return errors

下一步

布局故障排除

常见布局问题

1. 组件未对齐

  • 检查每行列跨度总和为12
  • 验证组件是否正确实例化
  • 检查CSS冲突

2. 响应式问题

  • 在不同屏幕尺寸上测试
  • 检查浏览器开发者工具
  • 验证响应式CSS是否已应用

3. 性能问题

  • 减少每行的组件数量
  • 对重型组件实施懒加载
  • 优化数据库查询

4. 打印布局问题

  • 使用打印专用CSS
  • 避免关键内容分页
  • 在浏览器中测试打印预览

调试布局

    def debug_layout(self):
        """调试布局创建"""
        layout = Layout(columns=12)

        print(f"Creating layout for {self.__class__.__name__}")

        # 为每一行添加调试信息

        for i, row_data in enumerate(self.get_row_data()):
            print(f"Adding row {i + 1}: {len(row_data)} components")

            row_components = []
            for j, component in enumerate(row_data):
                print(f"  Component {j + 1}: {component.__class__.__name__}")
                row_components.append((component, 12 // len(row_data)))

            layout.add_row(row_components, height="auto")

        print(f"Layout created with {len(layout.rows)} rows")
        return layout