Остальные компоненты админки

Честно говоря, не думал, что только знакомство с системой затянется аж на 10 заметок. Мне-то оно всё давно привычно и понятно, а вот когда пытаешься внятно об этом рассказать...
В общем, сегодня мы заканчиваем вводную часть, и дальше будет настоящая работа по постройке магазина VespShop.
На нужно посмотреть, какие еще компоненты я написал для удобной работы на Vesp, и начнём с переключателя языка - vesp-change-locale (по клику откроется GIFка)

vesp-change-locale

Как я уже упоминал, мультиязычность админки работает при помощи модуля @nuxtjs/i18n, который в свою очередь использует популярнейший Vue i18n.
Все лексиконы хранятся в src/admin/lexicons и загружаются через index.js, где происходит соединение стандартных и пользовательских словарей. Вам просто нужно добавлять соответствующие значения в файлы ru.js, en.js и de.js.
Я в своей работе обычно использую только 2 словаря: английский и немецкий, поэтому мой конфиг в nuxt.config.js выглядит так:
// Меняем всего 2 параметра:
// - файл с загрузкой лексиконов
Config.i18n.vueI18n = '@/lexicons/index.js'
// - и доступные языки
Config.i18n.locales = [
  {code: 'en', title: 'English'},
  {code: 'de', title: 'Deutsch'},
]
Именно массив из Config.i18n.locales будет использован в vesp-change-locale для вывода и переключения языков.
Файл src/admin/lexicons/index.js будет выглядеть следующим образом:
import merge from 'deepmerge'
import vespDe from '@vesp/frontend/lexicons/de'
import vespEn from '@vesp/frontend/lexicons/en'
import localDe from './de'
import localEn from './en'

// Здесь возвращается именно функция, чтобы при изменении словарей
// во время разработки работало обновление страницы через hot reload
export default () => {
  return {
    // Словарь по умолчанию
    fallbackLocale: 'en',
    // Доступные словари
    messages: {
      de: merge(vespDe, localDe),
      en: merge(vespEn, localEn),
    },
  }
}
Библиотека deepmerge позволяет нам рекурсивно соединить 2 объекта со словарями.
Если вам мультиязычность не нужна, то я всё равно рекомендую использовать лексиконы. Во-первых, их всё равно будут использовать компоненты Vesp, а во-вторых это просто удобно для работы.
Если вы хотите оставить только один язык, например, русский, то нужно поменять настройки таким образом:
// Заменяем весь стандартный конфиг своим
Config.i18n = {
  defaultLocale: 'ru', // Оставляем 1 язык
  strategy: 'no_prefix',
  vueI18n: '@/lexicons/index.js',
}
Ну а в lexicons/index.js оставляем только один словарь.
import merge from 'deepmerge'
import vespRu from '@vesp/frontend/lexicons/ru'
import localRu from './ru'

export default () => {
  return {
    fallbackLocale: 'ru',
    messages: {
      ru: merge(vespRu, localRu),
    },
  }
}
Использование компонента vesp-change-locale в таком случае не имеет смысла, потому что переключать нам нечего.

vesp-input-color-picker

Дальше у нас идут различные способы ввода, которые дополняют (а иногда и расширяют) имеющиеся в BootstrapVue.
Например, компонент выбора цвета:
При вызове нужно просто указать v-model, в который будет записан выбранный цвет:
<vesp-input-color-picker v-model="record.color" />
При редактировании модели, если какой-то цвет уже указан, он будет сразу выставлен в компоненте.

vesp-input-combo-box

Это компонент для поиска и выбора id модели из списка. Он обращается к указанному url методом GET, и рассчитывает на обычный ответ от контроллера с полями total и rows.
Вы уже могли видеть его в форме редактирования пользователя, здесь приведу в расширенном виде:
<vesp-input-combo-box 
    v-model="record.role_id" 
    url="admin/user-roles" 
    text-field="title" 
    value-field="id"
    limit="10"
    sort="title"
    dir="asc"
 />
Параметры text-field и value-field указывают, какое поле в ответе от сервера использовать для названия, а какое для значения v-model. Все остальные параметры, полагаю, и так понятны.
При запросе на сервер обязательно передаётся параметр combo=true, чтобы вы могли прописать в контроллере отдельные правила для выборки.
if ($this->getProperty('combo')) {
    // ...
}
Этот компонент я использую постоянно, например при написании этой заметки для выбора раздела:

vesp-input-date-picker

Хотя в BootstrapVue и есть свой собственный date-picker, меня он по ряду причин не устраивает. В первую очередь, он не умеет работать с диапазоном дат.
Поэтому, если вы указываете v-model массивом, даже пустым, то будет диапазон.
А если строкой - то обычная дата
Здесь внизу я вывел реальное значение v-model, чтобы вы заметили, что там может быть массив или строка. Это значение и улетит на сервер, где вы сможете проверить тип присланных данных.
Выбор времени даты определяется наличием параметра type="datetime", по умолчанию там type="date"
<vesp-input-date-picker v-model="record.date" type="datetime" />

vesp-input-password

Это обычное поле ввода пароля, которое при нажатии на кнопочку показывает или скрывает набранное.
<vesp-input-password v-model="record.password" />

vesp-input-remote-links

Этот очень интересный компонент предназначен для сохранения разных ссылок в одном массиве. Бывает очень полезно в разных проектах прицепить модели JSON колонку и хранить в ней ссылки на соц.сети.
Здесь я тоже внизу показываю реальное значение поля ввода.
<vesp-input-remote-links
    v-model="record.links"
    :services="{facebook: 'Facebook', instagram: 'Instagram', twitter: 'Twitter'}"
/>
Как видите, на Vue можно создавать очень интересные компоненты ввода для форм.

input-text-mask

