Начинаем разработку VespShop

Ну что, друзья! Начинается самое интересное - реальная разработка нашего магазина.
Сегодня напишем базовые миграции, модели и контроллеры админки. А потом создадим и страницы для работы со всем этим.
Перед начал разработки я провёл генеральную уборку кода пакета vesp/vesp и добавил новые команды в composer, так что советую пересоздать проект заново, базу данных и настройки .env можно не трогать, только обновить исходники.
По ходу дела я постараюсь прикинуть, сколько времени у меня уходит на те или иные операции.
Переходим в новый проект и запускаем composer db:create Products - это создаст новую миграцию в файле core/db/migrations/меткавремени-products.php.
Мы не ограничены количеством миграций, поэтому сегодня будет минимальный набор колонок в таблицах, а дальше будем добавлять новые по необходимости:
<?php
use Illuminate\Database\Schema\Blueprint;
use Vesp\Services\Migration;

final class Products extends Migration
{

    public function up(): void
    {
        // Таблица категорий товаров
        $this->schema->create(
            'categories',
            function (Blueprint $table) {
                $table->id();
                $table->string('title');
                $table->text('description')->nullable();
                $table->boolean('active')->default(true)->index();
                $table->timestamps();
            }
        );

        // Таблица товаров
        $this->schema->create(
            'products',
            function (Blueprint $table) {
                $table->id();
               // Связь товаров с категорией
                $table->foreignId('category_id')
                    // Запрет удаления категории, если в ней есть хотя-бы 1 товар
                    ->constrained('categories')->restrictOnDelete();
                $table->string('title');
                $table->text('description')->nullable();
                // Артикул товара должен быть уникальным
                $table->string('sku')->unique();
                // Цену храним в колонке с цифрами после запятой
                $table->unsignedDecimal('price')->nullable();
                $table->boolean('active')->default(true)->index();
                $table->timestamps();
            }
        );
    }

    public function down(): void
    {
        // При откате миграции удаляем таблицы в обратном порядке
        $this->schema->drop('products');
        $this->schema->drop('categories');
    }
}
Дальше делаем composer db:migrate и создаём наши модели в core/src/Models.

core/src/Models/Category.php

<?php

namespace App\Models;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

/**
 * @property int $id
 * @property string $title
 * @property ?string $description
 * @property bool $active
 * @property Carbon $created_at
 * @property Carbon $updated_at
 *
 * @property-read Product[] $products
 */
class Category extends Model
{
    protected $guarded = ['id', 'created_at', 'updated_at'];
    protected $casts = ['active' => 'boolean'];

    // Каждая категория может иметь много товаров
    public function products(): HasMany
    {
        return $this->hasMany(Product::class);
    }
}
Как видите - ничего особенного, просто прописываем всё то, что уже написали в миграциях.

core/src/Models/Product.php

<?php

namespace App\Models;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

/**
 * @property int $id
 * @property int $category_id
 * @property string $title
 * @property ?string $description
 * @property string $sku
 * @property float $price
 * @property bool $active
 * @property Carbon $created_at
 * @property Carbon $updated_at
 *
 * @property-read Category $category
 */
class Product extends Model
{
    protected $guarded = ['id', 'created_at', 'updated_at'];
    protected $casts = [
        'active' => 'boolean',
        'price' => 'float',
    ];

    //  Каждый товар принадлежит одной категории
    public function category(): BelongsTo
    {
        return $this->belongsTo(Category::class);
    }
}
Мне кажется, тут вопросов никаких - всё предельно просто. Раз у нас есть модели, можно написать для них и контроллеры.

core/src/Contollers/Admin/Categories.php

<?php

namespace App\Controllers\Admin;

use App\Models\Category;
use Vesp\Controllers\ModelController;

class Categories extends ModelController
{
    protected $scope = 'products';
    protected $model = Category::class;
}
Совершенно минимальная конфигурация - только требование разрешения products и указание модели.

core/src/Contollers/Admin/Products.php

<?php

namespace App\Controllers\Admin;

use App\Models\Product;
use Vesp\Controllers\ModelController;

