跳到内容

仪表盘

编辑此页

仪表盘是后端的入口点,它们链接到一个或多个 资源。仪表盘还显示一个主菜单,用于导航资源和已登录用户的信息。

假设你有一个简单的应用程序,包含三个 Doctrine 实体:用户、博客文章和分类。你的员工可以创建和编辑所有这些实体,但外部合作者只能创建博客文章。

你可以在 EasyAdmin 中按如下方式实现这一点

  • 创建三个 CRUD 控制器 (例如,UserCrudControllerBlogPostCrudControllerCategoryCrudController);
  • 为你的员工创建一个仪表盘 (例如,DashboardController) 并链接到这三个资源;
  • 为你的外部合作者创建一个仪表盘 (例如,ExternalDashboardController) 并且只链接到 BlogPostCrudController 资源。

从技术上讲,仪表盘是常规的 Symfony 控制器,所以你可以像在控制器中通常做的那样做任何事情,例如注入服务和使用快捷方式,如 $this->render()$this->isGranted()

仪表盘控制器类必须实现 EasyCorp\Bundle\EasyAdminBundle\Contracts\Controller\DashboardControllerInterface 接口,这确保了某些方法在仪表盘中被定义。除了实现接口,你也可以继承 AbstractDashboardController 类。运行以下命令可以快速生成一个仪表盘控制器

1
$ php bin/console make:admin:dashboard

仪表盘路由

每个仪表盘都使用单个 Symfony 路由来服务于其所有 URL。所需的信息通过查询字符串参数传递。如果你使用 make:admin:dashboard 命令生成了仪表盘,则该路由使用 Symfony 路由注解 或 PHP 属性(如果项目需要 PHP 8 或更高版本)来定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/Controller/Admin/DashboardController.php
namespace App\Controller\Admin;

use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class DashboardController extends AbstractDashboardController
{
    /**
     * @Route("/admin")
     */
    public function index(): Response
    {
        return parent::index();
    }

    // ...
}

/admin URL 只是一个默认值,你可以更改它。如果你这样做,别忘了在你的 Symfony 安全配置中也更新这个值,以便 限制对整个后端的访问

没有必要为此路由定义显式名称。Symfony 会自动生成路由名称,EasyAdmin 在运行时获取该值以生成所有 URL。但是,如果你在应用程序的其他部分生成指向仪表盘的 URL,你可以定义一个显式路由名称来简化你的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/Controller/Admin/DashboardController.php
namespace App\Controller\Admin;

use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class DashboardController extends AbstractDashboardController
{
    /**
     * @Route("/admin", name="some_route_name")
     */
    public function index(): Response
    {
        return parent::index();
    }

    // ...
}

如果你不使用注解,你必须在一个单独的文件中使用 YAML、XML 或 PHP 配置来配置仪表盘路由

1
2
3
4
5
6
# config/routes.yaml
dashboard:
    path: /admin
    controller: App\Controller\Admin\DashboardController::index

# ...

在实践中,你无需在你的应用程序中处理此路由或查询字符串参数,因为 EasyAdmin 提供了一个服务来 生成管理 URL

注意

使用单个路由来处理所有后端 URL 意味着生成的 URL 有点长且不够美观。这是一个合理的权衡,因为它使许多其他功能(例如生成管理 URL)变得更加简单。

仪表盘配置

仪表盘配置在 configureDashboard() 方法中定义(主菜单和用户菜单在其各自的方法中配置,稍后会解释)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
namespace App\Controller\Admin;

use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;

class DashboardController extends AbstractDashboardController
{
    // ...

    public function configureDashboard(): Dashboard
    {
        return Dashboard::new()
            // the name visible to end users
            ->setTitle('ACME Corp.')
            // you can include HTML contents too (e.g. to link to an image)
            ->setTitle('<img src="..."> ACME <span class="text-small">Corp.</span>')

            // the path defined in this method is passed to the Twig asset() function
            ->setFaviconPath('favicon.svg')

            // the domain used by default is 'messages'
            ->setTranslationDomain('my-custom-domain')

            // there's no need to define the "text direction" explicitly because
            // its default value is inferred dynamically from the user locale
            ->setTextDirection('ltr')

            // set this option if you prefer the page content to span the entire
            // browser width, instead of the default design which sets a max width
            ->renderContentMaximized()

            // set this option if you prefer the sidebar (which contains the main menu)
            // to be displayed as a narrow column instead of the default expanded design
            ->renderSidebarMinimized()

            // by default, all backend URLs include a signature hash. If a user changes any
            // query parameter (to "hack" the backend) the signature won't match and EasyAdmin
            // triggers an error. If this causes any issue in your backend, call this method
            // to disable this feature and remove all URL signature checks
            ->disableUrlSignatures()

            // by default, all backend URLs are generated as absolute URLs. If you
            // need to generate relative URLs instead, call this method
            ->generateRelativeUrls()
        ;
    }
}

