You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

352 lines
12 KiB

<?php
/*
* The MIT License
*
* Copyright 2019 Blobt.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace iron\widgets;
use Yii;
use yii\base\Widget;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
use yii\helpers\Url;
/**
* 参照yii\widgets\Menu,根据AdminLTE样式从写的一个小物件
* @author Blobt
* @email 380255922@qq.com
* 使用例子
* <?php
* echo Menu::widget([
* 'items' => [
* ['label' => 'MAIN NAVIGATION', 'is_header' => true],
* ['label' => 'Documentation', 'url' => 'https:www.baidu.com', 'icon' => 'fa-book'],
* ['label' => 'Products', 'url' => ['product/index'], 'items' => [
* ['label' => 'New Arrivals', 'url' => ['product/index', 'tag' => 'new']],
* ['label' => 'Most Popular', 'url' => ['product/index', 'tag' => 'popular']],
* ]],
* ['label' => 'Login', 'url' => ['site/login'], 'visible' => Yii::$app->user->isGuest],
* ]
* ]);
* ?>
*
*
*/
class Menu extends Widget
{
/**
* @var array 菜单的item数组。
* 菜单的item同时也是一个数组,结构如下所述:
* lable: string, optional,指定选项的label
* encode:boolean, optional,label是否需要转义
* url: string or array, optional, 生产菜单的路径
* 产生结果和yii的Url::to()方法一致,并且会套用[[linkTemplate]]模板
* visible: boolean, optional, 选项是否可见,默认是true(可见)
* active: boolean or Closure, optional, 是否被选中
* 如果为true,则会在css中添加[[activeCssClass]]
* 当使用Closure时候,匿名函数必须是`function ($item, $hasActiveChild, $isItemActive, $widget)`
* 匿名函数必须返回true或者false
* 如果不是使用匿名函数,就会使用[[isItemActive]]来判断item是否被选中
* items: array, optional, 指定子菜单选项,格式和父菜单是一样的
* template: string, optional,选项的渲染模板
* submenuTemplate: string, optional, 子菜单渲染模板
* options: array, optional, 指定html属性
*/
public $items = [];
/**
* @var array html属性,这些指定的属性会加到所有的item中
*/
public $itemOptions = [];
/**
* @var string 链接的渲染模板
*/
public $linkTemplateWithSub = "<a href=\"{url}\" class=\"nav-link {class}\"><i class=\" nav-icon fas {icon}\"></i><p>{label}<i class=\"right fas fa-angle-left\"></i></p></a>";
public $linkTemplateNoSub = "<a href=\"{url}\" class=\"nav-link {class}\"><i class=\"far {icon} nav-icon\"></i><p>{label}</p></a>";
/**
* @var string label的渲染模板
*/
public $labelTemplate = '{label}';
/**
* @var string 子菜单的渲染模板
*/
public $submenuTemplate = "\n<ul class=\"nav nav-treeview\">\n{items}\n</ul>\n";
/**
* @var boolean label是否要进行html转义
*/
public $encodeLabels = true;
/**
* @var string 被选中的菜单的CSS类
*/
public $activeCssClass = 'active';
/**
* @var string 控制二级菜单开合
*/
public $menuOpenClass = 'menu-open';
/**
* @var boolean 是否根据路由去判断菜单项是否被选中
*/
public $activateItems = true;
/**
* @var boolean 当某一个子菜单的选中时候是否也关联选中父菜单
*/
public $activateParents = true;
/**
* @var boolean 如果item的url没有设置时,是否不显示该item
*/
public $hideEmptyItems = true;
/**
* @var array 菜单容器标签的属性
*/
public $options = [
'class' => 'nav nav-pills nav-sidebar flex-column',
'data-widget' => 'treeview',
'role' => 'menu',
'data-accordion' => 'false'
];
/**
* @var string 第一个菜单item的css类
*/
public $firstItemCssClass;
/**
* @var string 最后一个菜单item的css类
*/
public $lastItemCssClass;
/**
* @var string 路由 ,run的时候会自动获取当前路由
*/
public $route;
/**
* @var string $_GET参数
*/
public $params;
/**
* @var string 菜单项默认Icon
*/
public $defaultIcon = 'fa-circle';
/**
* 渲染菜单
*/
public function run()
{
if ($this->route === null && Yii::$app->controller !== null) {
$this->route = Yii::$app->controller->getRoute();
}
if ($this->params === null) {
$this->params = Yii::$app->request->getQueryParams();
}
$items = $this->normalizeItems($this->items, $hasActiveChild);
if (!empty($items)) {
$options = $this->options;
$tag = ArrayHelper::remove($options, 'tag', 'ul');
return Html::tag($tag, $this->renderItems($items), $options);
}
}
/**
* 渲染菜单
* @param array $items
* @return string 渲染结果
*/
protected function renderItems($items)
{
$lines = [];
$n = count($items);
foreach ($items as $i => $item) {
/* 获取菜单项的自定义属性 */
$options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', []));
$tag = ArrayHelper::remove($options, 'tag', 'li');
$class = ['nav-item'];
if (isset($item['active'])) {
$class[] = $this->activeCssClass;
if (isset($item['items'])) {
$class[] = $this->menuOpenClass;
}
}
if ($i === 0 && $this->firstItemCssClass !== null) {
$class[] = $this->firstItemCssClass;
}
if ($i === $n - 1 && $this->lastItemCssClass !== null) {
$class[] = $this->lastItemCssClass;
}
if (isset($item['items'])) {
$class[] = 'has-treeview';
}
if (isset($item['is_header']) && $item['is_header']) {
$class[] = "header";
}
Html::addCssClass($options, $class);
$menu = $this->renderItem($item);
if (!empty($item['items'])) {
$submenuTemplate = ArrayHelper::getValue($item, 'submenuTemplate', $this->submenuTemplate);
$menu .= strtr($submenuTemplate, [
'{items}' => $this->renderItems($item['items']),
]);
}
$lines[] = Html::tag($tag, $menu, $options);
}
return implode("\n", $lines);
}
/**
* 渲染菜单项
* @param array $item
* @return string 渲染结果
*/
protected function renderItem($item)
{
if (isset($item['url'])) {
if (isset($item['template'])) {
$template = $item['template'];
} else {
$template = isset($item['items']) ? $this->linkTemplateWithSub : $this->linkTemplateNoSub;
}
return strtr($template, [
'{url}' => Html::encode(Url::to($item['url'])),
'{label}' => Html::encode($item['label']),
'{class}' => isset($item['active']) ? 'active' : '',
'{icon}' => Html::encode($item['icon'])
]);
}
$template = ArrayHelper::getValue($item, 'template', $this->labelTemplate);
return strtr($template, [
'{label}' => $item['label'],
'{class}' => isset($item['active']) ? 'active' : ''
]);
}
/**
* 判断菜单项是否被选中
* @param $item array
* @return boolean $item
*/
protected function isItemActive($item)
{
if (isset($item['url']) && is_array($item['url']) && isset($item['url'][0])) {
$route = Yii::getAlias($item['url'][0]);
if ($route[0] !== '/' && Yii::$app->controller) {
$route = Yii::$app->controller->module->getUniqueId() . '/' . $route;
}
$route = ltrim($route, '/');
if ($route != substr($this->route, 0, strrpos($this->route, '/')) && $route != $this->route &&
ltrim(Yii::$app->request->url, '/') !== $route) {
return false;
}
unset($item['url']['#']);
if (count($item['url']) > 1) {
foreach (array_splice($item['url'], 1) as $name => $value) {
if ($value !== null && (!isset($this->params[$name]) || $this->params[$name] != $value)) {
return false;
}
}
}
return true;
}
return false;
}
/**
* 格式化菜单item
* @param string $item
* @param bool $active
*/
protected function normalizeItems($items, &$active)
{
foreach ($items as $i => $item) {
/* 去除visible 为 false的item */
if (isset($item['visible']) && !$item['visible']) {
unset($items[$i]);
continue;
}
/* 添加默认icon */
if (!isset($item['icon'])) {
if (!empty($this->defaultIcon)) {
$items[$i]['icon'] = $this->defaultIcon;
}
}
/* 添加label */
if (!isset($item['label'])) {
$item['label'] = '';
}
/* 转义HTML */
$encodeLabel = isset($item['encode']) ? $item['encode'] : $this->encodeLabels;
if ($encodeLabel) {
$items[$i]['label'] = Html::encode($item['label']);
}
/* 格式化子菜单 item */
$hasActiveChild = false;
if (isset($item['items'])) {
$items[$i]['items'] = $this->normalizeItems($item['items'], $hasActiveChild);
if (empty($items[$i]['items']) && $this->hideEmptyItems) {
unset($items[$i]['items']);
}
}
/* 处理菜单是否被选中 */
if (!isset($item['active'])) {
if ($this->activateParents && $hasActiveChild || $this->activateItems && $this->isItemActive($item)) {
$active = $items[$i]['active'] = true;
}
} elseif ($item['active'] instanceof Closure) {
$active = $items[$i]['active'] = call_user_func($item['active'], $item, $hasActiveChild, $this->isItemActive($item), $this);
} elseif ($item['active']) {
$active = true;
}
}
return array_values($items);
}
}