在 Yii2 中,已经实现了按需要加载页面元素,需要进一步实现仅加载页面元素的 css、js 等文件
1、现有的控制器方法实现如下
/** * Displays homepage. * * @return mixed * @throws NotFoundHttpException */ public function actionIndex() { $detect = new MobileDetect(); $deviceType = $detect->isMobile() ? PageElementType::DEVICE_TYPE_MOBILE : PageElementType::DEVICE_TYPE_PC; $page = Page::find()->where(['alias' => 'site-index', 'status' => Page::STATUS_NORMAL])->limit(1)->one(); if (!$page) { throw new NotFoundHttpException("页面未找到"); } $elements = PageElement::find() ->joinWith(['element_type']) ->with(['element_type.fields']) ->where([ 'page_elements.page_id' => $page->id, 'page_element_types.device_type' => $deviceType ])->orderBy('page_elements.order_number ASC, page_elements.id ASC')->asArray()->all(); $elementIds = array_map(fn($el) => $el['id'], $elements); $inputs = PageElementInput::find() ->where(['element_id' => $elementIds]) ->asArray() ->all(); // 构建以 element_id 和 field_id 为索引的值集合 $inputMap = []; foreach ($inputs as $input) { $inputMap[$input['element_id']][$input['field_id']] = $input; } $renderedHtml = ''; foreach ($elements as $element) { $type = $element['element_type']; if (!$type) { continue; } $data = []; $template = '_' . $element['element_type']['title']; $viewFile = '@frontend/views/page-elements/' . $template . '.php'; if ($element['data_source_type'] == PageElement::DATA_SOURCE_TYPE_RECOMMENDATION_SLOT) { // 推荐位类型,结构固定 $slot = RecommendationSlot::getSlotWithContentsByAlias($element['data_source_value']); if (file_exists(Yii::getAlias($viewFile))) { $renderedHtml .= $this->renderPartial("//page-elements/{$template}", [ 'slot' => $slot, 'webAlias' => Yii::getAlias('@web'), // 推荐位模板里用到了 $webAlias ]); } } else { // 普通类型(手动输入的字段) foreach ($element['element_type']['fields'] as $field) { $fieldId = $field['id']; $elementId = $element['id']; $value = isset($inputMap[$elementId][$fieldId]) ? $inputMap[$elementId][$fieldId]['value'] : null; $data[$field['name']] = isset($value) ? StringHelper::parseIfJson($value) : null; } if (file_exists(Yii::getAlias($viewFile))) { $renderedHtml .= $this->renderPartial("//page-elements/{$template}", [ 'data' => $data, 'webAlias' => Yii::getAlias('@web'), // 推荐位模板里用到了 $webAlias ]); } } } $this->layout = 'site'; // 设置 view 参数 $this->view->params['activeMenu'] = 'site'; return $this->render($detect->isMobile() ? 'index-mobile' : 'index', [ 'page' => $page, 'content' => $renderedHtml, ]); }
2、布局文件的代码如下
<?php /** @var \yii\web\View $this */ /** @var string $content */ use Detection\MobileDetect; use frontend\assets\CaseAsset; use frontend\assets\CaseMobileAsset; use frontend\assets\SiteAsset; use frontend\assets\SiteMobileAsset; use yii\bootstrap5\Html; $detect = new MobileDetect(); if ($detect->isMobile()) { SiteMobileAsset::register($this); } else { SiteAsset::register($this); } ?> <?php $this->beginPage() ?> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="<?= Yii::$app->charset ?>"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <?php $this->registerCsrfMetaTags() ?> <title><?= Html::encode($this->title) ?></title> <meta name="description" content=""> <meta name="keywords" content=""> <meta itemprop="name" content=""/> <meta itemprop="image" content="https://s1.kuaihuiwu.com/assets/img/hero-img.png"/> <link href="https://s1.kuaihuiwu.com/assets/img/favicon.png" rel="icon" type="image/png"> <?php $this->head() ?> </head> <body> <?php $this->beginBody() ?> <div id="casesPage"> <?= $this->render('_header') ?> <?= $content ?> <?= $this->render('_contact') ?> <?= $this->render('_footer') ?> </div> <img class="back-to-top" src="<?= Yii::getAlias('@web/static/images/to_top.png') ?>" alt=""> <?php $this->endBody() ?> </body> </html> <?php $this->endPage();
3、视图文件 frontend/views/site/index.php 的代码如下
<?php use common\models\UseCaseCategoryGroup; use yii\widgets\LinkPager; use yii\helpers\Html; use yii\helpers\Url; /** @var yii\web\View $this */ /** @var yii\data\ActiveDataProvider $dataProvider */ /** @var array $categoryIds */ $this->title = ''; ?> <?= $content ?>
4、页面元素的代码如下
frontend/views/page-elements/_industry-carousels.php
<?php /** * @var array $slot 传入的内容数据(结构同 $rs10) * @var string $webAlias Web别名前缀 */ use yii\helpers\Html; ?> <?php if (!empty($slot) && !empty($slot['contents'])): ?> <div id="swiper-module" class="swiper-module-container"> <div class="swiper swiper-module"> <div class="swiper-wrapper"> <?php foreach ($slot['contents'] as $index => $content): ?> <div class="swiper-slide"> <a href="<?= Html::encode($content['link_1'] ?? '') ?>"> <?php $coverImagePath = isset($content['cover_image']['path']) ? $webAlias . $content['cover_image']['path'] : $webAlias . '/upload/placeholder.jpg'; ?> <?= Html::img($coverImagePath, [ 'loading' => 'lazy', 'alt' => Html::encode($content['title']), ]) ?> </a> </div> <?php endforeach; ?> </div> <div class="swiper-pagination"></div> </div> <div class="swiper-button"> <div class="button-prev"> <img src="<?= $webAlias ?>/static/images/page-elements/left.png" alt=""> </div> <div class="button-next"> <img src="<?= $webAlias ?>/static/images/page-elements/right.png" alt=""> </div> </div> </div> <?php endif; ?>
frontend/views/page-elements/_separator.php
<?php use yii\helpers\Url; ?> <div class="separator-module" style="<?php if (!empty($data['bg_image']['path'])): ?> background: url(<?= $webAlias . $data['bg_image']['path'] ?>) no-repeat center center/100%, <?php endif; ?> <?php if ($data['bg_color_type'] == 1): ?><?= $data['bg_color'] ?>; <?php else: ?> <?php if ($data['gradient_type'] == 1): ?> linear-gradient(<?= $data['angle'] ?>deg, <?= $data['start_color'] ?>, <?= $data['end_color'] ?>); <?php else: ?> radial-gradient(<?= $data['start_color'] ?>, <?= $data['end_color'] ?>); <?php endif; ?> <?php endif; ?>"> <div class="separator-title" style="font-size: <?= $data['title_font_size'] ?>px; color: <?= $data['title_color'] ?>;font-weight: <?= $data['title_font_weight'] ?>;"><?= $data['title'] ?></div> <div class="separator-desc" style="font-size: <?= $data['description_color'] ?>px; color: <?= $data['description_font_size'] ?>;font-weight: <?= $data['description_font_weight'] ?>;"> <?= $data['description'] ?> </div> </div>
5、页面最终实现了自动加载页面元素的功能。基于页面编辑器中的实现。如图1
6、首页使用了动态页面元素(PageElement),但所有 CSS/JS 都集中注册在 SiteAsset 中,导致资源臃肿。这是性能的痛点,尤其在移动端或首屏加载中表现明显。需要实现 根据页面元素类型,按需加载其对应的 CSS 和 JS 文件,而不是一次性加载全部 。如图2
7、方案:每个 PageElementType 单独对应一个 AssetBundle,然后在控制器中动态注册。为每个页面元素类型建立单独的 AssetBundle
frontend/assets/IndustryCarouselsAsset.php
<?php namespace frontend\assets; use yii\web\AssetBundle; class IndustryCarouselsAsset extends AssetBundle { public $basePath = '@webroot'; public $baseUrl = '@web'; public $css = [ 'static/css/swiper.min.css', 'static/css/rs-swiper.css', ]; public $js = [ 'static/js/swiper.min.js', 'static/js/rs-swiper.js', ]; public $depends = [ ]; }
frontend/assets/SeparatorAsset.php
<?php namespace frontend\assets; use yii\web\AssetBundle; class SeparatorAsset extends AssetBundle { public $basePath = '@webroot'; public $baseUrl = '@web'; public $css = [ 'static/css/separator.css', ]; public $js = [ ]; public $depends = [ ]; }
8、在控制器中按需要加载
private function registerElementAsset(string $title): void { // 转换 dash-case → PascalCase $classBaseName = str_replace(' ', '', ucwords(str_replace('-', ' ', $title))); $assetClass = "frontend\\assets\\{$classBaseName}Asset"; // 如果类存在,则注册 if (class_exists($assetClass)) { $assetClass::register(Yii::$app->view); } }
然后在 actionIndex() 渲染元素的地方使用:
$this->registerElementAsset($type['title']);
9、移除 SiteAsset 中对所有页面组件 CSS/JS 的集中注册,只保留页面基础样式和通用逻辑:
<?php namespace frontend\assets; use yii\web\AssetBundle; /** * Main frontend application asset bundle. */ class SiteAsset extends AssetBundle { public $basePath = '@webroot'; public $baseUrl = '@web'; public $css = [ ]; public $js = [ 'https://s1.kuaihuiwu.com/js/fab.min.js' ]; public $depends = [ 'frontend\assets\AppAsset', ]; }
近期评论