class Products extends ModelController
{
    protected $scope = 'products';
    protected $model = Product::class;
}
Здесь ровно то же самое.

core/routes.php

Добавляем 2 новых адреса в группу /admin:
$group->group(
    '/admin',
    static function (RouteCollectorProxy $group) {
        // Это было по-умолчанию
        $group->any('/users[/{id}]', App\Controllers\Admin\Users::class);
        $group->any('/user-roles[/{id}]', App\Controllers\Admin\UserRoles::class);

        // А вот это мы добавляем
        $group->any('/categories[/{id}]', App\Controllers\Admin\Categories::class);
        $group->any('/products[/{id}]', App\Controllers\Admin\Products::class);
    }
);
Собственно, с бэкендом мы уже закончили. У меня на создание миграций, моделей и контроллеров ушло примерно 15 минут.

Права доступа

Оба контроллера требуют одно разрешение - products, поэтому нам нужно добавить его группе администраторов.
Это можно сделать как вручную через админку, отредактировав группу Admin, так и через сиды.
Во втором случае меняем core/db/seeds/UserRoles.php и добавляем разрешение на 12й строке:
$roles = [
    'Administrator' => [
        'scope' => ['profile', 'users', 'products'], // Вот здесь
    ],
    'User' => [
        'scope' => ['profile'],
    ],
];
После этого можно сделать composer db:seed-one UserRoles для обновления только групп юзеров.

Фронтенд

Переходим на фронтенд и создаём страницу frontend/src/admin/pages/categories.vue:
<template>
  <div>
    <vesp-table
      :url="url"
      :header-actions="headerActions"
      :table-actions="tableActions"
      :fields="fields"
      :filters="filters"
      :sort="sort"
      :dir="dir"
      :row-class="rowClass"
    />
    <nuxt-child />
  </div>
</template>

<script>
//  Адрес нашего контроллера
export const url = 'admin/categories'

export default {
  name: 'CategoriesPage',
  validate({app}) {
    // Проверка разрешения
    return app.$hasScope('products')
  },
  data() {
    return {
      url,
      filters: {
        query: '',
      },
      sort: 'id',
      dir: 'asc',
    }
  },
  head() {
    return {
      title: [this.$t('models.category.title_many'), this.$t('project')].join(' / '),
    }
  },
  computed: {
    headerActions() {
      return [{route: 'categories-create', icon: 'plus', title: this.$t('actions.create')}]
    },
    tableActions() {
      return [
        {route: 'categories-edit-id', icon: 'edit', title: this.$t('actions.edit')},
        {function: 'onDelete', icon: 'times', title: this.$t('actions.delete'), variant: 'danger'},
      ]
    },
    fields() {
      return [
        {key: 'id', label: this.$t('components.table.columns.id'), sortable: true},
        {key: 'title', label: this.$t('models.category.title'), sortable: true},
        // Эта колонка будет выводить количество товаров категории
        {key: 'products_count', label: this.$t('models.category.products'), sortable: true},
        {
          key: 'created_at',
          label: this.$t('components.table.columns.created_at'),
          formatter: this.$options.filters.datetime,
          sortable: true,
        },
      ]
    },
  },
  methods: {
    rowClass(item) {
      return item && !item.active ? 'text-muted' : ''
    },
  },
}
</script>
Ничего необычного, всё примерно как на странице users.vue. Таким эе образом создаём и страницу products.vue - можно вообще скопировать предыдущую и переименовать внутри все слова categories и category на products и product соответственно. Я лично так и делаю.
Финальный штрих - добавляем новые страницы в frontend/src/admin/plugins/menu.js:
export default [
  // Новые записи ставим в начало
  {
    name: 'products',
    title: 'models.product.title_many',
    scope: 'products',
  },
  {
    name: 'categories',
    title: 'models.category.title_many',
    scope: 'products',
  },
  // Старые, понятно, тоже оставляем как было
  {
    name: 'users',
    title: 'models.user.title_many',
    scope: 'users',
    children: [
      {
        name: 'users-roles',
        title: 'models.user_role.title_many',
        scope: 'users',
      },
    ],
  },
]
Запускаем composer node:dev и заходим в админку как admin.
Всё работает, только нет записей в лексиконах.

