Vue.js интеграция

Imhotep View имеет встроенную поддержку Vue.js для создания реактивных интерфейсов. Интеграция позволяет использовать Vue-компоненты напрямую в Moon-шаблонах, автоматически собирая все зависимости и ресурсы.

Структура директорий

resources/views/
├── vue/
│   ├── components/          # Vue-компоненты
│   │   ├── Header.vue       # SFC компонент <v-header />
│   │   ├── Footer.vue
│   │   ├── Alert/           # Классический компонент (папка)
│   │   │   ├── Alert.js     # JS логика компонента
│   │   │   └── Alert.scss   # Стили компонента (или .css)
│   │   └── UI/              # Вложенные директории
│   │       ├── Select.vue   # <v-ui-select />
│   │       └── Button/      # Классический компонент <v-ui-button /> во вложенной папке 
│   │           ├── Button.js
│   │           └── Button.scss
│   ├── composables/         # Композаблы (реактивные функции)
│   │   ├── useAlert.js
│   │   └── useUser.js
│   ├── app.js               # Основное приложение (обязательно)
│   └── utils.js             # Вспомогательные функции (опционально)
PHP

Инициализация Vue

Для работы Vue-компонентов необходимо выполнить начальную настройку в Moon-шаблоне.

Базовый шаблон

<!DOCTYPE html>
<html>
<head>
    <title>Vue-интеграция</title>
    
    {{-- Подключение стилей компонентов --}}
    @vuehead
</head>
<body>
    {{-- Использование компонентов --}}
    <v-greeting :message="Hello, Imhotep!" />

    <script src="https://unpkg.com/vue@latest/dist/vue.global.js"></script>
    {{-- Инициализация Vue (всегда в конце body) --}}
    @vue([
        'user' => auth()->user(),
        'settings' => $settings
    ])
</body>
</html>
HTML

Директива @vuehead

Подключает стили и внешние ресурсы компонентов. Размещается внутри тега <head>.

Директива @vuecomposables

Управляет загрузкой композаблов. Если директива не указана, при инициализации через @vue будут загружены все композаблы из папки composables/.

Если директива указана, будут загружены только перечисленные композаблы, остальные игнорируются.

{{-- Без указания - загружаются все композаблы --}}
@vue([])

{{-- Загрузка только указанных композаблов --}}
@vuecomposables(['useAlert', 'useUser'])
@vue(['user' => auth()->user()])

{{-- Загрузка композаблов из пространства имен --}}
@vuecomposables(['admin::useAlert', 'admin::useUser'])
@vue([], 'admin')
HTML

Важно: @vuecomposables должна вызываться до @vue

Директива @vue

Производит полную инициализацию Vue-приложения: подключает компоненты, композаблы, стили и передает данные. Размещается в конце <body>.

{{-- Передача данных в компоненты --}}
@vue(['user' => auth()->user(), 'errors' => $errors])

{{-- Инициализация с указанием пространства имен --}}
@vue([], 'admin')  // Будет искать admin::vue.app

{{-- Инициализация без данных --}}
@vue([])
HTML

Важно: @vue должна вызываться после всех компонентов и после @vuecomposables (если используется).

Файл app.js

При инициализации через @vue автоматически подключается файл resources/views/vue/app.js, который отвечает за регистрацию и монтирование компонентов на странице.

// resources/views/vue/app.js

const { createApp } = Vue;

class componentRegistryPlugin {
    constructor() {
        this.components = new Map();
        this.instances = new Set();
    }

    register(name, component) {
        this.components.set(name, component)

        this.instances.forEach(app => {
            if (!app.component(name)) {
                app.component(name, component)
            }
        })
        return this
    }

    install(app) {
        // Регистрируем все компоненты в новом приложении
        this.components.forEach((component, name) => {
            app.component(name, component)
        })

        // Сохраняем экземпляр для будущих обновлений
        this.instances.add(app)

        // Добавляем метод для динамической регистрации
        app.config.globalProperties.$registerComponent = (name, component) => {
            this.register(name, component)
        }
    }
}

const componentRegistry = new componentRegistryPlugin();

if (window.$vueComponents) {
    window.$vueComponents.forEach(comp => {
        componentRegistry.register(comp.name, comp.component);

        const el = document.querySelector('[data-v-component="' + comp.name + '"]');
        if (el) {
            let initialData = JSON.parse(el.dataset.initial || '{}');

            for (let key in el.dataset) {
                if (key === 'initial' || !key.startsWith('initial')) continue;

                let name = key.replace('initial', '');
                name = name.charAt(0).toLowerCase() + name.slice(1);

                initialData[name] = JSON.parse(el.dataset[key] || '{}');
            }

            createApp(comp.component, {...initialData}).use(componentRegistry).mount(el)
        }
    });
}
JS

