跳转至

组件

基础使用

    from django_admin_dashboards.base import TableComponent

    table = TableComponent(
        title="Recent Orders",
        columns=["Order ID", "Customer", "Amount", "Date", "Status"],
        data=[
            ["#1001", "John Doe", "$125.50", "2024-01-15", "Completed"],
            ["#1002", "Jane Smith", "$89.99", "2024-01-14", "Processing"],
            ["#1003", "Bob Johnson", "$245.00", "2024-01-13", "Shipped"],
        ]
    )

所有参数

参数 类型 默认值 描述
title string 必需 表格标题
columns list [] 列标题
data list [] 行数据 (列表的列表)
table_id string None 自定义表格ID (为空时自动生成)
show_header bool True 显示列标题
striped bool True 使用斑马条纹
hover bool True 悬停时高亮行
bordered bool True 添加表格边框
small bool False 使用紧凑表格样式
responsive bool True 使表格响应式
empty_message string "No data available" 数据为空时显示的消息

带自定义样式的表格

    TableComponent(
        title="User List",
        columns=["Username", "Email", "Role", "Joined", "Status"],
        data=[
            ["john_doe", "john@example.com", "Admin", "2023-01-15", "Active"],
            ["jane_smith", "jane@example.com", "User", "2023-02-20", "Active"],
            ["bob_johnson", "bob@example.com", "Editor", "2023-03-10", "Inactive"],
        ],
        striped=True,      # Zebra striping

        hover=True,        # Row hover effect

        bordered=True,     # Table borders

        small=False,       # Normal table size

        responsive=True,   # Responsive table

        empty_message="No users found"
    )

动态 表格 数据

    from django.contrib.auth.models import User
    from django.db.models import Count
    from datetime import datetime, timedelta
    from django.utils import timezone

    def get_recent_users_table(self):
        """Generate table with recent user data"""
        recent_users = User.objects.select_related('profile').order_by('-date_joined')[:10]

        table_data = []
        for user in recent_users:
            # Determine user role

            if user.is_superuser:
                role = "Superuser"
            elif user.is_staff:
                role = "Staff"
            else:
                role = "User"

            # Format date

            joined_date = user.date_joined.strftime('%Y-%m-%d %H:%M')

            # Determine status

            if user.is_active:
                status = "Active"
            else:
                status = "Inactive"

            table_data.append([
                user.username,
                user.email,
                role,
                joined_date,
                status,
            ])

        return TableComponent(
            title="Recent Users (Last 10)",
            columns=["Username", "Email", "Role", "Joined", "Status"],
            data=table_data,
            striped=True,
            hover=True
        )

带操作的表格

    def get_user_table_with_actions(self):
        """Table with action buttons"""
        users = User.objects.filter(is_active=True)[:5]

        table_data = []
        for user in users:
            # Create action buttons HTML

            actions = f'''
            <div class="btn-group" role="group">
                <a href="/admin/auth/user/{user.id}/change/" class="btn btn-sm btn-primary">
                    <i class="ri-edit-line"></i> Edit
                </a>
                <a href="/admin/auth/user/{user.id}/delete/" class="btn btn-sm btn-danger">
                    <i class="ri-delete-bin-line"></i> Delete
                </a>
            </div>
            '''

            table_data.append([
                user.username,
                user.email,
                user.get_full_name() or "-",
                user.date_joined.strftime('%Y-%m-%d'),
                actions,
            ])

        return TableComponent(
            title="Active Users",
            columns=["Username", "Email", "Full Name", "Joined", "Actions"],
            data=table_data,
            # Note: HTML in data needs to be marked safe in template

        )

排序和分页

While 表格组件 doesn\'t have 内置 sorting and pagination, you can implement it:

    def get_paginated_table(self, request):
        """Table with pagination support"""
        page = int(request.GET.get('page', 1))
        page_size = 10

        # Get total count

        total_users = User.objects.count()

        # Calculate pagination

        start = (page - 1) * page_size
        end = start + page_size

        # Get paginated data

        users = User.objects.all()[start:end]

        # Generate table data

        table_data = [
            [user.username, user.email, user.date_joined.strftime('%Y-%m-%d')]
            for user in users
        ]

        table = TableComponent(
            title=f"Users (Page {page})",
            columns=["Username", "Email", "Joined"],
            data=table_data,
        )

        # Add pagination info to component

        table.total_pages = (total_users + page_size - 1) // page_size
        table.current_page = page
        table.page_size = page_size

        return table

