YIMP-Bootstrap 4上Yii 2的控制面板

我确信,很多喜欢框架而不是现成CMS的开发人员都有关于Bootstrap或其类似物的解决方案,该解决方案用于创建管理界面和其他后台界面。 我有。 它已经成功运行了很多年,但是已经过时了。 现在该重写了。


在开发新版本时,我尝试总结了我在该主题上的所有经验,结果得到了YIMP ,这是我不为共享的自行车: GitHubLiveDemoAPI Documentation


YIMP非常简单。 但是,在这种简单性背后还有一个漫长的想法,我也想分享一下。 因此,本文并非说明。 当然,在这里我们讨论架构,依赖管理,MVC范例和用户界面。


因此,YIMP是一个仪表板。 这不是现成的管理面板,也不是CMS甚至CMF。 表示代码需要独立编写或使用Gii(附带模板)。 YIMP提供了一个布局,该布局定义了控件应位于的位置以及应用程序通过其将数据传输到布局的接口。 这是在台式机上的外观:



布局自适应。 随着屏幕缩小,元素开始消失或在导航栏中的按钮上移动。 结果电话上的同一页面如下所示:


最好让它处于扰流板之下

我们在布局中看到了什么? 应用程序标题,面包屑,三个菜单(左,右和顶部),侧边栏中的小部件,页面标题。 在我的实践中,这组元素足以开发任何界面-从登录页面的管理页面到公司信息系统。 我试图安排它们,以便尽可能有效地利用空间。 你说什么


标记是用纯Bootstrap编写的,没有扩展和自定义。 尽可能使用Bootstrap中的类,因此,如果您决定使用自定义,那么应该没有问题。


如我所说,YIMP包含一个接口,应用程序通过该接口将数据传输到布局。 让我们看看这是怎么发生的。 打开引擎盖!


布局图


我认为开发人员应该完全控制布局,因此在安装YIMP时,我建议将其代码复制到他的应用程序中。 我认为,这比在程序包中保留布局并阻止一堆设置要好得多。 让我们看一下布局代码:


77行代码。 不必去研究!
<?php use dmitrybtn\yimp\widgets\Alert; use dmitrybtn\yimp\Yimp; use yii\bootstrap4\Html; $yimp = new Yimp(); $yimp->register($this); /** @var string $content Content came from view */ ?> <?php $this->beginPage() ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="<?= Yii::$app->charset ?>"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <?php echo Html::csrfMetaTags() ?> <title><?php echo Html::encode($yimp->nav->getTitle()) ?></title> <?php $this->head() ?> </head> <body> <?php $this->beginBody() ?> <?php echo $yimp->navbar() ?> <?php echo $yimp->beginSidebars() ?> <?php echo $yimp->beginLeftSidebar() ?> <?php echo $yimp->beginLeftSidebarMenu() ?> <?php echo $yimp->menuLeft([ 'options' => ['class' => 'nav-pills flex-column border rounded py-2'] ]) ?> <?php echo $yimp->endLeftSidebarMenu() ?> <?php if (isset($this->blocks[$yimp::SIDEBAR_LEFT])): ?> <?php echo $this->blocks[$yimp::SIDEBAR_LEFT] ?> <?php endif ?> <?php echo $yimp->endLeftSidebar() ?> <?php echo $yimp->beginRightSidebar() ?> <?php echo $yimp->beginRightSidebarMenu() ?> <?php echo $yimp->menuRight([ 'options' => ['class' => 'nav-pills flex-column border rounded py-2'] ]) ?> <?php echo $yimp->endRightSidebarMenu() ?> <?php if (isset($this->blocks[$yimp::SIDEBAR_RIGHT])): ?> <?php echo $this->blocks[$yimp::SIDEBAR_RIGHT] ?> <?php endif ?> <?php echo $yimp->endRightSidebar() ?> <?php echo $yimp->endSidebars() ?> <?php echo $yimp->beginContent() ?> <?php echo $yimp->headerDesktop() ?> <?php echo Alert::widget() ?> <?php echo $content ?> <?php echo $yimp->endContent() ?> <?php if (isset($this->blocks[$yimp::FOOTER])): ?> <?php echo $this->blocks[$yimp::FOOTER] ?> <?php endif ?> <?php $this->endBody() ?> </body> </html> <?php $this->endPage() ?> 

如您所见,所有YIMP标记都包装在方法中。 这些方法大多数都只打印从此数组获取的行。 是的, KISS原则就是我们的一切。


请注意,布局中使用了块。 需要它们来显示在视图中定义的窗口小部件(这就是呈现表单控件的方式)。 好吧,如果该小部件应该挂在所有页面上,则最好直接在布局中确定它。


因此,主要区域和小部件在视图中定义。 标题,菜单和面包屑在哪里确定? 我认为,最好在控制器中定义它们。 这一点很重要,因为这样的决定与MVC范式相矛盾。 让我们更详细地研究这个问题。


控制器


因此,让一个ProfileController可以显示有关当前用户的个人资料的信息并更改密码。 从逻辑上讲, profile/view操作将称为“我的配置文件”。 在主菜单中应该有一个“我的个人资料”项目也是合乎逻辑的。 最后,“我的个人资料”应该位于面包屑中:“首页/我的个人资料/更改密码”。 我认为用“我的资料”一词定义一个常量的愿望是很合理的。 您无法在视图中执行此操作。 为标题选择单独的图层很麻烦。 像这样的推理,我来到了控制器。