主菜单从仪表盘链接到不同的 CRUD 控制器。这是关联仪表盘和资源的唯一方法。出于安全原因,后端只能通过主菜单访问与仪表盘关联的资源。

主菜单是实现了 EasyCorp\Bundle\EasyAdminBundle\Contracts\Menu\MenuInterface 的对象的集合,这些对象配置每个菜单项的外观和行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use App\Entity\BlogPost;
use App\Entity\Category;
use App\Entity\Comment;
use App\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;

class DashboardController extends AbstractDashboardController
{
    // ...

    public function configureMenuItems(): iterable
    {
        return [
            MenuItem::linkToDashboard('Dashboard', 'fa fa-home'),

            MenuItem::section('Blog'),
            MenuItem::linkToCrud('Categories', 'fa fa-tags', Category::class),
            MenuItem::linkToCrud('Blog Posts', 'fa fa-file-text', BlogPost::class),

            MenuItem::section('Users'),
            MenuItem::linkToCrud('Comments', 'fa fa-comment', Comment::class),
            MenuItem::linkToCrud('Users', 'fa fa-user', User::class),
        ];
    }
}

MenuItem::new() 的第一个参数是项目显示的标签,第二个参数是要显示的 FontAwesome 图标的完整 CSS 类。

所有菜单项都定义了以下方法来配置一些选项

  • setCssClass(string $cssClass),设置应用于菜单项的 <li> 父元素的 CSS 类或类;
  • setLinkRel(string $rel),设置菜单项链接的 rel HTML 属性(查看 “rel”属性的允许值);
  • setLinkTarget(string $target),设置菜单项链接的 target HTML 属性(默认为 _self);
  • setPermission(string $permission),设置用户必须拥有的 Symfony 安全权限才能看到此菜单项。阅读 菜单安全参考 了解更多详情。
  • setBadge($content, string $style='secondary'),将给定的内容呈现为菜单项的徽章。它通常用于显示通知计数。第一个参数可以是任何可以在 Twig 模板中转换为字符串的值(数字、字符串、*stringable* 对象等)。第二个参数是预定义的 Bootstrap 样式之一(primarysecondarysuccessdangerwarninginfolightdark)或任意字符串内容,该内容作为与徽章关联的 HTML 元素的 style 属性的值传递。

其余选项取决于每个菜单项类型,如下节所述。

CRUD 菜单项

这是最常见的菜单项类型,它链接到某个 CRUD 控制器 的某个操作。你必须传递与 CRUD 控制器关联的 Doctrine 实体的 FQCN,而不是传递 CRUD 控制器的 FQCN *(完全限定类名)*

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use App\Entity\Category;
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;

public function configureMenuItems(): iterable
{
    return [
        // ...

        // links to the 'index' action of the Category CRUD controller
        MenuItem::linkToCrud('Categories', 'fa fa-tags', Category::class),

        // links to a different CRUD action
        MenuItem::linkToCrud('Add Category', 'fa fa-tags', Category::class)
            ->setAction('new'),

        MenuItem::linkToCrud('Show Main Category', 'fa fa-tags', Category::class)
            ->setAction('detail')
            ->setEntityId(1),

        // if the same Doctrine entity is associated to more than one CRUD controller,
        // use the 'setController()' method to specify which controller to use
        MenuItem::linkToCrud('Categories', 'fa fa-tags', Category::class)
            ->setController(LegacyCategoryCrudController::class),

        // uses custom sorting options for the listing
        MenuItem::linkToCrud('Categories', 'fa fa-tags', Category::class)
            ->setDefaultSort(['createdAt' => 'DESC']),
    ];
}

仪表盘菜单项

它链接到当前仪表盘的主页。你可以使用“路由菜单项”(如下所述)实现相同的效果,但这个更简单,因为你无需指定路由名称(它会自动找到)

1
2
3
4
5
6
7
8
9
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;

