使用 Layui 替换 Yii 基础模板的默认 Bootstrap 样式并尝试重写导航栏组件

Yii 2 框架本身与 Bootstrap 紧密集成,许多内置小部件(Widgets)都是基于 Bootstrap 5 设计的。

接下来我们介绍如何使用 Layui 代替掉 Bootstrap。

Yii 2 的前端资源(如 CSS、JavaScript、图片、字体等)由 Asset Manager 组件管理,禁用 Bootstrap 和导入 Layui 都需要通过 Asset Manager 进行操作。

禁用 Bootstrap

找到 /config/web.php,在 components 中添加 assetManager 组件配置

php 复制代码
'components' => [
    'assetManager' => [
        'bundles' => [
            'yii\bootstrap5\BootstrapAsset' => false,
            'yii\bootstrap5\BootstrapPluginAsset' => false,
        ]
    ],
    // 其它组件配置项
]

导入 Layui

使用 npm 安装包时默认会将新包安装到根目录的 node_modules 文件夹中,安装前需要修改 /config/web.php 文件的配置中 aliases 下的 @npm 将其指向 node_modules

php 复制代码
'aliases' => [
    ...
    // 使用于 LayuiAsset -> sourcePath
    '@npm'   => '@app/node_modules',
],

安装 Layui

shell 复制代码
npm i layui

新建 LayuiAsset 类

php 复制代码
// /assets/LayuiAsset.php
namespace app\assets;

use yii\web\AssetBundle;
use yii\web\View;

class LayuiAsset extends AssetBundle
{
    public $sourcePath = '@npm/layui/dist';

    public $css = [
        'css/layui.css',
    ];

    public $js = [
        'layui.js',
    ];

    public $jsOptions = [
        'position' => View::POS_HEAD,
        // 将 layer 注册为全局对象
        'onload' => 'layui.use(["layer"], function(){ window.layer = layui.layer;});',
    ];
}

修改依赖

php 复制代码
// /assets/AppAsset.php
class AppAsset extends AssetBundle
{
    ...
    public $depends = [
        'yii\web\YiiAsset',
        // 'yii\bootstrap5\BootstrapAsset'
        'app\assets\LayuiAsset',
    ];
}

至此已可以使用 Layui 所有组件进行页面开发。

下面尝试使用 Layui 风格封装一个 Navbar 小组件

Layui 风格的导航栏

Yii 2 的所有小组件继承自 yii\base\Widget,以下为抄袭 yii\bootstrap5\Nav 小组件封装的一个 Layui 风格的导航栏

php 复制代码
namespace app\widgets\layui;

use app\assets\LayuiAsset;
use yii\base\InvalidConfigException;
use yii\base\Widget;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
use Yii;

class Navbar extends Widget
{
    public array $items = [];
    public string $theme = '';
    public bool $encodeLabels = true;
    public bool $activateItems = true;
    public bool $activateParents = true;

    public array $options = ['class' => 'layui-nav'];

    public $route = null;
    public $params = null;

    public function init()
    {
        parent::init();
        if ($this->route === null && Yii::$app->controller !== null) {
            $this->route = Yii::$app->controller->getRoute();
        }
        if ($this->params === null) {
            $this->params = Yii::$app->request->getQueryParams();
        }
    }

    public function run(): string
    {
        LayuiAsset::register($this->getView());

        return $this->renderItems();
    }

    public function renderItems(): string
    {
        $items = [];
        foreach ($this->items as $item) {
            if (isset($item['visible']) && !$item['visible']) {
                continue;
            }
            // 一级菜单
            $items[] = $this->renderItem($item);
        }
        // 主题设置
        if ($this->theme) {
            $themeClass = $this->getThemeClass($this->theme);
            Html::addCssClass($this->options, ['class' => $themeClass]);
        }
        return Html::tag('ul', implode("\n", $items), $this->options);
    }

    public function renderItem($item)
    {
        if (is_string($item)) {
            return $item;
        }
        if (!isset($item['label'])) {
            throw new InvalidConfigException("The 'label' option is required.");
        }
        $encodeLabel = $item['encode'] ?? $this->encodeLabels;
        $label = $encodeLabel ? Html::encode($item['label']) : $item['label'];
        $items = ArrayHelper::getValue($item, 'items', '');  // 下拉选项
        $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []);
        $url = ArrayHelper::getValue($item, 'url', '#');
        $active = $this->isItemActive($item);

        $options = ['class' => 'layui-nav-item'];
        if (is_array($items)) {
            $url = 'javascript:void(0)';
            $items = $this->isChildActive($items, $active);
            $items = $this->renderDropdown($items, $item);
        }
        // 开启了菜单高亮则添加高亮类
        if ($this->activateItems && $active) {
            Html::addCssClass($options, ['class' => 'layui-this']);
        }

