diff --git a/app/Containers/Dashboard/Actions/Pages/ExportPageAction.php b/app/Containers/Dashboard/Actions/Pages/ExportPageAction.php new file mode 100644 index 0000000..3d0827c --- /dev/null +++ b/app/Containers/Dashboard/Actions/Pages/ExportPageAction.php @@ -0,0 +1,42 @@ +load(['section.mainSection', 'seo']); + + $sectionPath = null; + + if ($page->section) { + $sectionPath = [ + 'main_section_slug' => $page->section->mainSection?->slug, + 'sub_section_slug' => $page->section->slug, + ]; + } + + return [ + 'export_version' => 1, + 'exported_at' => now()->toIso8601String(), + 'page' => [ + 'title' => $page->title, + 'slug' => $page->slug, + 'code' => $page->code, + 'content' => $page->content, + 'settings' => $page->settings, + 'searchable' => $page->searchable, + 'sort' => $page->sort, + 'is_visible' => $page->is_visible, + ], + 'seo' => $page->seo ? [ + 'title' => $page->seo->title, + 'description' => $page->seo->description, + ] : null, + 'section_path' => $sectionPath, + ]; + } +} diff --git a/app/Containers/Dashboard/Actions/Pages/ImportPageAction.php b/app/Containers/Dashboard/Actions/Pages/ImportPageAction.php new file mode 100644 index 0000000..533d0dc --- /dev/null +++ b/app/Containers/Dashboard/Actions/Pages/ImportPageAction.php @@ -0,0 +1,84 @@ +resolveSubSectionId($data['section_path'] ?? null); + + // Generate search_data from content + if (!empty($pageData['content'])) { + $pageData['search_data'] = $this->generateSearchDataTask->run($pageData['content']); + } + + // Remove fields that should not be set during import + unset($pageData['icon']); + + $page = Page::create($pageData); + + // Attach SEO if present + if (!empty($data['seo'])) { + $page->seo()->create([ + 'title' => $data['seo']['title'] ?? null, + 'description' => $data['seo']['description'] ?? null, + ]); + } + + return $page; + } + + private function resolveSubSectionId(?array $sectionPath): ?int + { + if (empty($sectionPath)) { + return null; + } + + $mainSectionSlug = $sectionPath['main_section_slug'] ?? null; + $subSectionSlug = $sectionPath['sub_section_slug'] ?? null; + + if (!$mainSectionSlug || !$subSectionSlug) { + return null; + } + + $mainSection = MainSection::where('slug', $mainSectionSlug)->first(); + + if (!$mainSection) { + return null; + } + + $subSection = SubSection::where('main_section_id', $mainSection->id) + ->where('slug', $subSectionSlug) + ->first(); + + return $subSection?->id; + } +} diff --git a/app/Containers/Dashboard/UI/WEB/Controllers/PageController.php b/app/Containers/Dashboard/UI/WEB/Controllers/PageController.php index 77ad444..fb81493 100644 --- a/app/Containers/Dashboard/UI/WEB/Controllers/PageController.php +++ b/app/Containers/Dashboard/UI/WEB/Controllers/PageController.php @@ -6,6 +6,8 @@ use App\Containers\AppStructure\Models\Page; use App\Containers\Dashboard\Actions\ContentBuilder\UploadContentBuilderFilesAction; use App\Containers\Dashboard\Actions\Pages\CreatePageAction; use App\Containers\Dashboard\Actions\Pages\DeletePageAction; +use App\Containers\Dashboard\Actions\Pages\ExportPageAction; +use App\Containers\Dashboard\Actions\Pages\ImportPageAction; use App\Containers\Dashboard\Actions\Pages\ListPagesAction; use App\Containers\Dashboard\Actions\Pages\UpdatePageAction; use App\Containers\Dashboard\UI\WEB\Requests\StorePageRequest; @@ -14,6 +16,7 @@ use App\Http\Controllers\Controller; use Illuminate\Http\JsonResponse; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; +use Illuminate\Http\Response; use Inertia\Inertia; class PageController extends Controller @@ -23,6 +26,8 @@ class PageController extends Controller private readonly CreatePageAction $createPageAction, private readonly UpdatePageAction $updatePageAction, private readonly DeletePageAction $deletePageAction, + private readonly ExportPageAction $exportPageAction, + private readonly ImportPageAction $importPageAction, private readonly UploadContentBuilderFilesAction $uploadContentBuilderFilesAction, ) {} @@ -119,6 +124,58 @@ class PageController extends Controller } } + /** + * Экспортирует страницу в JSON-файл + */ + public function export(Page $page): Response + { + try { + $data = $this->exportPageAction->run($page); + + $filename = $page->slug . '_' . now()->format('Y-m-d_H-i-s') . '.json'; + + return response(json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), 200, [ + 'Content-Type' => 'application/json', + 'Content-Disposition' => 'attachment; filename="' . $filename . '"', + ]); + } catch (\Exception $e) { + return back()->with('error', 'Ошибка при экспорте страницы: ' . $e->getMessage()); + } + } + + /** + * Импортирует страницу из JSON-файла + */ + public function import(Request $request): RedirectResponse + { + if (config('app.env') !== 'local') { + return back()->with('error', 'Импорт доступен только в окружении local'); + } + + try { + $request->validate([ + 'import_file' => 'required|file|mimes:json,txt|max:512', + ]); + + $file = $request->file('import_file'); + $content = file_get_contents($file->getRealPath()); + $data = json_decode($content, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + return back()->with('error', 'Некорректный JSON: ' . json_last_error_msg()); + } + + $page = $this->importPageAction->run($data); + + return redirect()->route('dashboard.pages.edit', $page) + ->with('success', 'Страница "' . $page->title . '" успешно импортирована!'); + } catch (\InvalidArgumentException $e) { + return back()->with('error', 'Ошибка валидации: ' . $e->getMessage()); + } catch (\Exception $e) { + return back()->with('error', 'Ошибка при импорте страницы: ' . $e->getMessage()); + } + } + /** * Загружает файлы для ContentBuilder и возвращает метаданные */ diff --git a/app/Containers/Dashboard/UI/WEB/Routes/web.php b/app/Containers/Dashboard/UI/WEB/Routes/web.php index 09ac00f..96c5b65 100755 --- a/app/Containers/Dashboard/UI/WEB/Routes/web.php +++ b/app/Containers/Dashboard/UI/WEB/Routes/web.php @@ -375,8 +375,10 @@ Route::middleware(['access-check', 'dashboard.auth', 'dashboard.permission'])->g Route::get('/', [PageController::class, 'index'])->name('index'); Route::get('/create', [PageController::class, 'create'])->name('create'); Route::post('/', [PageController::class, 'store'])->name('store'); + Route::post('/import', [PageController::class, 'import'])->name('import'); Route::post('/upload-files', [PageController::class, 'uploadFiles'])->name('upload-files'); Route::get('/{page}/edit', [PageController::class, 'edit'])->name('edit'); + Route::get('/{page}/export', [PageController::class, 'export'])->name('export'); Route::put('/{page}', [PageController::class, 'update'])->name('update'); Route::delete('/{page}', [PageController::class, 'destroy'])->name('destroy'); }); diff --git a/resources/js/Pages/Dashboard/Pages/Index.vue b/resources/js/Pages/Dashboard/Pages/Index.vue index 5aa8ff5..b94935e 100644 --- a/resources/js/Pages/Dashboard/Pages/Index.vue +++ b/resources/js/Pages/Dashboard/Pages/Index.vue @@ -6,13 +6,31 @@ @@ -139,6 +157,13 @@ > +