public function configureMenuItems(): iterable
{
    return [
        MenuItem::linkToDashboard('Home', 'fa fa-home'),
        // ...
    ];
}

路由菜单项

它链接到你的 Symfony 应用程序定义的任何路由

1
2
3
4
5
6
7
8
9
10
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;

public function configureMenuItems(): iterable
{
    return [
        MenuItem::linkToRoute('The Label', 'fa ...', 'route_name'),
        MenuItem::linkToRoute('The Label', 'fa ...', 'route_name', ['routeParamName' => 'routeParamValue']),
        // ...
    ];
}

注意

阅读关于 在 EasyAdmin 中集成 Symfony 控制器/操作 的章节,以充分理解 linkToRoute() 生成的 URL。

URL 菜单项

它链接到相对或绝对 URL

1
2
3
4
5
6
7
8
9
10
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;

public function configureMenuItems(): iterable
{
    return [
        MenuItem::linkToUrl('Visit public website', null, '/'),
        MenuItem::linkToUrl('Search in Google', 'fab fa-google', 'https://google.com'),
        // ...
    ];
}

为避免将内部后端信息泄露到外部网站,EasyAdmin 会将 rel="noopener" 属性添加到所有 URL 菜单项,除非菜单项定义了自己的 rel 选项。

分区菜单项

它在菜单项之间创建视觉分隔,并且可以选择性地显示一个标签,该标签充当下方菜单项的标题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;

public function configureMenuItems(): iterable
{
    return [
        // ...

        MenuItem::section(),
        // ...

        MenuItem::section('Blog'),
        // ...
    ];
}

注销菜单项

它链接到用户必须访问才能从应用程序注销的 URL。如果你知道注销路由名称,你可以使用“路由菜单项”实现相同的效果,但这个更方便,因为它会自动为当前安全防火墙找到注销 URL

1
2
3
4
5
6
7
8
9
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;

public function configureMenuItems(): iterable
{
    return [
        // ...
        MenuItem::linkToLogout('Logout', 'fa fa-exit'),
    ];
}

注意

注销菜单项在某些身份验证方案(如 HTTP Basic)下不起作用,因为由于这些身份验证方案的工作方式,它们没有配置默认的注销路径。

如果你遇到类似 *“无法找到当前防火墙 LogoutListener,请手动提供提供程序密钥。”* 的错误,你需要删除注销菜单项或向你的身份验证方案添加注销提供程序。

退出模拟菜单项

它链接到用户必须访问才能停止模拟其他用户的 URL

1
2
3
4
5
6
7
8
9
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;

public function configureMenuItems(): iterable
{
    return [
        // ...
        MenuItem::linkToExitImpersonation('Stop impersonation', 'fa fa-exit'),
    ];
}

主菜单最多可以显示两级嵌套菜单。子菜单使用 subMenu() 项目类型定义

1
2
3
4
5
6
7
8
9
10
11
12
13
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;

public function configureMenuItems(): iterable
{
    return [
        MenuItem::subMenu('Blog', 'fa fa-article')->setSubItems([
            MenuItem::linkToCrud('Categories', 'fa fa-tags', Category::class),
            MenuItem::linkToCrud('Posts', 'fa fa-file-text', BlogPost::class),
            MenuItem::linkToCrud('Comments', 'fa fa-comment', Comment::class),
        ]),
        // ...
    ];
}

注意

在子菜单中,父菜单项不能链接到任何资源、路由或 URL;它只能展开/折叠子菜单项。

复杂的主菜单

configureMenuItems() 的返回类型是 iterable,因此你不必总是返回数组。例如,如果你的主菜单需要复杂的逻辑来决定为每个用户显示哪些项目,则使用生成器返回菜单项会更方便

1
2
3
4
5
6
7
8
9
10
11
12
public function configureMenuItems(): iterable
{
    yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');

    if ('... some complex expression ...') {
        yield MenuItem::section('Blog');
        yield MenuItem::linkToCrud('Categories', 'fa fa-tags', Category::class);
        yield MenuItem::linkToCrud('Blog Posts', 'fa fa-file-text', BlogPost::class);
    }

    // ...
}

用户菜单

当访问受保护的后端时,EasyAdmin 会显示已登录应用程序的用户的详细信息以及包含一些选项(如“注销”)的菜单(如果启用了 Symfony 的 注销功能)。