过滤器组件

用于数据看板数据过滤的交互式过滤器。采用现代化的下拉面板设计,节省空间并提供良好的用户体验。

UI特性: - 触发按钮:浅灰色按钮显示过滤器数量和状态 - 下拉面板:点击按钮展开包含所有过滤器的下拉面板 - Select2集成:所有选择框自动启用Select2,支持搜索功能 - 过滤器计数:按钮上显示当前激活的过滤器数量 - 响应式设计:自适应桌面和移动设备显示

基础使用

    from django_admin_dashboards.base import FilterComponent

    filters = FilterComponent(
        title="Dashboard Filters",
        filters=[
            {
                "name": "date_range",
                "label": "Date Range",
                "type": "date_range",
                "default": "last_30_days",
            },
            {
                "name": "status",
                "label": "Status",
                "type": "select",
                "options": [
                    {"value": "all", "label": "All"},
                    {"value": "active", "label": "Active"},
                    {"value": "inactive", "label": "Inactive"},
                ],
                "default": "all",
            },
        ],
    )

过滤器类型

选择过滤器

    {
        "name": "category",
        "label": "Category",
        "type": "select",
        "options": [
            {"value": "all", "label": "All Categories"},
            {"value": "electronics", "label": "Electronics"},
            {"value": "clothing", "label": "Clothing"},
            {"value": "books", "label": "Books"},
        ],
        "default": "all",

        "multiple": False,  # Single selection

        "searchable": True, # Enable search (Select2自动启用)
    }

日期范围过滤器

    {
        "name": "date_range",
        "label": "Date Range",
        "type": "date_range",
        "default": "last_30_days",
        "presets": [
            {"value": "all_time", "label": "All time"},
            {"value": "today", "label": "Today"},
            {"value": "yesterday", "label": "Yesterday"},
            {"value": "last_7_days", "label": "Last 7 Days"},
            {"value": "last_30_days", "label": "Last 30 Days"},
            {"value": "this_month", "label": "This Month"},
            {"value": "last_month", "label": "Last Month"},
            {"value": "this_year", "label": "This Year"},
            {"value": "custom", "label": "Custom Range"},
        ],
        "format": "YYYY-MM-DD",  # Date format

    }

多选过滤器

    {
        "name": "tags",
        "label": "Tags",
        "type": "multiselect",
        "options": [
            {"value": "featured", "label": "Featured"},
            {"value": "new", "label": "New"},
            {"value": "sale", "label": "On Sale"},
            {"value": "bestseller", "label": "Bestseller"},
        ],
        "default": ["featured", "new"],  # Default selections

        "searchable": True,
        "placeholder": "Select tags...",
    }

文本输入过滤器

    {
        "name": "search",
        "label": "Search",
        "type": "text",
        "placeholder": "Enter search term...",
        "default": "",
        "debounce": 300,  # Delay in ms before triggering filter

    }

数字范围过滤器

    {
        "name": "price_range",
        "label": "Price Range",
        "type": "number_range",
        "min_value": 0,
        "max_value": 10000,
        "step": 10,
        "suffix": "$",  # Display suffix

    }

复选框过滤器

    {
        "name": "options",
        "label": "Options",
        "type": "checkbox",
        "options": [
            {"value": "featured", "label": "Featured Only", "default": False},
            {"value": "in_stock", "label": "In Stock Only", "default": True},
            {"value": "free_shipping", "label": "Free Shipping", "default": False},
        ],
    }