Лексиконы

Идём в frontend/admin/src/lexicons и меняем все 3 словаря. Я привожу здесь только ru.js в сокращённом виде:
export default {
  project: 'Vesp',
  // ...
  models: {
    user: {
      // ...
    },
    user_role: {
      // ...
    },
    category: {
      title_one: 'Категория',
      title_many: 'Категории',
      title: 'Название',
      description: 'Описание',
      products: 'Товары',
      active: 'Включено',
    },
    product: {
      title_one: 'Товар',
      title_many: 'Товары',
      title: 'Название',
      description: 'Описание',
      sku: 'Артикул',
      price: 'Цена',
      category: 'Категория',
      active: 'Включено',
    },
  },
  // ...
}
В других словарях нужно добавить точно такие же записи, только на соответствующем языке. Для PhpStorm можно установить плагин Translation, это очень ускоряет переводы.
При каждом редактировании словарей страница разработки обновляется и в итоге всё становится вот так:

Формы редактирования

Дальше идём в frontend/src/admin/components/forms и создаём 2 формы для редактирования категорий и товаров.

forms/category.vue

<template>
  <div>
    <b-form-group :label="$t('models.category.title')">
      <b-form-input v-model.trim="record.title" required autofocus />
    </b-form-group>

    <b-form-group :label="$t('models.category.description')">
      <b-form-textarea v-model.trim="record.description" rows="5" />
    </b-form-group>

    <b-form-group>
      <b-form-checkbox v-model="record.active">
        {{ $t('models.category.active') }}
      </b-form-checkbox>
    </b-form-group>
  </div>
</template>

<script>
// Никакой особой логики
export default {
  // Имя компонента
  name: 'FormCategory',
  // Один-единственный принимаемый параметр через v-model
  props: {
    value: {
      type: Object,
      required: true,
    },
  },
  // И вычисляемый параметр для изменения
  computed: {
    record: {
      get() {
        return this.value
      },
      set(newValue) {
        this.$emit('input', newValue)
      },
    },
  },
}
</script>
Логику работы форм мы уже разбирали в уроке про модальные окна.

forms/product.vue

Почти такая же форма, за одним исключением - здесь добавляется обязательный выбор категории товара. Публикую только часть с template:
<template>
  <div>
    <b-form-group :label="$t('models.product.title')">
      <b-form-input v-model.trim="record.title" required autofocus />
    </b-form-group>

    <b-form-group :label="$t('models.product.description')">
      <b-form-textarea v-model.trim="record.description" rows="5" />
    </b-form-group>

    <!--Артикул и цену располагаем в одну строку-->
    <b-row>
      <b-col md="6">
        <b-form-group :label="$t('models.product.sku')">
          <b-form-input v-model.trim="record.sku" required />
        </b-form-group>
      </b-col>
      <b-col md="6">
        <b-form-group :label="$t('models.product.price')">
          <b-form-input v-model.trim="record.price" />
        </b-form-group>
      </b-col>
    </b-row>

    <!--А вот и выбор категории-->
    <b-form-group :label="$t('models.product.category')">
      <vesp-input-combo-box v-model="record.category_id" url="admin/categories" required />
    </b-form-group>

    <b-form-group>
      <b-form-checkbox v-model.trim="record.active">
        {{ $t('models.product.active') }}
      </b-form-checkbox>
    </b-form-group>
  </div>
</template>
Везде используем одни и те же значения для лексиконов.
Формы есть, осталось создать страницы для работы с моделями.

Создание и редактирование моделей

Напоминаю, что вся работа с конкретными моделями находится во вложенных маршрутах по отношению к их таблицам. Поэтому создаём новые страницы для товаров и категорий по следующим адресам:
  • pages/categories/create.vue - создание категории
  • pages/categories/edit/_id.vue - редактирование категории
  • pages/products/create.vue - создание товара
  • pages/products/edit/_id.vue - редактирование товара
Приведу здесь только страницы товаров, у категорий всё ровно так же - можно смело копипастить с небольшими изменениями в наборе полей и строках лексикона.

