form.blade.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. {{-- Вьюха: Форма создания/редактирования блока контента (ACF-подобные поля) --}}
  2. @extends('admin.layout')
  3. @section('title', $block->exists ? 'Редактировать блок' : 'Новый блок')
  4. @section('content_header')
  5. <div class="d-flex justify-content-between align-items-center">
  6. <h1 class="m-0">{{ $block->exists ? 'Блок: ' . $block->title : 'Новый блок' }}</h1>
  7. <a href="{{ route('admin.blocks.index') }}" class="btn btn-default btn-sm">
  8. <i class="fas fa-arrow-left"></i> Назад
  9. </a>
  10. </div>
  11. @stop
  12. @section('breadcrumb')
  13. <li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}">Главная</a></li>
  14. <li class="breadcrumb-item"><a href="{{ route('admin.blocks.index') }}">Блоки контента</a></li>
  15. <li class="breadcrumb-item active">{{ $block->exists ? 'Редактировать' : 'Новый блок' }}</li>
  16. @stop
  17. @section('content')
  18. @if($block->exists)
  19. <form action="{{ route('admin.blocks.update', $block) }}" method="POST" enctype="multipart/form-data">
  20. @method('PUT')
  21. @else
  22. <form action="{{ route('admin.blocks.store') }}" method="POST" enctype="multipart/form-data">
  23. @endif
  24. @csrf
  25. <div class="row">
  26. {{-- ── Левая колонка: поля блока ──────────────────────────────── --}}
  27. <div class="col-md-8">
  28. {{-- Системные поля (name, title, layout) --}}
  29. <div class="card card-primary card-outline">
  30. <div class="card-header"><h3 class="card-title">Идентификация</h3></div>
  31. <div class="card-body">
  32. <div class="form-group">
  33. <label>Название <span class="text-danger">*</span></label>
  34. <input type="text" name="title"
  35. class="form-control @error('title') is-invalid @enderror"
  36. value="{{ old('title', $block->title) }}" required>
  37. @error('title')<div class="invalid-feedback">{{ $message }}</div>@enderror
  38. </div>
  39. <div class="form-group">
  40. <label>
  41. Системное имя <span class="text-danger">*</span>
  42. <small class="text-muted">— только латиница, цифры и _</small>
  43. </label>
  44. <input type="text" name="name"
  45. class="form-control @error('name') is-invalid @enderror"
  46. value="{{ old('name', $block->name) }}"
  47. {{ $block->exists ? 'readonly' : '' }}
  48. placeholder="например: why_us" required>
  49. @error('name')<div class="invalid-feedback">{{ $message }}</div>@enderror
  50. @if($block->exists)
  51. <small class="text-muted">Системное имя нельзя изменить после создания.</small>
  52. @endif
  53. </div>
  54. @if($block->exists)
  55. {{-- Макет заблокирован: показываем только для чтения --}}
  56. <input type="hidden" name="layout" value="{{ $block->layout }}">
  57. <div class="form-group">
  58. <label>Макет блока</label>
  59. <input type="text" class="form-control"
  60. value="{{ $layoutDef['title'] ?? $block->layout }}" readonly>
  61. <small class="text-muted">Макет нельзя изменить после создания.</small>
  62. </div>
  63. @else
  64. {{-- Выбор макета при создании --}}
  65. <div class="form-group">
  66. <label>Макет блока <span class="text-danger">*</span></label>
  67. <select name="layout" id="layout-select"
  68. class="form-control @error('layout') is-invalid @enderror" required>
  69. <option value="">— выберите макет —</option>
  70. @foreach($layouts as $key => $layout)
  71. <option value="{{ $key }}"
  72. {{ old('layout') === $key ? 'selected' : '' }}>
  73. {{ $layout['title'] }}
  74. </option>
  75. @endforeach
  76. </select>
  77. @error('layout')<div class="invalid-feedback">{{ $message }}</div>@enderror
  78. <small class="text-muted">Определяет, какие поля нужно заполнить.</small>
  79. </div>
  80. @endif
  81. </div>
  82. </div>
  83. {{-- Содержимое: поля по макету --}}
  84. <div class="card card-success card-outline" id="block-fields-card"
  85. style="{{ (!$block->exists && !old('layout')) ? 'display:none' : '' }}">
  86. <div class="card-header"><h3 class="card-title">Содержимое блока</h3></div>
  87. <div class="card-body">
  88. @if($block->exists && $layoutDef)
  89. {{-- Редактирование: макет известен, рендерим сразу --}}
  90. @include('admin.blocks._fields', [
  91. 'fields' => $layoutDef['fields'],
  92. 'prefix' => 'data',
  93. 'values' => old('data', $block->data ?? []),
  94. ])
  95. @else
  96. {{-- Создание: рендерим поля для каждого макета, JS показывает нужный --}}
  97. @foreach($layouts as $key => $layout)
  98. <div class="layout-fields-set {{ old('layout') === $key ? '' : 'd-none' }}"
  99. data-layout="{{ $key }}">
  100. @include('admin.blocks._fields', [
  101. 'fields' => $layout['fields'],
  102. 'prefix' => 'data',
  103. 'values' => old('data', []),
  104. ])
  105. </div>
  106. @endforeach
  107. @endif
  108. </div>
  109. </div>
  110. </div>
  111. {{-- ── Правая колонка: настройки ───────────────────────────────── --}}
  112. <div class="col-md-4">
  113. <div class="card card-secondary card-outline">
  114. <div class="card-header"><h3 class="card-title">Настройки</h3></div>
  115. <div class="card-body">
  116. <div class="custom-control custom-switch">
  117. <input type="checkbox" class="custom-control-input" id="is_active"
  118. name="is_active" value="1"
  119. {{ old('is_active', $block->is_active ?? true) ? 'checked' : '' }}>
  120. <label class="custom-control-label" for="is_active">Блок активен</label>
  121. </div>
  122. @if($block->exists && $block->layout)
  123. <hr>
  124. <small class="text-muted d-block">
  125. Подключение на Blade-странице:
  126. </small>
  127. @php
  128. $blockHint = '{!! \App\Models\Block::getByName(\'' . $block->name . '\')?->render() !!}';
  129. @endphp
  130. <code class="d-block mt-1 small">{{ $blockHint }}</code>
  131. @endif
  132. </div>
  133. <div class="card-footer">
  134. <button type="submit" class="btn btn-primary btn-block">
  135. <i class="fas fa-save"></i> Сохранить
  136. </button>
  137. <a href="{{ route('admin.blocks.index') }}" class="btn btn-secondary btn-block mt-1">
  138. Отмена
  139. </a>
  140. </div>
  141. </div>
  142. </div>
  143. </div>
  144. </form>
  145. @stop
  146. @push('js')
  147. <script>
  148. $(document).ready(function () {
  149. // ── Переключение полей при смене макета (только создание) ──────────────
  150. $('#layout-select').on('change', function () {
  151. var val = $(this).val();
  152. $('.layout-fields-set').addClass('d-none');
  153. if (val) {
  154. $('[data-layout="' + val + '"]').removeClass('d-none');
  155. $('#block-fields-card').show();
  156. } else {
  157. $('#block-fields-card').hide();
  158. }
  159. });
  160. // ── Превью загружаемого изображения ───────────────────────────────────
  161. $(document).on('change', '.js-img-upload', function () {
  162. var wrap = $(this).closest('.form-group');
  163. var preview = wrap.find('.js-img-preview-new');
  164. var file = this.files[0];
  165. if (!file) return;
  166. // Обновляем лейбл кнопки
  167. $(this).siblings('.custom-file-label').text(file.name);
  168. // Читаем файл и показываем превью
  169. var reader = new FileReader();
  170. reader.onload = function (e) {
  171. preview.find('img').attr('src', e.target.result);
  172. preview.find('.js-img-new-name').text(file.name);
  173. preview.show();
  174. // Скрываем старое превью (заменяется)
  175. wrap.find('.js-img-preview-current').hide();
  176. };
  177. reader.readAsDataURL(file);
  178. });
  179. // ── Repeater: добавить строку ──────────────────────────────────────────
  180. $(document).on('click', '.btn-add-repeater-row', function () {
  181. var repName = $(this).data('repeater');
  182. var rowsContainer = $('#repeater-rows-' + repName);
  183. var template = $('#repeater-tpl-' + repName).html();
  184. var newIndex = rowsContainer.find('.repeater-row').length;
  185. // Заменяем __IDX__ на реальный порядковый индекс
  186. var html = template.replace(/__IDX__/g, newIndex);
  187. rowsContainer.append(html);
  188. });
  189. // ── Repeater: удалить строку ───────────────────────────────────────────
  190. $(document).on('click', '.btn-remove-repeater-row', function () {
  191. $(this).closest('.repeater-row').remove();
  192. });
  193. });
  194. </script>
  195. @endpush