完整过滤器示例

    FilterComponent(
        title="Advanced Filters",
        filters=[
            {
                "name": "date_range",
                "label": "Date Range",
                "type": "date_range",
                "default": "last_30_days",
                "presets": [
                    {"value": "today", "label": "Today"},
                    {"value": "last_7_days", "label": "Last 7 Days"},
                    {"value": "last_30_days", "label": "Last 30 Days"},
                    {"value": "this_quarter", "label": "This Quarter"},
                ],
            },
            {
                "name": "status",
                "label": "Status",
                "type": "select",
                "options": [
                    {"value": "all", "label": "All Statuses"},
                    {"value": "pending", "label": "Pending"},
                    {"value": "approved", "label": "Approved"},
                    {"value": "rejected", "label": "Rejected"},
                    {"value": "completed", "label": "Completed"},
                ],
                "default": "all",
                "searchable": True,
            },
            {
                "name": "categories",
                "label": "Categories",
        "type": "multi_select",
                "options": [
                    {"value": "electronics", "label": "Electronics"},
                    {"value": "clothing", "label": "Clothing"},
                    {"value": "home", "label": "Home & Garden"},
                    {"value": "sports", "label": "Sports"},
                    {"value": "books", "label": "Books & Media"},
                ],
                "default": [],
                "searchable": True,
                "placeholder": "Select categories...",
            },
            {
                "name": "price_range",
                "label": "Price Range ($)",
                "type": "number_range",
                "min": 0,
                "max": 5000,
                "step": 50,
                "default_min": 0,
                "default_max": 1000,
            },
            {
                "name": "features",
                "label": "Features",
                "type": "checkbox",
                "options": [
                    {"value": "featured", "label": "Featured Only", "default": False},
                    {"value": "new", "label": "New Arrivals", "default": False},
                    {"value": "discounted", "label": "Discounted", "default": True},
                ],
            },
        ],
    )

动态过滤器选项

    def get_dynamic_filters(self):
        """Generate filters with dynamic options from database"""
        from django.db.models import Count

        # Get categories from database

        categories = Product.objects.values('category').annotate(
            count=Count('id')
        ).order_by('category')

        category_options = [
            {"value": "all", "label": "All Categories"}
        ]
        category_options.extend([
            {"value": cat['category'], "label": f"{cat['category']} ({cat['count']})"}
            for cat in categories if cat['category']
        ])

        # Get statuses from database

        statuses = Order.objects.values('status').annotate(
            count=Count('id')
        ).distinct()

        status_options = [
            {"value": "all", "label": "All Statuses"}
        ]
        status_options.extend([
            {"value": status['status'], "label": f"{status['status']} ({status['count']})"}
            for status in statuses
        ])

        return FilterComponent(
            title="Dynamic Filters",
            filters=[
                {
                    "name": "category",
                    "label": "Category",
                    "type": "select",
                    "options": category_options,
                    "default": "all",
                },
                {
                    "name": "status",
                    "label": "Order Status",
                    "type": "select",
                    "options": status_options,
                    "default": "all",
                },
                {
                    "name": "date_range",
                    "label": "Order Date",
                    "type": "date_range",
                    "default": "last_30_days",
                },
            ],
        )

将过滤器应用于数据

    def get_filtered_data(self, request):
        """Apply filters to database queries"""
        # Get filter values from request

        category = request.GET.get('category', 'all')
        status = request.GET.get('status', 'all')
        date_range = request.GET.get('date_range', 'last_30_days')

        # Start with base queryset

        queryset = Order.objects.all()

        # Apply category filter

        if category != 'all':
            queryset = queryset.filter(product__category=category)

        # Apply status filter

        if status != 'all':
            queryset = queryset.filter(status=status)

        # Apply date range filter

        if date_range == 'last_30_days':
            thirty_days_ago = timezone.now() - timedelta(days=30)
            queryset = queryset.filter(created_at__gte=thirty_days_ago)
        elif date_range == 'last_7_days':
            seven_days_ago = timezone.now() - timedelta(days=7)
            queryset = queryset.filter(created_at__gte=seven_days_ago)
        # ... handle other date ranges

        return queryset

自定义 Components

为您特定的功能创建您自己的自定义组件。