Файл utils.js

Если вашим компонентам нужны общие функции, вы можете разместить их в файл resources/views/vue/utils.js, который автоматически подключается при инициализации.

// resources/views/vue/utils.js

const formatPrice = (price) => {
    return new Intl.NumberFormat('ru-RU', {
        style: 'currency',
        currency: 'RUB'
    }).format(price);
};
JS

Создание компонентов

Вы можете использовать два способа создания Vue-компонентов.

Однофайловые компоненты (SFC)

Однофайловые компоненты (файлы .vue) объединяют шаблон, логику и стили в одном файле. Это предпочтительный способ для большинства случаев.

<!-- resources/views/vue/components/Greeting.vue -->

<template>
  <div class="greeting">
    <p>{{ greeting }}</p>
    <button @click="count++">Счетчик: {{ count }}</button>
  </div>
</template>

<script>
  export default {
    props: {
      message: {
        type: String,
        default: 'Hello World!'
      }
    },
    data() {
      return {
        count: 0,
        greeting: this.message
      };
    },
    mounted() {
      console.log('Компонент смонтирован');
    }
  }
</script>

<style lang="scss">
  .greeting {
    padding: 20px;
    background: #f5f5f5;
    border-radius: 8px;

    p {
      color: #2c3e50;
      font-weight: bold;
    }
  }
</style>
VUE

Классический компонент

Состоит из двух файлов: ComponentName.js и ComponentName.scss (или .css). Менее удобный вариант чем SFC.

Важно: Для классического компонента обязательно наличие (window.$vueComponents = window.$vueComponents || []).push({ ... }); для правильной инициализации. Имена JS и SCSS файлов должны совпадать с именем компонента.

// resources/views/vue/components/Alert/Alert.js

(window.$vueComponents = window.$vueComponents || []).push({
    name: 'alert',
    component: {
        setup() {
            const { state } = useAlert();
            return { state };
        },
        template: `
            <transition name="fade-alert">
                <div v-if="state.visible" class="alert" :class="'is-' + state.type">
                    {{ state.message }}
                </div>
            </transition>
        `
    }
});
JS
// resources/views/vue/components/Alert/Alert.scss

.alert {
    z-index: 9999;
    position: fixed;
    top: 2rem; right: 2rem;
    display: flex;
    align-items: center;
    gap: 1rem;
    min-width: 300px;
    padding: 1rem 1.25rem;
    background: rgba(255, 255, 255, 0.9);
    backdrop-filter: blur(10px);
    border: 1px solid rgba(0, 0, 0, 0.05);
    border-radius: 12px;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
    color: #1f2937;
}

/* Vue Transitions */
.fade-alert-enter-active, .fade-alert-leave-active {
    transition: all 0.3s ease;
}
.fade-alert-enter-from, .fade-alert-leave-to {
    opacity: 0;
    transform: translateY(-20px);
}
SCSS

Именование компонентов

Имя компонента формируется из пути к файлу относительно папки components/:

  • Путь преобразуется в kebab-case (строчные буквы с дефисами)
  • Вложенные директории добавляются через дефис: UI/Select.vue → v-ui-select
  • Компонент из папки Alert/Alert.js → v-alert

Использование компонентов

Использование Vue-компонентов доступно только из Moon-шаблонов. Все Vue-компоненты начинаются с префикса v-, например <v-alert />.

Передача данных в компоненты

Для передачи данных в компонент используйте классический вариант :name="$name" или data-initial="$user".

<div>
    {{-- Подключение Vue-компонента --}}
    <v-alert :message="Hello" />
    
    {{-- Через data-initial атрибуты --}}
    <v-user-profile data-initial="$user" />
</div>
HTML

Компоненты с пространствами имен

{{-- Vue-компонент из пространства имен: admin --}}
<admin::v-alert />
HTML

Создание композаблов

Композаблы размещаются в папке resources/views/vue/composables. Названием файла является именем композабла. Имя всегда начинается с use (например, useAlert). Композаблы автоматически подключаются во время инициализации тега @vue.

// resources/views/vue/composables/useUser.js

export function useUser() {
    const name = 'Алексей';
    const age = 30;
    
    function greet() {
        console.log(`Привет, ${name}!`);
    }
    
    return { name, age, greet };
}
JS

Если необходимо подключить только определенные композаблы, передайте их список в директиву @vuecomposables перед @vue:

// resources/views/layout/default.moon.php

<body>
@vuecomposables(['useUser', 'useAlert'])
@vue([])
</body>
HTML