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');
$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];
$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)) {
}
}
+ // Обновляем основную картинку на первую из мультиформы
+ $this->updateMainImageFromFirstMedia($modelEdit, $adminId);
+
$uploadImage = UploadedFile::getInstanceByName('MatrixErpProperty[imageFile]');
if ($uploadImage) {
* @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) {
$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) {
}
$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;
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) {
}
$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,
]
];
'class' => 'gallery-item px-2',
'target' => "_blank",
'rel'=> "noopener noreferrer",
- 'title' => $relaFileName,
+ 'title' => $displayName,
];
$imagesResult[] = Html::a($tagImageThumbRow, $fileName , $options);
* @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
[['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],
];
}
'name' => 'Name',
'description' => 'Description',
'file_id' => 'File ID',
+ 'foto_order' => 'Foto Order',
'date' => 'Date',
'created_admin_id' => 'Created Admin ID',
'created_at' => 'Created At',
}
public function getMediaFiles() {
- return $this->hasMany(MatrixErpMedia::class, ['guid' => 'guid']);
+ return $this->hasMany(MatrixErpMedia::class, ['guid' => 'guid'])->orderBy(['foto_order' => SORT_ASC]);
}
}
/** @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 = [
<?= $form->field($modelMatrixErpProperty, 'id')->hiddenInput()->label(false) ?>
<?= $form->field($modelMatrixErp, 'id')->hiddenInput()->label(false) ?>
<?= $form->field($modelMatrixErpProperty, 'guid')->hiddenInput()->label(false) ?>
+ <?= $form->field($modelMatrixErpProperty, 'image_id')->hiddenInput()->label(false) ?>
<?= $form->field($modelMatrixErpProperty, 'display_name')->textInput() ?>
<?= $form->field($modelMatrixErpProperty, 'weight')->textInput(['type' => 'number', 'step' => 0.01]) ?>
+ <?php
+ // Загружаем данные для мультиинпута в правильном порядке
+ $mediaFilesData = $modelMatrixErpProperty->mediaFiles;
+ ?>
+
<div class="form-group">
<?= $form->field($modelMatrixErpProperty, 'mediaFiles')->widget(MultipleInput::className(), [
+ 'data' => $mediaFilesData,
'min' => 0,
'max' => 100,
'columns' => [
]
],
[
- '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;',
'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 = '<div style="display: flex; flex-direction: row; align-items: center; gap: 4px;">';
+ $result .= '<img src="' . FileService::padWithSlash($data->file->url) . '" alt="' . htmlspecialchars($data->name ?: basename($data->file->url)) . '" style="max-width: 80px; max-height: 60px; object-fit: cover; border: 1px solid #ddd; border-radius: 4px;" onclick="window.open(this.src, \'_blank\');" />';
+ $result .= '<div style="font-size: 10px;">' . $downloadLink . '</div>';
+ $result .= '</div>';
+ } else {
+ // Для других файлов показываем иконку + ссылку на скачивание
+ $result = '<div style="display: flex; align-items: center; gap: 8px;">';
+ $result .= '<i class="fa fa-file-o" style="font-size: 24px; color: #666;"></i>';
+ $result .= '<div style="font-size: 12px;">' . $downloadLink . '</div>';
+ $result .= '</div>';
+ }
}
- return $resultData ?? '';
+
+ return $result;
},
'headerOptions' => [
-// 'style' => 'width: 70px;',
+ 'style' => 'width: 120px;',
]
],
<?php ActiveForm::end(); ?>
</div>
-<script>
- jQuery(".multiple-input-list__item").on("click", ".multiple-input-list__item", function(e) {
- console.log('add-item1');
- // jQuery(".multiple-input-list__item input:file").last().attr("required", "required");
- jQuery(".multiple-input-list__item input:text").last().attr("required", "required");
+<style>
+.sortable-ghost {
+ opacity: 0.4;
+ background: #f0f0f0;
+}
+
+.sortable-chosen {
+ background: #e9ecef;
+}
+
+.sortable-drag {
+ transform: rotate(5deg);
+}
+
+.sortable-item-dragging {
+ opacity: 0.5;
+}
+
+.multiple-input-list__item {
+ transition: all 0.2s ease;
+}
+
+.multiple-input-list__item:hover {
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+}
+
+.sortable-handle {
+ cursor: move;
+ user-select: none;
+}
+
+.sortable-handle:hover {
+ background: #e9ecef !important;
+}
+</style>
+<script>
+ // Обработчик добавления новых элементов
+ $(document).on('multipleinput.afterAddRow', function(e, row) {
+ // Добавляем handle для нового элемента в первую ячейку
+ if (!$(row).find('.sortable-handle').length) {
+ $(row).find('td:first').prepend('<div class="sortable-handle" style="cursor: move; padding: 5px; background: #f8f9fa; border-right: 1px solid #dee2e6; display: inline-block; margin-right: 10px; vertical-align: top;"><i class="fa fa-bars"></i></div>');
+ $(row).find('td:first').css('display', 'flex');
+ }
+ updateNumRowValues();
});
- jQuery(".multiple-input-list__item").on("click", "input:text", function(e) {
- console.log('add-item2');
- // jQuery(".multiple-input-list__item input:file").last().attr("required", "required");
- jQuery(".multiple-input-list__item input:text").last().attr("required", "required");
+ // Обработчик удаления элементов
+ $(document).on('multipleinput.afterRemoveRow', function(e) {
+ updateNumRowValues();
});
- jQuery(".multiple-input-list__item").on("click", "input:file", function(e) {
- console.log('add-item3');
- // jQuery(".multiple-input-list__item input:file").last().attr("required", "required");
- jQuery(".multiple-input-list__item input:text").last().attr("required", "required");
- });
</script>
\ No newline at end of file
$(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('<div class="sortable-handle" style="cursor: move; padding: 5px; background: #f8f9fa; border-right: 1px solid #dee2e6; display: inline-block; margin-right: 10px; vertical-align: top;"><i class="fa fa-bars"></i></div>');
+ $(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('<div class="sortable-handle" style="cursor: move; padding: 5px; background: #f8f9fa; border-right: 1px solid #dee2e6; display: inline-block; vertical-align: top;"><i class="fa fa-bars"></i></div>');
+ }
+ });
+ }
+}
+
+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');
+}
+