用户名是对当前用户对象调用 __toString() 方法的结果。用户头像是一个通用的头像图标。使用 configureUserMenu() 方法配置此菜单的功能和项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
use EasyCorp\Bundle\EasyAdminBundle\Config\UserMenu;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\Security\Core\User\UserInterface;

class DashboardController extends AbstractDashboardController
{
    // ...

    public function configureUserMenu(UserInterface $user): UserMenu
    {
        // Usually it's better to call the parent method because that gives you a
        // user menu with some menu items already created ("sign out", "exit impersonation", etc.)
        // if you prefer to create the user menu from scratch, use: return UserMenu::new()->...
        return parent::configureUserMenu($user)
            // use the given $user object to get the user name
            ->setName($user->getFullName())
            // use this method if you don't want to display the name of the user
            ->displayUserName(false)

            // you can return an URL with the avatar image
            ->setAvatarUrl('https://...')
            ->setAvatarUrl($user->getProfileImageUrl())
            // use this method if you don't want to display the user image
            ->displayUserAvatar(false)
            // you can also pass an email address to use gravatar's service
            ->setGravatarEmail($user->getMainEmailAddress())

            // you can use any type of menu item, except submenus
            ->addMenuItems([
                MenuItem::linkToRoute('My Profile', 'fa fa-id-card', '...', ['...' => '...']),
                MenuItem::linkToRoute('Settings', 'fa fa-user-cog', '...', ['...' => '...']),
                MenuItem::section(),
                MenuItem::linkToLogout('Logout', 'fa fa-sign-out'),
            ]);
    }
}

管理上下文

EasyAdmin 在每个后端请求上自动初始化一个类型为 EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext 的变量。此对象实现了 上下文对象 设计模式,并存储了后端不同部分常用的所有信息。

此上下文对象会自动注入到每个模板中,作为一个名为 ea 的变量(“EasyAdmin”的首字母缩写)

1
2
3
4
5
<h1>{{ ea.dashboardTitle }}</h1>

