Магазин на Yii2, часть 34. Показываем меню страниц в публичной части
22.09.2019
Теги: Web-разработка • Yii2 • Иерархия • ИнтернетМагазин • КаталогТоваров • Практика • СтраницаСайта • Удалить • Фреймворк
Список всех страниц сайта
Сейчас для показа всех страниц в панели управления используется класс ActiveDataProvider
и виджет GridView
. Нам это не подходит, потому что страницы надо показывать с учетом иерархии. По аналогии с категориями каталога изменим метод контроллера actionIndex()
и view-шаблон index.php
.
class PageController extends AdminController { /*...*/ public function actionIndex() { return $this->render( 'index', ['pages' => Page::getTree()] ); } /*...*/ }
class Page extends ActiveRecord { /*...*/ public static function getTree($parent = 0) { $children = self::find() ->where(['parent_id' => $parent]) ->asArray() ->all(); $result = []; foreach ($children as $page) { if ($parent) { $page['name'] = '— ' . $page['name']; } $result[] = $page; $result = array_merge( $result, self::getTree($page['id']) ); } return $result; } /*...*/ }
<?php /* * Страница списка всех страниц, файл modules/admin/views/page/index.php */ use yii\helpers\Html; use yii\grid\GridView; /* @var $this yii\web\View */ /* @var $dataProvider yii\data\ActiveDataProvider */ $this->title = 'Все страницы'; ?> <h1><?= Html::encode($this->title); ?></h1> <p> <?= Html::a('Добавить страницу', ['create'], ['class' => 'btn btn-success']); ?> </p> <table class="table table-striped table-bordered"> <thead> <tr> <th>Наименование</th> <th>Мета-тег keywords</th> <th>Мета-тег description</th> <th><span class="glyphicon glyphicon-eye-open"></span></th> <th><span class="glyphicon glyphicon-pencil"></span></th> <th><span class="glyphicon glyphicon-trash"></span></th> </tr> </thead> <tbody> <?php foreach ($pages as $page): ?> <tr> <td><?= $page['name']; ?></td> <td><?= $page['keywords']; ?></td> <td><?= $page['description']; ?></td> <td> <?= Html::a( '<span class="glyphicon glyphicon-eye-open"></span>', ['/admin/page/view', 'id' => $page['id']] ); ?> </td> <td> <?= Html::a( '<span class="glyphicon glyphicon-pencil"></span>', ['/admin/page/update', 'id' => $page['id']] ); ?> </td> <td> <?= Html::a( '<span class="glyphicon glyphicon-trash"></span>', ['/admin/page/delete', 'id' => $page['id']], [ 'data-confirm'=> 'Вы уверены, что хотите удалить эту страницу?', 'data-method'=> 'post' ] ); ?> </td> </tr> <?php endforeach; ?> </tbody> </table>
Проверка перед удалением страницы
Перед удалением страницы необходимо проверить, что у нее нет дочерних страниц. Поэтому добавим в модель метод beforeDelete()
:
class Page extends ActiveRecord { /*...*/ public function beforeDelete() { $children = self::find()->where(['parent_id' => $this->id])->all(); if (!empty($children)) { Yii::$app->session->setFlash( 'warning', 'Нельзя удалить страницу, которая имеет дочерние стрницы' ); return false; } return parent::beforeDelete(); } /*...*/ }
Показываем меню в публичной части
Для этого надо получить список всех страниц и передать эти данные в layout-шаблон. Добавим в класс AppController
переменную $pageMenu
и метод beforeAction()
для решения этой задачи:
class AppController extends Controller { /*...*/ public $pageMenu; /*...*/ public function beforeAction($action) { $this->pageMenu = Page::getTree(); return parent::beforeAction($action); } /*...*/ }
Еще нам потребуется модель для страниц:
<?php namespace app\models; use Yii; use yii\db\ActiveRecord; class Page extends ActiveRecord { /** * Метод возвращает имя таблицы БД */ public static function tableName() { return 'page'; } /** * Метод возвращает все страницы в виде дерева */ public static function getTree() { // пробуем извлечь данные из кеша $data = Yii::$app->cache->get('page-menu'); if ($data === false) { // данных нет в кеше, получаем их заново $pages = Page::find() ->select(['id', 'name', 'slug', 'parent_id']) ->indexBy('id') ->asArray() ->all(); $data = self::makeTree($pages); // сохраняем полученные данные в кеше Yii::$app->cache->set('page-menu', $data, 60); } return $data; } /** * Принимает на вход линейный массив элеменов, связанных отношениями * parent-child, и возвращает массив в виде дерева */ protected static function makeTree($data = []) { if (count($data) == 0) { return []; } $tree = []; foreach ($data as $id => &$node) { if ($node['parent_id'] == 0) { $tree[$id] = &$node; } else { $data[$node['parent_id']]['childs'][$id] = &$node; } } return $tree; } }
И изменяем layout-шаблон:
<?php /* @var $this \yii\web\View */ /* @var $content string */ use yii\helpers\Html; use yii\helpers\Url; use app\assets\AppAsset; use yii\bootstrap\Modal; AppAsset::register($this); ?> <?php $this->beginPage(); ?> <!DOCTYPE html> <html lang="<?= Yii::$app->language; ?>"> <head> <meta charset="<?= Yii::$app->charset; ?>"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <?php $this->registerCsrfMetaTags(); ?> <title><?= Html::encode($this->title); ?></title> <?php $this->head(); ?> </head> <body> <?php $this->beginBody(); ?> <header> <div class="header-top"> <div class="container"> <div class="row"> <div class="col-sm-6"> <ul class="nav nav-pills"> <li><a href="#"><i class="fa fa-phone"></i> +2 95 01 88 821</a></li> <li><a href="#"><i class="fa fa-envelope"></i> info@domain.com</a></li> </ul> </div> <div class="col-sm-6"> <ul class="nav nav-pills pull-right"> <li><a href="#"><i class="fa fa-facebook"></i></a></li> <li><a href="#"><i class="fa fa-twitter"></i></a></li> <li><a href="#"><i class="fa fa-linkedin"></i></a></li> <li><a href="#"><i class="fa fa-dribbble"></i></a></li> <li><a href="#"><i class="fa fa-google-plus"></i></a></li> </ul> </div> </div> </div> </div> <div class="header-middle"> <div class="container"> <div class="row"> <div class="col-sm-4"> <div class="pull-left"> <a href="<?= Url::home(); ?>"> <?= Html::img( '@web/images/home/logo.png', ['alt' => Yii::$app->params['shopName']] ); ?> </a> </div> </div> <div class="col-sm-8"> <ul class="pull-right"> <li><i class="fa fa-user"></i> <a href="#">Аккаунт</a></li> <li><i class="fa fa-star"></i> <a href="#">Избранное</a></li> <li><i class="fa fa-crosshairs"></i> <a href="#">Оформить</a></li> <li> <i class="fa fa-shopping-cart"></i> <a href="<?= Url::to(['basket/index']); ?>">Корзина</a> </li> <li><i class="fa fa-lock"></i> <a href="#">Войти</a></li> </ul> </div> </div> </div> </div> <div class="header-bottom"> <div class="container"> <div class="row"> <div class="col-sm-8"> <div id="menu"> <ul> <li> <a href="<?= Url::to(['catalog/index']); ?>"> Каталог </a> </li> <?php foreach ($this->context->pageMenu as $page): ?> <li> <a href="<?= Url::to(['page/view', 'slug' => $page['slug']]); ?>"> <?= $page['name']; ?> </a> <?php if (isset($page['childs'])): ?> <ul> <?php foreach ($page['childs'] as $child): ?> <li> <a href="<?= Url::to(['page/view', 'slug' => $child['slug']]); ?>"> <?= $child['name']; ?> </a> </li> <?php endforeach; ?> </ul> <?php endif; ?> </li> <?php endforeach; ?> </ul> </div> </div> <div class="col-sm-4"> <form method="post" action="<?= Url::to(['catalog/search']); ?>" class="pull-right"> <?= Html::hiddenInput( Yii::$app->request->csrfParam, Yii::$app->request->csrfToken ); ?> <div class="input-group"> <input type="text" name="query" class="form-control" placeholder="Поиск по каталогу"> <div class="input-group-btn"> <button class="btn btn-default" type="submit"> <span class="glyphicon glyphicon-search"></span> </button> </div> </div> </form> </div> </div> </div> </div> </header> <?= $content ?> <footer> <div class="container"> Copyright © 2018 E-SHOPPER Inc. All rights reserved. </div> </footer> <?php $checkout = Url::to(['order/checkout']); $footer = <<<FOOTER <button type="button" class="btn btn-default" data-dismiss="modal"> Продолжить покупки </button> <a href="$checkout" class="btn btn-warning"> Оформить заказ </a> FOOTER; Modal::begin([ 'header' => '<h2>Корзина</h2>', 'id' => 'basket-modal', 'size'=>'modal-lg', 'footer' => $footer ]); Modal::end(); unset($checkout, $footer); ?> <?php $this->endBody(); ?> </body> </html> <?php $this->endPage(); ?>
Показываем страницу сайта
Для начала изменим настройки компонента UrlManager
:
/*...*/ $config = [ /*...*/ 'components' => [ /*...*/ 'urlManager' => [ 'enablePrettyUrl' => true, 'showScriptName' => false, 'rules' => [ // раздел каталога: 2, 3, 4 страница списка товаров 'catalog/category/<id:\d+>/page/<page:\d+>' => 'catalog/category', // раздел каталога: первая страница списка товаров 'catalog/category/<id:\d+>' => 'catalog/category', // бренд каталога: 2, 3, 4 страница списка товаров 'catalog/brand/<id:\d+>/page/<page:\d+>' => 'catalog/brand', // бренд каталога: первая страница списка товаров 'catalog/brand/<id:\d+>' => 'catalog/brand', // страница отдельного товара каталога 'catalog/product/<id:\d+>' => 'catalog/product', // правило для 2, 3, 4 страницы результатов поиска 'catalog/search/query/<query:.*?>/page/<page:\d+>' => 'catalog/search', // правило для первой страницы результатов поиска 'catalog/search/query/<query:.*?>' => 'catalog/search', // правило для первой страницы с пустым запросом 'catalog/search' => 'catalog/search', // страница сайта '/page/<slug:[-_0-9a-zA-Z]+>/' => 'page/view' ], ], /*...*/ ], /*...*/ ]; /*...*/
Добавим метод actionView()
в контроллер — он будет отвечать за показ страницы:
class PageController extends AppController { /* * Главная страница сайта */ public function actionIndex() { /*...*/ } /* * Произвольная страница сайта */ public function actionView($slug) { if ($page = Page::find()->where(['slug' => $slug])->one()) { $this->setMetaTags( $page->name, $page->keywords, $page->description ); return $this->render( 'view', ['page' => $page] ); } throw new NotFoundHttpException('Запрошенная страница не найдена'); } }
И последнее — создадим view-шаблон для показа отдельной страницы сайта:
<?php /* * Произвольная страница сайта, файл views/page/view.php */ use app\components\TreeWidget; use app\components\BrandsWidget; ?> <section> <div class="container"> <div class="row"> <div class="col-sm-3"> <h2>Каталог</h2> <div class="category-products"> <?= TreeWidget::widget(); ?> </div> <h2>Бренды</h2> <div class="brand-products"> <?= BrandsWidget::widget(); ?> </div> </div> <div class="col-sm-9"> <h1><?= $page['name']; ?></h1> <?= $page['content']; ?> </div> </div> </div> </section>
- Магазин на Yii2, часть 32. Админка: удаление категорий и CRUD для страниц
- Магазин на Yii2, часть 33. Админка: приводим в порядок CRUD-код для страниц
- Магазин на Yii2, часть 28. Админка: выбор родителя и список всех категорий
- Магазин на Yii2, часть 35. Админка: загрузка картинок для страниц и страница 404
- Магазин на Yii2, часть 31. Админка: загрузка изображений для категорий и брендов
- Магазин на Yii2, часть 30. Админка: WYSIWYG-редактор и изображение для товара
- Магазин на Yii2, часть 29. Админка: добавляем список товаров категории
Поиск: Web-разработка • Yii2 • Иерархия • Интернет магазин • Каталог товаров • Страница сайта • Удалить • Фреймворк