И последний компонент очень полезен, если нам нужно ограничить ввод текстовой маской. Например, для российских телефонов
Как видите, истиное значение v-model оставляет только цифры и знак +:
<vesp-input-text-mask
    v-model="record.phone"
    mask="+7 (XXX) XXX-XX-XX"
    placeholder-char="X"
    :state="!record.phone ? null : record.phone.length === 12"
    :format-value="(value) => value.replaceAll(/[^\d+]/g, '')"
/>
В параметрах можно задать маску ввода, указать какой именно символ в ней является плейсхолдером, и отформатировать получаемое значение собственной функцией.
Параметр state - это стандартная фишка BootstrapVue для полей ввода. При указании false будет ошибка, true - успех, а null ничего не делает.
В этом примере я просто проверяю количество введёных символов, если они вообще есть.

Заключение

На данный момент мы рассмотрели все доступные компоненты Vesp. Напоминаю, что они подключаются в проект добавлением @vesp/frontend в Config.modules файла nuxt.config.js.
По умолчанию компоненты включены только в приложении src/admin, а src/site идёт пустой.
На следующем занятии мы, вооружившись всеми этими значениями, начнём строить наш VespShop.

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

Комментариев не много, вероятно всем все понятно. Прошу заранее простить - Slim4, Eloquent, VueJS, NuxtJs, Phinx это новые слова в моем лексиконе, поэтому спасибо за подробную детализацию уроков. Исходя из выше написаного, задам глупый вопрос - NuxtJs имеет более 200 модулей, означает ли это, что для их интеграции необходимо писать отдельный компонент Vesp?
Василий Наумкин
Нет, конечно - это уже готовые модули, их нужно только подключить, как мы подключаем модуль авторизации или BootstrapVue.
Я же постарался написать свой модуль для Nuxt и сейчас рассказал какие в него входят компоненты.
Но дальше мы будем писать и свои уникальные компоненты для работы, это очень легко и удобно, когда разберёшься.
Кирилл Дворянинов
Ну как понятно :) Я иногда по раза 2-3 статью читаю. Честно говоря разработка на вот этих фреймворках vue, react и т.п. Мне кажется очень сложной, компоненты, контроллеры и прочее ерунда. Дочитав до этой статьи я все равно половины немного не понимаю особенно синтаксис, много чего нового. Но как говорится - через тернии к звездам!
Василий Наумкин
Прекрасно тебя понимаю, я когда сам в этом разбирался - голова дымилась.
Но зато теперь прямо-таки получаю удовольствие от разработки!
Александр Наумов
Василий, добрый день!
Решил на сайте сделать форум "Заказать обратный звонок" в модульном окне с отправкой сообщения в Телеграмм.
Взял я у BootstrapVue модальное окно и добавил туда vesp-input-text-mask в итоге получаю ошибку: "Cannot read property 'phone' of undefined", пишу в script:
 data() {
   return {
     record: {
       phone: '',
     },
ошибка пропала, но поля input нет. Все тоже самое и с другими полями vesp-input я их не могу активировать.
Вот код :
<template>
  <div>
    <b-modal
      id="modal-prevent-closing"
      ref="modal"
      title="Заказать обратный звонок"
      centered
      @show="resetModal"
      @hidden="resetModal"
      @ok="handleOk"
    >
      <form ref="form" @submit.stop.prevent="handleSubmit">
        <b-form-group label="Имя" label-for="name-input" invalid-feedback="Имя обязательно" :state="nameState">
          <b-form-input id="name-input" v-model="name" :state="nameState" required></b-form-input>
        </b-form-group>

        <b-form-group>
          <vesp-input-text-mask
            v-model="record.phone"
            mask="+7 (XXX) XXX-XX-XX"
            placeholder-char="X"
            :state="!record.phone ? null : record.phone.length === 12"
            :format-value="(value) => value.replaceAll(/[^\d+]/g, '')"
          />
          <!-- <vesp-input-color-picker v-model="record.color" /> -->
        </b-form-group>
      </form>
    </b-modal>
  </div>
</template>

<script>
export default {
  data() {
    return {
      name: '',
      nameState: null,
      submittedNames: [],
      record: {
        phone: '',
      },
    }
  },
  methods: {
    checkFormValidity() {
      const valid = this.$refs.form.checkValidity()
      this.nameState = valid
      return valid
    },
    resetModal() {
      this.name = ''
      this.nameState = null
    },
    handleOk(bvModalEvent) {
      // Предотвратить закрытие модального окна
      bvModalEvent.preventDefault()
      // Обработчик отправки триггера
      this.handleSubmit()
    },
    handleSubmit() {
      // Выйти, если форма недействительна
      if (!this.checkFormValidity()) {
        return
      }
      // Вставьте имя в представленные имена
      this.submittedNames.push(this.name)
      // Скрыть модальное окно вручную
      this.$nextTick(() => {
        this.$bvModal.hide('modal-prevent-closing')
      })
    },
  },
}
</script>
Пересмотрел все варианты с применением vesp-input, те, что есть в VESP SHOP, так и не понял, почему у меня не работает.
В DOM дереве у меня:
<vesp-input-text-mask mask="+7 (XXX) XXX-XX-XX" placeholder-char="X" format-value="function formatValue(value){return value.replaceAll(/[^\d+]/g,&quot;&quot;);}"></vesp-input-text-mask>
Выходит, что движок не обработал. В nuxt.config.js у меня так: Config.modules = ['bootstrap-vue/nuxt', '@nuxtjs/axios', '@nuxtjs/pwa', '@vesp/frontend']
Где я что-то не так сделал?
Василий Наумкин
На сайте компоненты Vesp по умолчанию не подключены, нужно поменять вот эту настройку:
Александр Наумов
Понял, спасибо!
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
Спасибо!