        return Html::tag('li', Html::a($label, $url, $linkOptions) . $items, $options);
    }

    protected function renderDropdown($items, $item): string
    {
        if (is_string($item)) {
            return $item;
        }
        $options = ['class' => 'layui-nav-child'];
        $content = '';
        foreach ($items as $item) {
            $itemOptions = $this->isItemActive($item) ? ['class' => 'layui-this'] : [];
            if (is_string($item)) {
                $content .= Html::tag('dd', $item);
            } else {
                $encodeLabel = $item['encode'] ?? $this->encodeLabels;
                $label = $encodeLabel ? Html::encode($item['label']) : $item['label'];
                $url = ArrayHelper::getValue($item, 'url', '#');
                $content .= Html::tag('dd', Html::a($label, $url), $itemOptions);
            }
        }
        return Html::tag('dl', $content, $options);
    }

    protected function isItemActive($item): bool
    {
        if (!$this->activateItems) {
            return false;
        }
        if (isset($item['active'])) {
            return (bool)ArrayHelper::getValue($item, 'active', false);
        }
        if (isset($item['url'][0]) && is_array($item['url'])) {
            $route = $item['url'][0];
            if ($route[0] !== '/' && Yii::$app->controller) {
                $route = Yii::$app->controller->module->getUniqueId() . '/' . $route;
            }
            if (ltrim($route, '/') !== $this->route) {
                return false;
            }
            unset($item['url']['#']);
            if (count($item['url']) > 1) {
                $params = $item['url'];
                unset($params[0]);
                foreach ($params as $name => $value) {
                    if ($value !== null && (!isset($this->params[$name]) || $this->params[$name] != $value)) {
                        return false;
                    }
                }
            }

            return true;
        }
        return false;
    }

    protected function isChildActive(array $items, bool &$active): array
    {
        foreach ($items as $i => $child) {
            if (is_array($child) && !ArrayHelper::getValue($child, 'visible', true)) {
                continue;
            }
            if (is_array($child) && $this->isItemActive($child)) {
                ArrayHelper::setValue($items[$i], 'active', true);
                if ($this->activateParents) {
                    $active = true;
                }
            }
            $childItems = ArrayHelper::getValue($child, 'items');
            if (is_array($childItems)) {
                $activeParent = false;
                $items[$i]['items'] = $this->isChildActive($childItems, $activeParent);
                if ($activeParent) {
                    Html::addCssClass($items[$i]['options'], ['class' => 'layui-this']);
                    $active = true;
                }
            }
        }

        return $items;
    }

    private function getThemeClass($theme)
    {
        if (in_array($theme, ['gray','cyan','green','blue'])) {
            return 'layui-bg-' . $theme;
        }
        return '';
    }
}

使用方法

php 复制代码
<?= \app\widgets\layui\Navbar::widget([
        'items' => [
            ['label' => 'Home', 'url' => ['/site/index']],
            ['label' => 'About', 'url' => ['/site/about']],
            ['label' => 'Contact', 'url' => ['/site/contact']],
            Yii::$app->user->isGuest
            ? ['label' => 'Login', 'url' => ['/site/login']]
            : ['label' => 'Logout (' . Yii::$app->user->identity->username . ')', 'url' => ['/site/logout'], 'linkOptions' => ['data-method' => 'post']],
            ['label' => 'Setting', 'items' => [
                ['label' => 'System1', 'url' => ['/site/system1']],
                ['label' => 'System2', 'url' => ['/site/system2']],
                ['label' => 'System3', 'url' => ['/site/system3']],
            ]]
        ]
]) ?>

效果不错呢,嘻嘻。

相关推荐
程序员 Andy18 分钟前
项目中为什么使用SpringBoot?
java·spring boot·后端
bobz9657 小时前
5070 Ti CodeLlama 7B > Mistral 7B > Qwen3 8B
后端
麦兜*8 小时前
Spring Boot 集成 Docker 构建与发版完整指南
java·spring boot·后端·spring·docker·系统架构·springcloud
程序视点8 小时前
2025最佳图片无损放大工具推荐:realesrgan-gui评测与下载指南
前端·后端
fured9 小时前
[调试][实现][原理]用Golang实现建议断点调试器
开发语言·后端·golang
bobz96510 小时前
linux cpu CFS 调度器有使用 令牌桶么?
后端
bobz96510 小时前
linux CGROUP CPU 限制有使用令牌桶么?
后端
David爱编程11 小时前
多核 CPU 下的缓存一致性问题:隐藏的性能陷阱与解决方案
java·后端
追逐时光者11 小时前
一款基于 .NET 开源、功能全面的微信小程序商城系统
后端·.net
绝无仅有12 小时前
Go 并发同步原语:sync.Mutex、sync.RWMutex 和 sync.Once
后端·面试·github