创建自定义组件

    # myapp/components.py

    from django_admin_dashboards.base import Component

    class ProgressBarComponent(Component):
        """Custom progress bar component"""

        component_type = "progress_bar"
        template_name = "admin/components/progress_bar.html"

        def __init__(self, title, value, max_value=100, color="primary", **kwargs):
            super().__init__(**kwargs)
            self.title = title
            self.value = value
            self.max_value = max_value
            self.color = color
            self.percentage = (value / max_value * 100) if max_value > 0 else 0

        def get_context_data(self, request):
            context = super().get_context_data(request)
            context.update({
                "percentage": self.percentage,
                "display_value": f"{self.value}/{self.max_value}",
            })
            return context

自定义组件模板

    <div class="progress-bar-component">
        <div class="progress-bar-header">
            <h4>{{ component.title }}</h4>
            <span class="progress-value">{{ component.display_value }}</span>
        </div>
        <div class="progress-bar-container">
            <div class="progress-bar" style="width: {{ percentage }}%; background-color: var(--color-{{ component.color }});"></div>
        </div>
        <div class="progress-bar-footer">
            <span class="progress-percentage">{{ percentage|floatformat:1 }}%</span>
        </div>
    </div>

使用自定义组件

    from myapp.components import ProgressBarComponent

    class MyDashboard(Dashboard):
        def get_layout(self):
            layout = Layout(columns=12)

            layout.add_row([
                (ProgressBarComponent(
                    title="Project Completion",
                    value=75,
                    max_value=100,
                    color="success"
                ), 6),
                (ProgressBarComponent(
                    title="Budget Usage",
                    value=45000,
                    max_value=50000,
                    color="warning"
                ), 6),
            ], height="auto")

            return layout

带 JavaScript 的组件

    class InteractiveMapComponent(Component):
        """Interactive map component with JavaScript"""

        component_type = "interactive_map"
        template_name = "admin/components/interactive_map.html"

        def __init__(self, title, data_points, center=None, zoom=10, **kwargs):
            super().__init__(**kwargs)
            self.title = title
            self.data_points = data_points  # List of {lat, lng, title, value}

            self.center = center or {"lat": 0, "lng": 0}
            self.zoom = zoom
            self.map_id = f"map_{id(self)}"

        def get_context_data(self, request):
            context = super().get_context_data(request)
            context.update({
                "map_id": self.map_id,
                "data_points_json": json.dumps(self.data_points),
                "center_json": json.dumps(self.center),
                "zoom": self.zoom,
            })
            return context

        class Media:
            css = {
                "all": ("css/leaflet.css",)
            }
            js = ("js/leaflet.js", "js/map-component.js")

组件样式

CSS 类和自定义

每个组件都有用于样式的特定 CSS 类:

卡片组件 Classes

  • .card - 主要卡片容器
  • .card-primary.card-success 等 - 颜色变体
  • .card-icon - 图标容器
  • .card-content - 内容区域
  • .card-title - 卡片标题
  • .card-value - 主要值
  • .card-change - 变化指示器
  • .card-multi-value - 多值容器
  • .card-value-item - 单个值项
  • .card-value-label - 值标签

图表组件 Classes

  • .图表-container - 主要图表容器
  • .图表-wrapper - 图表画布包装器
  • canvas - Chart.js 画布元素

表格组件 Classes

  • .table-container - 表格包装器
  • .dashboard-table - 表格元素
  • .table-responsive - 响应式包装器
  • theadtbodytrthtd - 标准表格元素

过滤器组件 Classes

  • .dashboard-filters - 过滤器容器
  • .filter-group - 单个过滤器组
  • .filter-label - 过滤器标签
  • .filter-control - 过滤器输入/选择

自定义 CSS 样式

将自定义 CSS 添加到您的数据看板:

    class StyledDashboard(Dashboard):
        class Media:
            css = {
                "all": (
                    "django_admin_dashboards/css/dashboard.css",
                    "remixicon/remixicon.css",
                    "myapp/css/custom-styles.css",  # 您的自定义 CSS

                )
            }