pages/products/create.vue

<template>
  <vesp-modal v-model="record" :url="url" :title="$t('models.product.title_one')">
    <template #form-fields>
      <form-product v-model="record" />
    </template>
  </vesp-modal>
</template>

<script>
// импорт адреса контроллера из таблицы
import {url} from '../products'
// Импорт формы
import FormProduct from '../../components/forms/product'

export {url}
export default {
  name: 'ProductCreatePage',
  components: {FormProduct},
  data() {
    return {
      url,
      record: {
        // Значения по-умолчанию
        title: '',
        description: '',
        sku: '',
        price: null,
        category_id: null,
        active: true,
      },
    }
  },
}
</script>

pages/products/edit/_id.vue

У страниц редактирования обычно вообще ничего не меняется.
Всё нужное просто импортируется из страницы создания модели и добавляется предварительная загрузка модели из API.
<script>
import Create, {url} from '../create'

export default {
  name: 'ProductEditPage',
  extends: Create,
  validate({params}) {
    return /^\d+$/.test(params.id)
  },
  async asyncData({app, params, error}) {
    try {
      const {data: record} = await app.$axios.get(url + '/' + params.id)
      return {record}
    } catch (e) {
      error({statusCode: e.statusCode, message: e.data})
    }
  },
}
</script>

Всё готово

Действительно, спустя примерно час создания файлов и копирования их туда-сюда, у нас всё готово для проверки работы.
У меня всё запустилось с первого раза. Нажимайте на картинку, чтобы посмотреть GIFку
Итого, спустя примерно 1.5 - 2 часа у вас должно появиться 2 связанных друг сдругом модели товаров и их категорий.
Как видите, нет ничего сложного, всё максимально автоматизировано и предназначего для очень быстрой работы.
Наш новый проект я опубликовал на Github, чтобы вы могли посмотреть, что именно у меня получается, вот все изменения за сегодня.
На следующем уроке будем расширять наши новые модели и страницы.

38 комментариев

Странное дело... чтобы отобразились хоть какие-то изменения после редактирования vue файлов на домене vesp-shop.test, мне приходится давать команду composer node:generate При этом если я копирую адрес http://192.168.0.175:4000/admin/ из результатов команды composer node:dev и работаю с ним, то там вижу всегда актуальную информацию. Но там проблема кросс-доменных запросов, потому что ajax запросы улетают на http://vesp-shop.test/api/ Не понятна эта магия)
Василий Наумкин
Проект локально находится на vesp-shop.test, PHP крутится только там, больше нигде - все запросы в API идут туда.
А вот фронт у нас есть в 2х видах:
  • режим разработки, который открывается на локальном IP после команды composer node:dev
  • режим готового приложения, которое собирается в статичные файлы после composer node:generate
Режим разработки нужен, чтобы сразу видеть все изменения, а статические файлы - для выгрузки на хостинг. Или, в нашем случае, на локальный vesp-shop.test
Так при разработке всегда используем режим dev, а generate - если хотим видеть наш проект на локальном домене после разработки, без запуска node.
Спасибо за разъяснение, этот момент понял. Тогда остается проблема еще в том, что когда я захожу на адрес http://192.168.0.175:4000/admin/products, то в консоли пишется ошибка: Access to XMLHttpRequest at 'http://vesp-shop.test/api/admin/products?page=1&limit=20&sort=id&dir=asc&query=' from origin 'http://192.168.0.175:4000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Причем на странице категории такой ошибки нет.
Василий Наумкин
А если напрямую открыть http://vesp-shop.test/api/admin/products?page=1&limit=20&sort=id&dir=asc&query= - ошибки нет, случайно?
Очень похоже, что просто контроллер не работает, и возвращает неправильный JSON ответ.
Да, правы вы! Там ошибка, но это по моей невнимательности она была допущена. Исправил, все заработало! Спасибо!
Василий Наумкин
На здоровье!
Пересоздал проект заново, набираю в терминале composer db:create Productsи получаю ошибку
 Command "db:create" is not defined.  Did you mean one of these?           
      db:migrate                       
      db:rollback                      
      db:seed   