{% for menuItem in ea.mainMenu.items %}
    {# ... #}
{% endfor %}

AdminContext 变量在每个请求上动态创建,因此你不能直接将其注入到你的服务中。相反,使用 AdminContextProvider 服务来获取上下文变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use EasyCorp\Bundle\EasyAdminBundle\Provider\AdminContextProvider;

final class SomeService
{
    private $adminContextProvider;

    public function __construct(AdminContextProvider $adminContextProvider)
    {
        $this->adminContextProvider = $adminContextProvider;
    }

    public function someMethod()
    {
        $context = $this->adminContextProvider->getContext();
    }

    // ...
}

在 EasyAdmin 的 CRUD 控制器集成到 EasyAdmin 中的 Symfony 控制器 中,在你想要注入上下文对象的任何参数中使用 AdminContext 类型提示

1
2
3
4
5
6
7
8
9
10
use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class SomeController extends AbstractController
{
    public function someMethod(AdminContext $context)
    {
        // ...
    }
}

翻译

后端界面使用 Symfony 翻译 功能完全翻译。EasyAdmin 自己的消息和内容使用 EasyAdminBundle 翻译域(感谢我们的社区为数十种语言慷慨提供翻译)。

其余内容(例如,菜单项的标签、实体和字段名称等)默认使用 messages 翻译域。你可以使用 translationDomain() 方法更改此值

1
2
3
4
5
6
7
8
9
10
11
12
13
class DashboardController extends AbstractDashboardController
{
    // ...

    public function configureDashboard(): Dashboard
    {
        return Dashboard::new()
            // ...

            // the argument is the name of any valid Symfony translation domain
            ->setTranslationDomain('admin');
    }
}

后端使用在 Symfony 应用程序中配置的相同语言。当语言环境为阿拉伯语 (ar)、波斯语 (fa) 或希伯来语 (he) 时,HTML 文本方向会自动设置为 rtl(从右到左)。否则,文本将显示为 ltr(从左到右),但你可以显式配置此值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class DashboardController extends AbstractDashboardController
{
    // ...

    public function configureDashboard(): Dashboard
    {
        return Dashboard::new()
            // ...

            // most of the times there's no need to configure this explicitly
            // (default: 'rtl' or 'ltr' depending on the language)
            ->setTextDirection('rtl');
    }
}

提示

如果你想让后端使用与公共网站不同的语言,你需要 使用用户语言环境 在翻译服务检索请求语言环境之前设置请求语言环境。

注意

存储在数据库中的内容(例如,博客文章的内容或产品的名称)不会被翻译。EasyAdmin 不支持将实体属性内容翻译成不同的语言。

页面模板

EasyAdmin 提供了几个页面模板,这些模板在你的仪表盘中添加自定义逻辑时非常有用。

登录表单模板

Twig 模板路径:@EasyAdmin/page/login.html.twig

它显示一个简单的用户名 + 密码登录表单,其样式与后端的其余部分相匹配。该模板定义了许多配置选项,但大多数应用程序可以依赖其默认值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;

class SecurityController extends AbstractController
{
    /**
     * @Route("/login", name="login")
     */
    public function login(AuthenticationUtils $authenticationUtils): Response
    {
        $error = $authenticationUtils->getLastAuthenticationError();
        $lastUsername = $authenticationUtils->getLastUsername();

        return $this->render('@EasyAdmin/page/login.html.twig', [
            // parameters usually defined in Symfony login forms
            'error' => $error,
            'last_username' => $lastUsername,

            // OPTIONAL parameters to customize the login form:

            // the translation_domain to use (define this option only if you are
            // rendering the login template in a regular Symfony controller; when
            // rendering it from an EasyAdmin Dashboard this is automatically set to
            // the same domain as the rest of the Dashboard)
            'translation_domain' => 'admin',

            // the title visible above the login form (define this option only if you are
            // rendering the login template in a regular Symfony controller; when rendering
            // it from an EasyAdmin Dashboard this is automatically set as the Dashboard title)
            'page_title' => 'ACME login',

            // the string used to generate the CSRF token. If you don't define
            // this parameter, the login form won't include a CSRF token
            'csrf_token_intention' => 'authenticate',

            // the URL users are redirected to after the login (default: '/admin')
            'target_path' => $this->generateUrl('admin_dashboard'),

            // the label displayed for the username form field (the |trans filter is applied to it)
            'username_label' => 'Your username',

            // the label displayed for the password form field (the |trans filter is applied to it)
            'password_label' => 'Your password',

            // the label displayed for the Sign In form button (the |trans filter is applied to it)
            'sign_in_label' => 'Log in',

            // the 'name' HTML attribute of the <input> used for the username field (default: '_username')
            'username_parameter' => 'my_custom_username_field',

            // the 'name' HTML attribute of the <input> used for the password field (default: '_password')
            'password_parameter' => 'my_custom_password_field',

            // whether to enable or not the "forgot password?" link (default: false)
            'forgot_password_enabled' => true,

            // the path (i.e. a relative or absolute URL) to visit when clicking the "forgot password?" link (default: '#')
            'forgot_password_path' => $this->generateUrl('...', ['...' => '...']),

            // the label displayed for the "forgot password?" link (the |trans filter is applied to it)
            'forgot_password_label' => 'Forgot your password?',

            // whether to enable or not the "remember me" checkbox (default: false)
            'remember_me_enabled' => true,

            // remember me name form field (default: '_remember_me')
            'remember_me_parameter' => 'custom_remember_me_param',

            // whether to check by default the "remember me" checkbox (default: false)
            'remember_me_checked' => true,

            // the label displayed for the remember me checkbox (the |trans filter is applied to it)
            'remember_me_label' => 'Remember me',
        ]);
    }
}

内容页面模板

Twig 模板路径:@EasyAdmin/page/content.html.twig

它显示一个类似于索引/详情/表单页面的简单页面,带有主标题、侧边栏菜单和中央内容部分。唯一的区别是内容部分完全为空,因此它对于显示你自己的内容和自定义表单、在 EasyAdmin 中集成 Symfony 操作 等非常有用。示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{# templates/admin/my-custom-page.html.twig #}
{% extends '@EasyAdmin/page/content.html.twig' %}

{% block content_title %}The Title of the Page{% endblock %}
{% block page_actions %}
    <a class="btn btn-primary" href="...">Some Action</a>
{% endblock %}

{% block main %}
    <table class="datagrid">
        <thead>
            <tr>
                <td>Some Column</td>
                <td>Another Column</td>
            </tr>
        </thead>
        <tbody>
            {% for data in my_own_data %}
                <tr>
                    <td>{{ data.someColumn }}</td>
                    <td>{{ data.anotherColumn }}</td>
                </tr>
            {% endfor %}
        </tbody>
    </table>
{% endblock %}
本作品,包括代码示例,均根据 Creative Commons BY-SA 3.0 许可证获得许可。
目录
    版本