自定义 CSS example:

    /* myapp/static/myapp/css/custom-styles.css */

    /* Custom card styling */
    .card.custom-theme {
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        color: white;
        border: none;
        box-shadow: 0 10px 20px rgba(0,0,0,0.1);
    }

    .card.custom-theme .card-title {
        color: rgba(255,255,255,0.9);
        font-weight: 600;
    }

    .card.custom-theme .card-value {
        color: white;
        font-size: 2.5rem;
        font-weight: 700;
    }

    /* Custom table styling */
    .dashboard-table.custom-table {
        border-collapse: separate;
        border-spacing: 0;
        border-radius: 8px;
        overflow: hidden;
    }

    .dashboard-table.custom-table thead {
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        color: white;
    }

    .dashboard-table.custom-table th {
        border: none;
        padding: 1rem;
        font-weight: 600;
    }

    .dashboard-table.custom-table tbody tr:hover {
        background-color: rgba(102, 126, 234, 0.1);
    }

    /* Custom chart container */
    .chart-container.custom-chart {
        border: 1px solid #e0e0e0;
        border-radius: 8px;
        padding: 1.5rem;
        background: white;
        box-shadow: 0 2px 4px rgba(0,0,0,0.05);
    }

    .chart-container.custom-chart h3 {
        color: #333;
        margin-bottom: 1rem;
        font-weight: 600;
        border-bottom: 2px solid #667eea;
        padding-bottom: 0.5rem;
    }

响应式设计

组件默认是响应式的。对于自定义响应式行为:

    /* 响应式卡片布局 */
    @media (max-width: 768px)

        .card-value {
            font-size: 1.8rem !important;
        }

        .card-multi-value {
            flex-direction: column;
        }

        .card-value-separator {
            display: none;
        }
    }

    /* Responsive table */
    @media (max-width: 768px)

        .dashboard-table {
            font-size: 0.9rem;
        }

        .dashboard-table th,
        .dashboard-table td {
            padding: 0.5rem;
        }
    }

    /* Responsive charts */
    @media (max-width: 768px)

        .chart-wrapper {
            height: 250px !important;
        }
    }

性能优化

组件级缓存

    from django.core.cache import cache
    from django.utils.functional import cached_property

    class OptimizedDashboard(Dashboard):

        @cached_property
        def cached_data(self):
            """缓存昂贵的数据计算"""
            cache_key = f"dashboard_data_{self.__class__.__name__}"
            data = cache.get(cache_key)

            if not data:
                data = self.calculate_expensive_data()
                cache.set(cache_key, data, timeout=300)  # 5 minutes

            return data

        def get_cards(self):
            """使用缓存数据用于卡片"""
            data = self.cached_data

            return [
                CardComponent(
                    title="Total Revenue",
                    value=f"${data['revenue']:,.2f}",
                    color="success",
                    icon_class="ri-money-dollar-circle-line"
                ),
                # ... more cards using cached data

            ]

懒加载组件

    class LazyDashboard(Dashboard):

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

            # Immediate components

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

            # Lazy-loaded components

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

            return layout

        def get_lazy_chart(self):
            """异步加载数据的图表"""
            chart = ChartComponent(
                title="Large Dataset Chart",
                chart_type="line",
                data={},  # Empty initial data

                options={
                    "responsive": True,
                    "plugins": {
                        "legend": {"position": "top"},
                    }
                }
            )

            # Add lazy loading attribute

            chart.lazy_load = True
            chart.data_url = "/api/large-dataset/"

            return chart

