From: Vladimir Fomichev Date: Fri, 3 Oct 2025 14:15:31 +0000 (+0300) Subject: Сортировка X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=a028af8b6255f8e7442f514ca3857253bbdee0f0;p=erp24_rep%2Fyii-erp24%2F.git Сортировка --- diff --git a/erp24/controllers/MatrixErpPropertyController.php b/erp24/controllers/MatrixErpPropertyController.php index 5ec32f56..771b4f25 100644 --- a/erp24/controllers/MatrixErpPropertyController.php +++ b/erp24/controllers/MatrixErpPropertyController.php @@ -148,12 +148,36 @@ class MatrixErpPropertyController extends Controller if (Yii::$app->request->isPost) { $postMatrixErpProperty = $this->request->post('MatrixErpProperty'); - $modelsMatrixErpMedia = MultipleModel::createMultipleModel(MatrixErpMedia::class, - 'MatrixErpProperty', 'mediaFiles'); - + // Получаем данные из формы $loadDataMatrixErpMedia = ArrayHelper::getValue($postMatrixErpProperty, 'mediaFiles'); + + // Сортируем данные по foto_order перед обработкой + if (!empty($loadDataMatrixErpMedia)) { + usort($loadDataMatrixErpMedia, function($a, $b) { + $orderA = $a['foto_order'] ?? 999; + $orderB = $b['foto_order'] ?? 999; + return $orderA <=> $orderB; + }); + } + + // Создаем модели вручную, сохраняя правильные ID + $modelsMatrixErpMedia = []; if (!empty($loadDataMatrixErpMedia)) { - MultipleModel::loadMultipleFromArray($modelsMatrixErpMedia, $loadDataMatrixErpMedia , '',[]); + foreach ($loadDataMatrixErpMedia as $index => $mediaData) { + if (!empty($mediaData['id'])) { + // Для существующих записей находим модель по ID + $model = MatrixErpMedia::findOne($mediaData['id']); + if ($model) { + $model->load($mediaData, ''); + $modelsMatrixErpMedia[] = $model; + } + } else { + // Для новых записей создаем новую модель + $model = new MatrixErpMedia(); + $model->load($mediaData, ''); + $modelsMatrixErpMedia[] = $model; + } + } } $loadMediaIds = ArrayHelper::getColumn($modelsMatrixErpMedia, 'id'); @@ -178,23 +202,7 @@ class MatrixErpPropertyController extends Controller $infos = FileService::uploadFileMultilevel('MatrixErpProperty', $adminId, ['mediaFiles','mediaFile']); - foreach ($modelsMatrixErpMedia as $key => $modelMatrixErpMedia) { - if (!empty($modelMatrixErpMedia->id)) { - $modelMatrixErpMediaRow = MatrixErpMedia::find()->where(['id'=> $modelMatrixErpMedia->id])->one(); - /** @var MatrixErpMedia $modelMatrixErpMediaRow */ - if (!empty($loadDataMatrixErpMedia[$key])) { - if (!empty($modelMatrixErpMediaRow)) { - $modelMatrixErpMediaRow->load($loadDataMatrixErpMedia[$key],''); - } else { - $modelMatrixErpMediaRow = $modelMatrixErpMedia; - } - } - } else { - /** @var MatrixErpMedia $modelMatrixErpMediaRow */ - /** @var MatrixErpMedia $modelMatrixErpMedia */ - $modelMatrixErpMediaRow = $modelMatrixErpMedia; - } - + foreach ($modelsMatrixErpMedia as $key => $modelMatrixErpMediaRow) { if (array_key_exists($key, $infos) && !empty($infos[$key])) { $info = $infos[$key]; @@ -214,6 +222,12 @@ class MatrixErpPropertyController extends Controller $modelMatrixErpMediaRow->created_admin_id = $adminId; $modelMatrixErpMediaRow->date = date("Y-m-d H:i:s"); $modelMatrixErpMediaRow->created_at = date("Y-m-d H:i:s"); + + // Устанавливаем foto_order - следующий доступный номер для этого guid + $maxOrder = MatrixErpMedia::find() + ->where(['guid' => $modelEdit->guid]) + ->max('foto_order') ?? 0; + $modelMatrixErpMediaRow->foto_order = $maxOrder + 1; } if ($modelMatrixErpMediaRow->validate()) { if (empty($modelMatrixErpMediaRow->id)) { @@ -223,6 +237,9 @@ class MatrixErpPropertyController extends Controller } } + // Обновляем основную картинку на первую из мультиформы + $this->updateMainImageFromFirstMedia($modelEdit, $adminId); + $uploadImage = UploadedFile::getInstanceByName('MatrixErpProperty[imageFile]'); if ($uploadImage) { @@ -275,6 +292,93 @@ class MatrixErpPropertyController extends Controller * @return MatrixErpProperty the loaded model * @throws NotFoundHttpException if the model cannot be found */ + /** + * Обновляет основную картинку на первую из мультиформы + * @param MatrixErpProperty $modelEdit + * @param int $adminId + */ + protected function updateMainImageFromFirstMedia($modelEdit, $adminId) + { + // Находим первую медиа запись (с минимальным порядком) + $firstMedia = MatrixErpMedia::find() + ->where(['guid' => $modelEdit->guid]) + ->andWhere(['>', 'foto_order', 0]) // Исключаем записи с foto_order = 0 (если есть) + ->orderBy(['foto_order' => SORT_ASC]) + ->one(); + + if ($firstMedia && $firstMedia->file) { + $file = $firstMedia->file; + + // Проверяем, что файл существует и это изображение + if ($file->file_type === 'image' && file_exists($file->url)) { + Yii::info("Processing main image update for file: {$file->url}", __METHOD__); + // Сохраняем старую картинку для возможного удаления + $oldImageId = $modelEdit->image_id; + + // Создаем новую запись в Images на основе файла + $image = new Images(); + + // Генерируем filename как в методе loadImage + $originalName = basename($file->url); + $ar_file_name = explode('.', $originalName); + $ext = '.' . end($ar_file_name); + $file_name = md5($originalName . rand(100000, 999999)) . $ext; + + // Используем оригинальное название файла из MatrixErpMedia или имя файла + $displayName = !empty($firstMedia->name) ? $firstMedia->name : pathinfo($originalName, PATHINFO_FILENAME); + $image->original_name = $displayName . $ext; + $image->filename = $file_name; + $image->type = mime_content_type($file->url) ?: 'image/jpeg'; + + // Получаем размеры изображения + $imageInfo = getimagesize($file->url); + if ($imageInfo) { + $image->width = $imageInfo[0]; + $image->height = $imageInfo[1]; + } + + $image->size = filesize($file->url); + $image->created_at = time(); + $image->updated_at = time(); + + if ($image->save()) { + // Копируем файл в директорию images + $sourcePath = $file->url; + $destDir = \Yii::getAlias('@uploads/images/' . substr($image->filename, 0, 2)); + $destPath = $destDir . '/' . $image->filename; + + if (!is_dir($destDir)) { + mkdir($destDir, 0777, true); + } + + if (copy($sourcePath, $destPath)) { + // Обновляем основную картинку + $modelEdit->image_id = $image->id; + $modelEdit->external_image_url = MarketplaceService::getProductImageUrl($image->id); + + if ($modelEdit->save()) { + Yii::info("Main image updated successfully to image_id: {$image->id}", __METHOD__); + // Удаляем старую картинку, если она была + if ($oldImageId && $oldImageId !== $image->id) { + $oldImage = Images::findOne($oldImageId); + if ($oldImage) { + $oldImage->delete(); + Yii::info("Old image deleted: {$oldImageId}", __METHOD__); + } + } + } else { + Yii::error("Failed to save MatrixErpProperty with new image_id: " . json_encode($modelEdit->getErrors()), __METHOD__); + } + } else { + Yii::error("Failed to copy file from {$sourcePath} to {$destPath}", __METHOD__); + // Если копирование не удалось, удаляем созданную запись + $image->delete(); + } + } + } + } + } + protected function findModel($id) { if (($model = MatrixErpProperty::findOne(['id' => $id])) !== null) { diff --git a/erp24/helpers/ImageHelper.php b/erp24/helpers/ImageHelper.php index 0dac85fd..bd6a8f66 100644 --- a/erp24/helpers/ImageHelper.php +++ b/erp24/helpers/ImageHelper.php @@ -35,7 +35,8 @@ class ImageHelper $modelImage = \yii_app\records\Images::findOne(['id' => $imageId]); if ($modelImage && File::src($modelImage->filename, 'images') != null) { $fileName = File::src($modelImage->filename, 'images'); - $relaFileName = File::getRealName($imageId); + // Используем original_name для понятного названия + $displayName = $modelImage->original_name ?: basename($fileName); try { $imageThumbRow = File::getResizedImageByName($modelImage->filename, $width, $height); } catch (\Exception $ex) { @@ -43,7 +44,7 @@ class ImageHelper } $imageSrcRow = '/' . $imageThumbRow; $srcImageThumbRow = '/' . $imageThumbRow; - $image = Html::img($srcImageThumbRow, ['class' => 'file-preview-image tumb', 'alt' => $relaFileName]); + $image = Html::img($srcImageThumbRow, ['class' => 'file-preview-image tumb', 'alt' => $displayName]); } return $image; @@ -60,7 +61,8 @@ class ImageHelper if ($modelImage && File::src($modelImage->filename, 'images') != null) { $fileName = File::src($modelImage->filename, 'images'); - $relaFileName = File::getRealName($imageIdRow); + // Используем original_name для понятного названия + $displayName = $modelImage->original_name ?: basename($fileName); try { $imageThumbRow = File::getResizedImageByName($modelImage->filename, $w , $h); } catch (\Exception $ex) { @@ -68,16 +70,16 @@ class ImageHelper } $imageSrcRow = '/' . $imageThumbRow; $srcImageThumbRow = '/' . $imageThumbRow; - $tagImageThumbRow = Html::img($srcImageThumbRow, ['class' => 'file-preview-image tumb', 'alt' => $relaFileName]); + $tagImageThumbRow = Html::img($srcImageThumbRow, ['class' => 'file-preview-image tumb', 'alt' => $displayName]); if (true === $forWidget) { $images[] = [ 'url' => $fileName, 'src' => $imageSrcRow, 'options' => [ - 'title' => $relaFileName, + 'title' => $displayName, 'class' => 'file-preview-image tumb', - 'alt' => $relaFileName, + 'alt' => $displayName, 'data-lightbox' => $fileName, ] ]; @@ -86,7 +88,7 @@ class ImageHelper 'class' => 'gallery-item px-2', 'target' => "_blank", 'rel'=> "noopener noreferrer", - 'title' => $relaFileName, + 'title' => $displayName, ]; $imagesResult[] = Html::a($tagImageThumbRow, $fileName , $options); diff --git a/erp24/records/MatrixErpMedia.php b/erp24/records/MatrixErpMedia.php index 37415baf..6e8465cf 100644 --- a/erp24/records/MatrixErpMedia.php +++ b/erp24/records/MatrixErpMedia.php @@ -13,6 +13,7 @@ use Yii; * @property string|null $description Описание * @property int|null $file_id Файл * @property string $date + * @property int|null $foto_order Порядок фото * @property int|null $created_admin_id * @property string|null $created_at * @property int|null $updated_admin_id @@ -39,7 +40,7 @@ class MatrixErpMedia extends \yii\db\ActiveRecord [['guid', 'name', 'date'], 'required'], [['description'], 'string'], [['mediaFiles', 'id'], 'safe'], - [['file_id', 'created_admin_id', 'updated_admin_id'], 'integer'], + [['file_id', 'foto_order', 'created_admin_id', 'updated_admin_id'], 'integer'], [['guid', 'name', 'date', 'created_at', 'updated_at'], 'string', 'max' => 100], ]; } @@ -55,6 +56,7 @@ class MatrixErpMedia extends \yii\db\ActiveRecord 'name' => 'Name', 'description' => 'Description', 'file_id' => 'File ID', + 'foto_order' => 'Foto Order', 'date' => 'Date', 'created_admin_id' => 'Created Admin ID', 'created_at' => 'Created At', diff --git a/erp24/records/MatrixErpProperty.php b/erp24/records/MatrixErpProperty.php index 83a8e4e0..340b86f6 100644 --- a/erp24/records/MatrixErpProperty.php +++ b/erp24/records/MatrixErpProperty.php @@ -92,6 +92,6 @@ class MatrixErpProperty extends \yii\db\ActiveRecord } public function getMediaFiles() { - return $this->hasMany(MatrixErpMedia::class, ['guid' => 'guid']); + return $this->hasMany(MatrixErpMedia::class, ['guid' => 'guid'])->orderBy(['foto_order' => SORT_ASC]); } } diff --git a/erp24/views/matrix_erp_property/_form.php b/erp24/views/matrix_erp_property/_form.php index 2069004a..e4cc6ee0 100644 --- a/erp24/views/matrix_erp_property/_form.php +++ b/erp24/views/matrix_erp_property/_form.php @@ -12,6 +12,7 @@ use yii_app\services\FileService; /** @var yii_app\records\MatrixErpProperty $modelMatrixErpProperty */ /** @var yii\widgets\ActiveForm $form */ +$this->registerJsFile('/js/Sortable.js', ['position' => \yii\web\View::POS_END]); $this->registerJsFile('/js/matrix_erp_property/_form.js', ['position' => \yii\web\View::POS_END]); $options = [ @@ -38,6 +39,7 @@ $this->registerJs( field($modelMatrixErpProperty, 'id')->hiddenInput()->label(false) ?> field($modelMatrixErp, 'id')->hiddenInput()->label(false) ?> field($modelMatrixErpProperty, 'guid')->hiddenInput()->label(false) ?> + field($modelMatrixErpProperty, 'image_id')->hiddenInput()->label(false) ?> field($modelMatrixErpProperty, 'display_name')->textInput() ?> @@ -82,8 +84,14 @@ $this->registerJs( field($modelMatrixErpProperty, 'weight')->textInput(['type' => 'number', 'step' => 0.01]) ?> + mediaFiles; + ?> +
field($modelMatrixErpProperty, 'mediaFiles')->widget(MultipleInput::className(), [ + 'data' => $mediaFilesData, 'min' => 0, 'max' => 100, 'columns' => [ @@ -118,10 +126,10 @@ $this->registerJs( ] ], [ - 'name' => 'num_row', + 'name' => 'foto_order', 'type' => BaseColumn::TYPE_HIDDEN_INPUT, 'value' => function($data) { - return $data['num_row'] ?? ''; + return $data['foto_order'] ?? ''; }, 'headerOptions' => [ 'style' => 'width: 70px;', @@ -139,13 +147,31 @@ $this->registerJs( 'type' => BaseColumn::TYPE_STATIC, 'value' => function($data) { /* @var $data \yii_app\records\MatrixErpMedia */ + $result = ''; + if (!empty($data->file)) { - $resultData = FileService::getFile($data->file); + // Получаем ссылку на скачивание файла от FileService + $downloadLink = FileService::getFile($data->file); + + // Для изображений показываем миниатюру + ссылку на скачивание + if ($data->file->file_type === 'image') { + $result = '
'; + $result .= '' . htmlspecialchars($data->name ?: basename($data->file->url)) . ''; + $result .= '
' . $downloadLink . '
'; + $result .= '
'; + } else { + // Для других файлов показываем иконку + ссылку на скачивание + $result = '
'; + $result .= ''; + $result .= '
' . $downloadLink . '
'; + $result .= '
'; + } } - return $resultData ?? ''; + + return $result; }, 'headerOptions' => [ -// 'style' => 'width: 70px;', + 'style' => 'width: 120px;', ] ], @@ -186,23 +212,56 @@ $this->registerJs(
- \ No newline at end of file diff --git a/erp24/web/js/matrix_erp_property/_form.js b/erp24/web/js/matrix_erp_property/_form.js index 7415ccbe..fa347552 100644 --- a/erp24/web/js/matrix_erp_property/_form.js +++ b/erp24/web/js/matrix_erp_property/_form.js @@ -60,4 +60,94 @@ function flowwowCategoryChanged(categoryIndex, subCategoryIndex) { $(document).ready(() => { flowwowCategoryChanged($("#matrixerpproperty-flowwow_category").val(), yiiOptions.subCategory); -}); \ No newline at end of file + + // Добавляем функциональность сортировки файлов с задержкой + setTimeout(function() { + initSortableMediaFiles(); + }, 500); + + // Также пытаемся инициализировать после загрузки всех виджетов + $(window).on('load', function() { + console.log('Window load event'); + setTimeout(function() { + initSortableMediaFiles(); + }, 100); + }); +}); + +// Глобальная переменная для хранения экземпляра sortable +let mediaSortableInstance = null; + +function initSortableMediaFiles() { + // Для таблиц MultipleInput нужно инициализировать sortable на tbody, а не на table + let sortableContainer = document.querySelector('.multiple-input-list tbody'); + if (!sortableContainer) { + sortableContainer = document.querySelector('.multiple-input-list'); + } + + if (sortableContainer && !mediaSortableInstance) { + // Инициализируем Sortable.js для перетаскивания строк таблицы + mediaSortableInstance = Sortable.create(sortableContainer, { + handle: '.sortable-handle', + animation: 150, + ghostClass: 'sortable-ghost', + chosenClass: 'sortable-chosen', + dragClass: 'sortable-drag', + // Указываем, что перетаскиваем именно tr элементы + draggable: 'tr.multiple-input-list__item', + onStart: function(evt) { + // Добавляем визуальную индикацию перетаскивания + $(evt.item).addClass('sortable-item-dragging'); + }, + onEnd: function(evt) { + $(evt.item).removeClass('sortable-item-dragging'); + updateNumRowValues(); + } + }); + + // Добавляем handle для перетаскивания к каждому элементу + $('.multiple-input-list__item').each(function() { + if (!$(this).find('.sortable-handle').length) { + // Добавляем handle в первую ячейку строки + $(this).find('td:first').prepend('
'); + $(this).find('td:first').css('display', 'flex'); + } + }); + } else if (sortableContainer && mediaSortableInstance) { + // Если sortable уже инициализирован, просто обновляем handles + $('.multiple-input-list__item').each(function() { + if (!$(this).find('.sortable-handle').length) { + $(this).find('td:first').prepend('
'); + } + }); + } +} + +function updateNumRowValues() { + $('.multiple-input-list__item').each(function(index) { + // Ищем поле foto_order и устанавливаем порядок (начиная с 1) + let fotoOrderInput = $(this).find('input[name*="MatrixErpProperty[mediaFiles]"][name*="[foto_order]"]'); + if (!fotoOrderInput.length) { + fotoOrderInput = $(this).find('input[name*="[foto_order]"]'); + } + if (!fotoOrderInput.length) { + fotoOrderInput = $(this).find('input[type="hidden"][name*="foto_order"]'); + } + + if (fotoOrderInput.length) { + // Устанавливаем порядок начиная с 1 + fotoOrderInput.val(index + 1); + } + }); + + // Обновляем основную картинку на первую в новом порядке + updateMainImageFromFirstFile(); +} + +function updateMainImageFromFirstFile() { + // Основная картинка обновляется автоматически на сервере + // после сохранения формы в методе updateMainImageFromFirstMedia + // Здесь ничего не делаем - сервер сам обработает обновление + console.log('Main image will be updated on server side'); +} +