wizard.blade.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. {{-- Вьюха: Мастер генерации макета блока из HTML --}}
  2. @extends('admin.layout')
  3. @section('title', 'Генератор макета блока')
  4. @section('content_header')
  5. <div class="d-flex justify-content-between align-items-center">
  6. <h1 class="m-0">Генератор макета блока</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">Генератор макета</li>
  16. @stop
  17. @section('content')
  18. @if(session('success'))
  19. <div class="alert alert-success alert-dismissible">
  20. <button type="button" class="close" data-dismiss="alert">&times;</button>
  21. {{ session('success') }}
  22. </div>
  23. @endif
  24. {{-- ════════════════════════════════════════════════════════════════════════ --}}
  25. {{-- ШАГ 1: вставляем HTML --}}
  26. {{-- ════════════════════════════════════════════════════════════════════════ --}}
  27. @if($parsed === null)
  28. <div class="row">
  29. <div class="col-md-8">
  30. <div class="card card-primary card-outline">
  31. <div class="card-header"><h3 class="card-title">Шаг 1 — Вставьте HTML секции</h3></div>
  32. <form action="{{ route('admin.blocks.wizard.analyze') }}" method="POST">
  33. @csrf
  34. <div class="card-body">
  35. <div class="row">
  36. <div class="col-md-6">
  37. <div class="form-group">
  38. <label>Системное имя макета <span class="text-danger">*</span>
  39. <small class="text-muted">— латиница и _</small>
  40. </label>
  41. <input type="text" name="layout_name"
  42. class="form-control @error('layout_name') is-invalid @enderror"
  43. value="{{ old('layout_name') }}"
  44. placeholder="hero_banner">
  45. @error('layout_name')<div class="invalid-feedback">{{ $message }}</div>@enderror
  46. </div>
  47. </div>
  48. <div class="col-md-6">
  49. <div class="form-group">
  50. <label>Название макета <span class="text-danger">*</span></label>
  51. <input type="text" name="layout_title"
  52. class="form-control @error('layout_title') is-invalid @enderror"
  53. value="{{ old('layout_title') }}"
  54. placeholder="Главный баннер">
  55. @error('layout_title')<div class="invalid-feedback">{{ $message }}</div>@enderror
  56. </div>
  57. </div>
  58. </div>
  59. <div class="form-group">
  60. <label>HTML секции <span class="text-danger">*</span></label>
  61. <textarea name="html" rows="18"
  62. class="form-control text-monospace @error('html') is-invalid @enderror"
  63. placeholder="Вставьте HTML-разметку секции или повторяющихся карточек...">{{ old('html') }}</textarea>
  64. @error('html')<div class="invalid-feedback">{{ $message }}</div>@enderror
  65. <small class="text-muted d-block mt-1">
  66. Можно вставить как одну секцию целиком, так и несколько однотипных карточек.
  67. Парсер сам определит структуру.
  68. </small>
  69. </div>
  70. </div>
  71. <div class="card-footer">
  72. <button type="submit" class="btn btn-primary">
  73. <i class="fas fa-search"></i> Анализировать HTML
  74. </button>
  75. </div>
  76. </form>
  77. </div>
  78. </div>
  79. <div class="col-md-4">
  80. <div class="card card-info card-outline">
  81. <div class="card-header"><h3 class="card-title">Как это работает</h3></div>
  82. <div class="card-body text-sm">
  83. <p><strong>1.</strong> Вставьте HTML-код секции.</p>
  84. <p><strong>2.</strong> Парсер найдёт все текстовые элементы, заголовки, ссылки, картинки и повторяющиеся блоки.</p>
  85. <p><strong>3.</strong> Вы увидите список полей и черновик Blade-шаблона. Всё можно отредактировать.</p>
  86. <p><strong>4.</strong> После подтверждения создаётся файл макета и Blade-шаблон. Макет появится в дропдауне при создании блока.</p>
  87. <hr>
  88. <p class="text-muted small">
  89. Поддерживаемые теги: <code>h1–h6</code>, <code>p</code>, <code>a</code>, <code>img</code>, любые листовые элементы с текстом.
  90. </p>
  91. </div>
  92. </div>
  93. </div>
  94. </div>
  95. {{-- ════════════════════════════════════════════════════════════════════════ --}}
  96. {{-- ШАГ 2: review и сохранение --}}
  97. {{-- ════════════════════════════════════════════════════════════════════════ --}}
  98. @else
  99. @php
  100. $isRepeater = $parsed['is_repeater'];
  101. $fields = $parsed['fields'];
  102. @endphp
  103. <form action="{{ route('admin.blocks.wizard.generate') }}" method="POST">
  104. @csrf
  105. <input type="hidden" name="layout_name" value="{{ $layout_name }}">
  106. <input type="hidden" name="layout_title" value="{{ $layout_title }}">
  107. <input type="hidden" name="is_repeater" value="{{ $isRepeater ? 1 : 0 }}">
  108. <div class="row">
  109. {{-- ── Левая колонка: поля ───────────────────────────────────────── --}}
  110. <div class="col-md-6">
  111. {{-- Обнаруженная структура --}}
  112. <div class="alert {{ $isRepeater ? 'alert-info' : 'alert-success' }}">
  113. <i class="fas fa-{{ $isRepeater ? 'layer-group' : 'align-left' }}"></i>
  114. @if($isRepeater)
  115. <strong>Обнаружены повторяющиеся элементы.</strong>
  116. Добавлено поле <code>items</code> типа repeater.
  117. @else
  118. <strong>Плоская структура.</strong>
  119. Обнаружено {{ count($fields) }} {{ trans_choice('поле|поля|полей', count($fields)) }}.
  120. @endif
  121. </div>
  122. {{-- Плоские поля (не repeater) --}}
  123. @php
  124. $flatFields = collect($fields)->where('type', '!=', 'repeater')->values()->all();
  125. $repeaterField = collect($fields)->firstWhere('type', 'repeater');
  126. @endphp
  127. @if(count($flatFields) > 0)
  128. <div class="card card-success card-outline">
  129. <div class="card-header"><h3 class="card-title">Поля блока</h3></div>
  130. <div class="card-body p-0">
  131. <table class="table table-sm mb-0">
  132. <thead><tr>
  133. <th>Системное имя</th>
  134. <th>Название</th>
  135. <th>Тип</th>
  136. </tr></thead>
  137. <tbody>
  138. @foreach($flatFields as $i => $field)
  139. <tr>
  140. <td>
  141. <input type="text" name="flat_fields[{{ $i }}][name]"
  142. class="form-control form-control-sm"
  143. value="{{ $field['name'] }}">
  144. </td>
  145. <td>
  146. <input type="text" name="flat_fields[{{ $i }}][label]"
  147. class="form-control form-control-sm"
  148. value="{{ $field['label'] }}">
  149. </td>
  150. <td>
  151. <select name="flat_fields[{{ $i }}][type]" class="form-control form-control-sm">
  152. @foreach(['text','textarea','url','image'] as $t)
  153. <option value="{{ $t }}" {{ $field['type'] === $t ? 'selected' : '' }}>{{ $t }}</option>
  154. @endforeach
  155. </select>
  156. </td>
  157. </tr>
  158. @endforeach
  159. </tbody>
  160. </table>
  161. </div>
  162. </div>
  163. @endif
  164. {{-- Repeater-поле и его sub_fields --}}
  165. @if($repeaterField)
  166. <div class="card card-info card-outline">
  167. <div class="card-header">
  168. <h3 class="card-title">Repeater-группа</h3>
  169. </div>
  170. <div class="card-body">
  171. <div class="row mb-2">
  172. <div class="col-6">
  173. <label class="small text-muted">Системное имя</label>
  174. <input type="text" name="repeater_name"
  175. class="form-control form-control-sm"
  176. value="{{ $repeaterField['name'] }}">
  177. </div>
  178. <div class="col-6">
  179. <label class="small text-muted">Название</label>
  180. <input type="text" name="repeater_label"
  181. class="form-control form-control-sm"
  182. value="{{ $repeaterField['label'] }}">
  183. </div>
  184. </div>
  185. <label class="font-weight-bold small">Дочерние поля (sub_fields):</label>
  186. <table class="table table-sm mb-0">
  187. <thead><tr>
  188. <th>Системное имя</th>
  189. <th>Название</th>
  190. <th>Тип</th>
  191. </tr></thead>
  192. <tbody>
  193. @foreach($repeaterField['sub_fields'] as $j => $sub)
  194. <tr>
  195. <td>
  196. <input type="text" name="sub_fields[{{ $j }}][name]"
  197. class="form-control form-control-sm"
  198. value="{{ $sub['name'] }}">
  199. </td>
  200. <td>
  201. <input type="text" name="sub_fields[{{ $j }}][label]"
  202. class="form-control form-control-sm"
  203. value="{{ $sub['label'] }}">
  204. </td>
  205. <td>
  206. <select name="sub_fields[{{ $j }}][type]" class="form-control form-control-sm">
  207. @foreach(['text','textarea','url','image'] as $t)
  208. <option value="{{ $t }}" {{ $sub['type'] === $t ? 'selected' : '' }}>{{ $t }}</option>
  209. @endforeach
  210. </select>
  211. </td>
  212. </tr>
  213. @endforeach
  214. </tbody>
  215. </table>
  216. </div>
  217. </div>
  218. @endif
  219. {{-- Мета --}}
  220. <div class="card card-secondary card-outline">
  221. <div class="card-body">
  222. <div class="row">
  223. <div class="col-6">
  224. <label class="small text-muted">Системное имя макета</label>
  225. <input type="text" class="form-control form-control-sm" value="{{ $layout_name }}" readonly>
  226. </div>
  227. <div class="col-6">
  228. <label class="small text-muted">Название макета</label>
  229. <input type="text" class="form-control form-control-sm" value="{{ $layout_title }}" readonly>
  230. </div>
  231. </div>
  232. </div>
  233. <div class="card-footer">
  234. <button type="submit" class="btn btn-success btn-block">
  235. <i class="fas fa-save"></i> Сохранить макет и Blade-шаблон
  236. </button>
  237. <a href="{{ route('admin.blocks.wizard') }}" class="btn btn-secondary btn-block mt-1">
  238. <i class="fas fa-redo"></i> Начать заново
  239. </a>
  240. </div>
  241. </div>
  242. </div>
  243. {{-- ── Правая колонка: Blade-шаблон ─────────────────────────────── --}}
  244. <div class="col-md-6">
  245. <div class="card card-warning card-outline">
  246. <div class="card-header d-flex justify-content-between align-items-center">
  247. <h3 class="card-title">Черновик Blade-шаблона</h3>
  248. <small class="text-muted">можно редактировать</small>
  249. </div>
  250. <div class="card-body p-0">
  251. <textarea name="blade" rows="30"
  252. class="form-control text-monospace border-0"
  253. style="border-radius:0;font-size:12px;resize:vertical">{{ $parsed['blade'] }}</textarea>
  254. </div>
  255. <div class="card-footer text-muted small">
  256. Файл будет сохранён в
  257. <code>resources/views/blocks/{{ $layout_name }}.blade.php</code>
  258. </div>
  259. </div>
  260. </div>
  261. </div>
  262. </form>
  263. @endif
  264. @stop