数据库查询优化

    def get_optimized_user_cards(self):
        """为用户卡片优化的数据库查询"""
        from django.db.models import Count, Avg, Sum
        from django.db.models.functions import TruncMonth

        # Single query for all metrics

        stats = User.objects.aggregate(
            total=Count('id'),
            active=Count('id', filter=Q(is_active=True)),
            staff=Count('id', filter=Q(is_staff=True)),
            superusers=Count('id', filter=Q(is_superuser=True)),
        )

        # Another optimized query for recent data

        recent_stats = User.objects.filter(
            date_joined__gte=timezone.now() - timedelta(days=30)
        ).aggregate(
            new_users=Count('id'),
            avg_daily=Count('id') / 30,  # Average per day

        )

        return [
            CardComponent(
                title="Total Users",
                value=stats['total'],
                color="primary",
                icon_class="ri-user-line"
            ),
            CardComponent(
                title="Active Users",
                value=stats['active'],
                color="success",
                icon_class="ri-user-heart-line"
            ),
            CardComponent(
                title="New Users (30d)",
                value=recent_stats['new_users'],
                change=f"+{recent_stats['avg_daily']:.1f}/day",
                trend="up",
                color="info",
                icon_class="ri-user-add-line"
            ),
        ]

组件配方

快速统计卡片

    def quick_stats_card(title, value, icon, color="primary"):
        """快速统计卡片辅助函数"""
        return CardComponent(
            title=title,
            value=value,
            color=color,
            icon_class=icon,
            description="刚刚更新",
            footer_text="点击查看详情",
            link_url=f"/admin/stats/?metric={title.lower().replace(' ', '_')}"
        )

趋势指示器卡片

    def trend_card(title, current_value, previous_value, icon, color="primary"):
        """带有趋势指示器的卡片"""
        if previous_value == 0:
            change_percent = 100 if current_value > 0 else 0
        else:
            change_percent = ((current_value - previous_value) / previous_value) * 100

        trend = "up" if change_percent > 0 else "down" if change_percent < 0 else "neutral"
        change_text = f"{change_percent:+.1f}%"

        return CardComponent(
            title=title,
            value=current_value,
            change=change_text,
            trend=trend,
            color=color,
            icon_class=icon
        )

进度卡片

    def progress_card(title, current, target, icon, color="primary"):
        """显示目标进度的卡片"""
        percentage = (current / target * 100) if target > 0 else 0

        return CardComponent(
            title=title,
            value=f"{current}/{target}",
            change=f"{percentage:.1f}%",
            trend="up" if percentage >= 100 else "neutral",
            color="success" if percentage >= 100 else "warning" if percentage >= 75 else "primary",
            icon_class=icon
        )

对比图表

    def comparison_chart(title, current_data, previous_data, labels):
        """比较当前周期与先前周期的图表"""
        return ChartComponent(
            title=title,
            chart_type="bar",
            data={
                "labels": labels,
                "datasets": [
                    {
                        "label": "当前周期",
                        "data": current_data,
                        "backgroundColor": "rgba(75, 192, 192, 0.6)",
                        "borderColor": "rgb(75, 192, 192)",
                        "borderWidth": 1,
                    },
                    {
                        "label": "先前周期",
                        "data": previous_data,
                        "backgroundColor": "rgba(255, 99, 132, 0.6)",
                        "borderColor": "rgb(255, 99, 132)",
                        "borderWidth": 1,
                    }
                ]
            },
            options={
                "responsive": True,
                "plugins": {
                    "legend": {"position": "top"},
                },
                "scales": {
                    "y": {"beginAtZero": True}
                }
            }
        )

后续步骤

组件故障排除

常见问题

1. 组件未渲染

  • 检查组件是否正确实例化
  • 验证自定义组件的模板是否存在
  • 检查浏览器控制台中的 JavaScript 错误

2. 数据未显示

  • 验证数据是否为组件的正确格式
  • 检查 None 或空值
  • 确保数据库查询返回结果

3. 性能问题

  • 使用 select_relatedprefetch_related 优化数据库查询
  • 为昂贵的计算实现缓存
  • 对大型数据集使用懒加载

4. 样式问题

  • 检查 CSS 选择器是否正确
  • 验证自定义 CSS 是否加载
  • 检查与 Django Admin 样式的 CSS 冲突

调试组件

    # 添加调试输出到组件

    class DebugComponent(Component):
        def get_context_data(self, request):
            context = super().get_context_data(request)
            print(f"Component: {self.component_type}", file=sys.stderr)
            print(f"Request: {request}", file=sys.stderr)
            print(f"Context keys: {list(context.keys())}", file=sys.stderr)
            return context