Василий Наумкин
А ты как пересоздал? Надо же все файлы заново загрузить, включая composer.json - там есть новые команды.
Я так понимаю, что надо было просто скачать пакет vesp/vesp и перезаписать все файлы в директории VespShop. Я же "пересоздал проект заново" выполнив composer create-project vesp/vesp ./VespShop
Василий Наумкин
Эта команда вернёт ошибку, если директория не пустая.
Скорее всего так и было, просто ты не обратил внимания =)
Наверное так и было бы, но я умудрился выполнить composer create-project vesp/vesp ./VespShop уже находясь в директории VespShop и там чудесным образом создалась новая директория VespShop со всем содержимым =) А можно как-то обновлять пакет vesp/ через composer?
Василий Наумкин
А можно как-то обновлять пакет vesp/ через composer?
Да, конечно, composer update и composer node:update - но это обновит только PHP и JS зависимости, которые в core/vendor и frontend/node_modules.
Всё остальное - это уже твои файлы, они не обновляются, и меняются только тобой. Что с ними хочешь, то и делай.
Я просто по ходу курса обучения сам вижу свои косяки и правлю, поэтому сейчас пришлось файлы обновить. А в реальной работе обновляются только зависимости.
Мы не ограничены количеством миграций, поэтому сегодня будет минимальный набор колонок в таблицах, а дальше будем добавлять новые по необходимости
А мы в этом курсе еще вернемся к доработке карточки товара (подключение текстового редактора, загрузка фото, работа с масивом свойств товара, отметок т.д.) и работе со списком товаров (груповое редактирование товаров (например, оключить публикацию) или, например, выбрать товары определенной категории (учитывая, что они идут общим списком )?
Василий Наумкин
Забыл ответить на комментарий, исправляюсь.
Загрузка фото точно будет, текстовый редактор ты можешь попробовать подключить и сам. Со списком товаров ничего особенного делать не планирую.
На данный момент я хочу провести вас через полный цикл разработки, заканчивая запуском проекта на хостинге. А дорабатывать его потом можно сколько угодно.
Спасибо! Посоветуешь текстовый редактор? Не факт что получится, но обязательно попробую подключить самостоятельно.
Василий Наумкин
Я обычно не пользуюсь RTE редакторами, потому что они пишут всякое непонятное что в HTML.
Но можно вот здесь почитать - https://www.tiny.cloud/blog/best-vue-rich-text-editors/
Кирилл Дворянинов
Почти все заработало, но под конец понеслось что-то не понятное:
categories/create.vue Импортирую ранее созданную форму в forms/category.vue и у меня выдает в этом месте ошибку: export default { name: 'CategoryCreatePage', components: {FormCategory}, Ошибка тут }
// Конкретно тут в конце, пишет что компонент зарегистрирован (оно и логично ведь я задал ему это имя в форме), но не используется в vue/no-unused-components. Я специально попробовал добавить форму импортировать и добавить components: {FormCategory} продукта и странно он почему-то ошибку не выдает. Попробовал удалить FormCategory и просто импортированный FormProduct, ошибок в редакторе естественно не получаю, в админке пытаюсь добавить категорию и выдает следующее: _SQLSTATE[42S22]: Column not found: 1054 Unknown column 'updated_at' in 'field list' (SQL: insert into old_categories (title, description, active, updated_at, created_at) values (qweq, qwe, true, 2022-06-20 19:44:19, 2022-06-20 19:44:19))_
Василий Наумкин
Если ты что-то импортировал, то нужно это использовать внутри template - иначе ESLint будет ругаться на бесполезный импорт. То есть, у тебя там обязятельно должен быть вызван <form-category v-model="record" />
В комментарии можно прикладывать картинки, кстати говоря - это вроде твой скриншот из телеграма
А насчёт ошибки SQL что-то странное. Проверь, есть ли у тебя в БД у категорий такая колонка вообще. Если нет, то ты забыл указать $table->timestamps(); в миграции. Тогда нужно всё указать, и сделать
composer db:rollback
composer db:migrate
Кирилл Дворянинов
Спасибо, теперь я понял по поводу импорта :) А по поводу ошибки sql - timestamps(); прописан, в таблице также есть записи. Но при попытке запустить composer db:rollback получаю следующее:
Василий Наумкин
Но при попытке запустить composer db:rollback получаю следующее:
Тебе там пишут warning, что в в миграции одновременно методы change и up/down - так нельзя. Удали change(), мы его не используем.
в таблице также есть записи.
На 2м скриншоте наоброт видно, что колонок с датами у тебя нет. Разберись с миграциями, в репозитории можно свериться с правильным файлом.
Александр Наумов
Почему-то, у меня не создались таблицы app_categories и app_products, и файл ***_products.php верный и команду выполнил composer db:migrate, а таблицы не создаются.
Василий, подскажи, на каком этапе создаются таблицы?
Василий Наумкин
Надо всё внимательно проверять, что-то не так сделал. Таблицы создаются сразу при запуске миграции.
Если их нет, у тебя должна быть какая-то ошибка при запуске.
Александр Наумов
Устанавливал, все по инструкции, ошибок никаких не было, после браузер показал ошибку, когда перешел в раздел Категории:
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'VespShop.app_categories' doesn't exist (SQL: select count(*) as aggregate from `old_categories`)
Сейчас после запуска команды: composer db:create Products
composer db:rollback
Василий Наумкин
Что-то странное у тебя произошло: миграция есть, и вроде как выполнена, но таблицы при этом отсутствуют. Как-будто вручную были удалены после выполнения миграции.
Нужно это привести в порядок, самый простой способ - удалить запись Products из old_migrations и запустить composer db:migrate
Ну или вообще удалить все таблицы и сделать composer db:migrate && composer db:seed
Александр Наумов
Василий, спасибо большое, за ценный комментарий, еще больше стал понимать, как оно все устроено! Первым способом восстановил таблицы.
Василий Наумкин
На здоровье!
Что это за ошибка никто не в курсе? И как от нее избавится?
ERROR in 
..../Vesp2/frontend/src/admin/pages/products.vue
   4:7  error  Delete `··`  prettier/prettier
   5:1  error  Delete `··`  prettier/prettier
   6:7  error  Delete `··`  prettier/prettier