下一步是不仅要定义控制器中的动作标题,还要定义面包屑和菜单。 并这样做,以便YIMP可以读取它们。 这里我们需要一个例子。 让我们看一下ProfileController类的可能实现。


52行简单代码。 最好仔细看!
 class ProfileController extends \yii\web\Controller { public $nav; public function init() { parent::init(); $this->nav = new \dmitrybtn\yimp\Navigator; } public static function titleView() { return ' '; } public static function titlePassword() { return ' '; } public static function crumbsToView() { return [ ['label' => static::titleView(), 'url' => ['/profile/view']] ]; } public function actionView() { $this->nav->title = static::titleView(); $this->nav->menuRight = [ ['label' => ''], ['label' => static::titlePassword(), ['password']], ]; ... return $this->render('view'); } public function actionPassword() { $this->nav->title = static::titlePassword(); $this->nav->crumbs = static::crumbsToView(); ... return $this->render('password'); } } 

标头和面包屑是使用静态方法定义的,这意味着它们可以在任何地方使用。 例如,在应用程序的主菜单中,您可以编写:


 ['label' => ProfileController::titleView(), 'url' => ['/profile/view']], 

为什么要使用这些方法? 因为明天将询问您而不是单词“我的个人资料”,以显示当前用户的登录名。


与面包屑一样。 假设我们的用户有一个由ImageController负责的图片列表。 然后在image/create动作中可以编写:


  $this->nav->crumbs = ProfileController::crumbsToView(), 

并获得面包屑,例如“首页/我的个人资料/添加图片”。 顺便说一句,由于image/create操作被称为“添加图片”,因此需要更正profile/view操作菜单:


  $this->nav->menuRight = [ ['label' => ''], ['label' => static::titlePassword(), ['password']], ['label' => ImageController::titleCreate(), 'url' => ['/image/create']] ]; 

我认为这个想法是可以理解的。 我认为,这是一种简单有效的解决方案,您可以摆脱MVC范式。 是的,控制器代码越来越大,但是在控制器中有一定的位置-我们不在那儿编写业务逻辑,对吗? 是的,我很想知道您对此事的看法。


我们走得更远。 您可能已经猜到了,定义为\dmitrybtn\yimp\Navigatornav属性用于将标题,菜单和面包屑从控制器传输到布局。 这是YIMP的另一个功能。



设置如何进入布局? 很简单 在初始化期间,YIMP检查当前控制器的nav属性。 如果此属性是可读的并且是导航器( instanceof \dmitrybtn\yimp\Navigator ),则将其用于显示相应的信息。 导航器包含与布局元素相对应的属性,在API文档中更容易看到其完整列表。


在应用程序中,建议创建自己的导航器并在其中定义不依赖于当前操作(顶部和左侧)的菜单。 之后,在所有控制器中,您需要创建nav属性并将其定义为导航器(可以使用双手,可以继承或可以使用trait)。 如有必要,您可以定义几个导航器。


这种方法消除了YIMP与控制器之间的直接关系。 所有交互都是通过一个对象执行的,该对象的实现由开发人员控制。 也就是说,这与SOLID的依赖转换原理或GRASP的低耦合原理相同。


从理论上讲,对于控制器和导航器都使用接口是正确的。 但是在这里,我决定采用最简单的方法,而不要弄乱系统。 最后,DIP不讨论接口,而是讨论抽象。 在这种情况下,抽象是关于特定类中某种类型的属性的存在的协议。 你觉得呢


当系统中出现对YIMP一无所知的模块时,YIMP与控制器之间就没有直接关系就变得很重要。 反之亦然-使用YIMP编写的模块安装在不使用YIMP的系统中。


在第一种情况下,YIMP将不会在控制器中看到nav属性。 不会有错误,但是您的菜单将从屏幕上消失,并且操作ID将用作标题。 如何成为 非常简单-如果YIMP无法从控制器获取导航器,它将使用别名yimp-nav通过DI容器创建yimp-nav 。 使用此别名,您可以注册自己的默认导航器,例如通过在应用程序设置中指定:


  'container' => [ 'definitions' => [ 'yimp-nav' => [ 'class' => '\your\own\Navigator', ] ] ], 

在第二种情况下,控制器中的导航器将是,但是没有人可以读取它。 在这种情况下,建议为模块中的视图编写包装器,以使导航器从当前控制器适应Yii接受的格式。 也就是说,在主区域中显示<h1><title>和面包屑通过参数Yii::$app->view ,并以按钮的形式显示右键菜单。


结论


现在发布的YIMP没有版本。 我看不出发布预发行版本的任何理由-一切都太简单了。 我认为最好在一个真实的项目上测试几个星期,然后立即切换到1.0.0版。 因此,非常欢迎对GitHub提出批评,评论和帮助。


我正在完成一个实现访问控制的模块。 如何完成-我会写。


如您所见,这里没有什么复杂的。 我相信你们中的许多人都有类似的库存。 我很想知道您如何解决您在管理区域中的用户界面任务。


谢谢大家!

Source: https://habr.com/ru/post/zh-CN466505/


All Articles