Василий Наумкин
Рассказывал в одной из предыдущих заметок, раздел ESLint и Prettier.
И как на странице товаров вывести название категории я к сожалению не разобрался и не смог В проекте на github просто такая строка {key: 'category.title', label: this.$t('models.product.category')}, Но если ее вставить на этом этапе то ни чего не будет как вытащить название категории по id в таблицу пока не понятно )
Следующий урок там ответ
Все получилось до пункта "Создание и редактирование моделей". Т.е. в админке создались вкладки "Категории" и "Товары" и они добавились в меню.
Но после того как создал директории и файлы:
pages/categories/create.vue - создание категории
pages/categories/edit/_id.vue - редактирование категории
pages/products/create.vue - создание товара
pages/products/edit/_id.vue - редактирование товара 
пошли ошибки типа:
friendly-errors 10:27:26
* ../../components/inputs/alias in ./node_modules/babel-loader/lib??ref--2-0!./node_modules/vue-loader/lib??vue-loader-options!./src/admin/components/forms/product.vue?vue&type=script&lang=js&
Старался делать как написано, где был не уверен - брал файлы с Github, , но что-то видимо сделал не так.(
Василий Наумкин
Ошибки пишут, чтобы их читать и исправлять.
The relative module was not found:

../../components/inputs/alias
Модуль с относительным путём не был найден. Проверяй путь к этому компоненту .
У меня в директории
/frontend/src/admin/components/
вообще нет компонента
inputs/alias.vue
. И в исходниках его не было, странно неужели я что-то по курсу пропустил? В репозитории Vesp-shop - он есть. Сейчас создам у себя и с ним попробую.
Василий Наумкин
Ну так на этом уроке ничего про компонент inputs/alias.vue и не сказано. Ты откуда узнал, что его нужно использовать в формах?
В конце указана ссылка на полный коммит с данными на момент урока. Там про этот компонент ничего не сказано, он появится позже.
Если ты забежал вперёд и копируешь итоговый код после окончания курса - сам себе злобный буратина. Копируй уж тогда всё полностью.
Не совсем так, да и не особо важно теперь, когда все в итоге получилось). Я выше писал, что половина этого урока у меня получилась, и появились вкладки Категории и Товаров, а также они отобразились в меню. Но вот когда создал эти директории и страницы:
pages/categories/create.vue - создание категории
pages/categories/edit/_id.vue - редактирование категории
pages/products/create.vue - создание товара
pages/products/edit/_id.vue - редактирование товара 
у меня пошли ошибки, и ты мне написал выше проверить путь к этому компоненту. Я проверил и компонент у себя не обнаружил. Затем его добавил и все заработало))) Теперь формы появились и создаются категории и товары. Прикольно)
Василий Наумкин
Но вот когда создал эти директории и страницы:
Еще раз, в этом уроке нет ничего про компонент alias, он появится позже. Откуда ты его взял? Как его импорт оказался на твоих созданных страницах?
Только если ты скопировал код из репозитория на момент завершения курса. Так делать не нужно - я оставляю ссылки на финальный код урока в конце самого урока.
Смотреть нужно именно его.
Еще раз, в этом уроке нет ничего про компонент alias, он появится позже. Откуда ты его взял? Как его импорт оказался на твоих созданных страницах?
Я про этот компонент ничего не знал и заранее ничего не копировал конечно. Но я выполнял урок и дошел до ошибки. В моем первом комменте я описал ошибку и приложил скрин. В ответ на этот мой комментарий ты ответил:
Проверяй путь к этому компоненту .
После твоего комментария я и стал проверять путь к компоненту и обнаружил, что компонента у меня и нет. Я решил, что вся проблема в этом и добавил компонент. И только после того, как я добавил этот компонент - я смог выполнить этот урок до конца. Т.е. исчезли ошибки, добавились формы и как на твоей гифке - я могу создавать товары и категории.
Василий Наумкин
Но я выполнял урок и дошел до ошибки.
Просто покажи скриншот, где в этом уроке что-то написано про компонент input-alias.
Прямо вот ткни меня носом, где я здесь написал тебе его включить в свою форму, чтобы ты получил ошибку.
Ты видишь на моей картинке где-то <input-alias ...? Нет? Правильно, потому что в этом уроке про него нет ни слова.
Ты просто скопировал более поздний код из репозитория и теперь тратишь моё время зря.
Блин, вот оказывается из-за чего! Извини Василий, не было злого умысла!) Я видно уже припарился и когда дошел до этого Публикую только часть с template, понял, что вдруг что не так сделаю из-за того, что Почти такая же форма (значит почти - да не совсем) и тут уже полез в твой Github и оттуда дернул новые файлы (ну чтобы наверняка - без почти), и поэтому ошибка и вылезла
Почти такая же форма, за одним исключением - здесь добавляется обязательный выбор категории товара. Публикую только часть с template:
bezumkin.ru
Personal website of Vasily Naumkin
Прямой эфир
Александр Наумов
23.07.2024, 00:20:37
Василий, спасибо большое!!
Василий Наумкин
01.07.2024, 11:56:41
Да, верно, именно так. А в контроллере, скорее всего, ловить данные методом post.
Василий Наумкин
26.06.2024, 09:38:15
О, точно, вылезает если не залогинен. Спасибо, исправил!
Василий Наумкин
09.04.2024, 04:45:01
> Ошибка 500 Это не похоже на ошибку Nginx, это скорее всего ошибка PHP - надо смотреть его логи. ...
Василий Наумкин
20.03.2024, 21:21:52
Volledig!
Андрей
14.03.2024, 13:47:10
Василий! Как всегда очень круто! Моё почтение!
russel gal
09.03.2024, 20:17:18
> А этот стоило написать хотя бы затем, чтобы получить комментарий от юзера, который ничего не писал...
Александр Наумов
27.01.2024, 03:06:18
Василий, спасибо! Извини, тупанул.
Василий Наумкин
22.01.2024, 07:43:20
Давай-давай!
Василий Наумкин
24.12.2023, 14:26:13
Спасибо!