Доработка страницы товаров и их администрирования

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2021-04-04 23:54:34 +10:00
parent 4c61ccbb65
commit 7ca19da826
18 changed files with 900 additions and 327 deletions

88
composer.lock generated
View File

@ -351,16 +351,16 @@
},
{
"name": "egulias/email-validator",
"version": "3.1.0",
"version": "3.1.1",
"source": {
"type": "git",
"url": "https://github.com/egulias/EmailValidator.git",
"reference": "62c3b73c581c834885acf6e120b412b76acc495a"
"reference": "c81f18a3efb941d8c4d2e025f6183b5c6d697307"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/62c3b73c581c834885acf6e120b412b76acc495a",
"reference": "62c3b73c581c834885acf6e120b412b76acc495a",
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/c81f18a3efb941d8c4d2e025f6183b5c6d697307",
"reference": "c81f18a3efb941d8c4d2e025f6183b5c6d697307",
"shasum": ""
},
"require": {
@ -407,7 +407,7 @@
],
"support": {
"issues": "https://github.com/egulias/EmailValidator/issues",
"source": "https://github.com/egulias/EmailValidator/tree/3.1.0"
"source": "https://github.com/egulias/EmailValidator/tree/3.1.1"
},
"funding": [
{
@ -415,7 +415,7 @@
"type": "github"
}
],
"time": "2021-03-07T14:33:28+00:00"
"time": "2021-04-01T18:37:14+00:00"
},
{
"name": "ezyang/htmlpurifier",
@ -783,7 +783,7 @@
"source": {
"type": "git",
"url": "https://git.hood.su/mirzaev/yii2/arangodb",
"reference": "a7abed56d6a3d8d03b1c99e620a9c0ef3887bfcb"
"reference": "7a6e6fbadafc65ecb516d5a2dc87bd83f7c19274"
},
"require": {
"php": "^8.0.0",
@ -825,7 +825,7 @@
"ArangoDb",
"yii2"
],
"time": "2021-03-21T19:01:43+00:00"
"time": "2021-03-29T00:20:46+00:00"
},
{
"name": "mirzaev/yii2-arangodb-sessions",
@ -833,12 +833,11 @@
"source": {
"type": "git",
"url": "https://git.hood.su/mirzaev/yii2/arangodb/sessions/",
"reference": "80c16734945a35a0d49170b9a1a854efb322fefb"
"reference": "e709d3d9145ccdec0dc2c50390d3b73b73fb1109"
},
"require": {
"mirzaev/yii2-arangodb": "~2.1.x-dev",
"php": "^8.0.0",
"triagens/arangodb": "~3.2",
"yiisoft/yii2": "2.*"
},
"require-dev": {
@ -863,14 +862,14 @@
"role": "Developer"
}
],
"description": "Yii\\web\\DbSession implementation for use with ArangoDB",
"description": "yii\\web\\DbSession implementation for use with ArangoDB",
"homepage": "https://git.hood.su/mirzaev/yii2/arangodb/sessions",
"keywords": [
"ArangoDb",
"DbSession",
"yii2"
],
"time": "2021-03-21T18:39:52+00:00"
"time": "2021-03-29T00:19:31+00:00"
},
{
"name": "moonlandsoft/yii2-phpexcel",
@ -2384,16 +2383,16 @@
},
{
"name": "codeception/codeception",
"version": "4.1.19",
"version": "4.1.20",
"source": {
"type": "git",
"url": "https://github.com/Codeception/Codeception.git",
"reference": "138dc9345a81ec994dcd6b9680c501a752a37b00"
"reference": "d8b16e13e1781dbc3a7ae8292117d520c89a9c5a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Codeception/Codeception/zipball/138dc9345a81ec994dcd6b9680c501a752a37b00",
"reference": "138dc9345a81ec994dcd6b9680c501a752a37b00",
"url": "https://api.github.com/repos/Codeception/Codeception/zipball/d8b16e13e1781dbc3a7ae8292117d520c89a9c5a",
"reference": "d8b16e13e1781dbc3a7ae8292117d520c89a9c5a",
"shasum": ""
},
"require": {
@ -2467,7 +2466,7 @@
],
"support": {
"issues": "https://github.com/Codeception/Codeception/issues",
"source": "https://github.com/Codeception/Codeception/tree/4.1.19"
"source": "https://github.com/Codeception/Codeception/tree/4.1.20"
},
"funding": [
{
@ -2475,7 +2474,7 @@
"type": "open_collective"
}
],
"time": "2021-03-28T13:26:08+00:00"
"time": "2021-04-02T16:41:51+00:00"
},
{
"name": "codeception/lib-asserts",
@ -3057,20 +3056,22 @@
},
{
"name": "fakerphp/faker",
"version": "v1.13.0",
"version": "v1.14.1",
"source": {
"type": "git",
"url": "https://github.com/FakerPHP/Faker.git",
"reference": "ab3f5364d01f2c2c16113442fb987d26e4004913"
"reference": "ed22aee8d17c7b396f74a58b1e7fefa4f90d5ef1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/FakerPHP/Faker/zipball/ab3f5364d01f2c2c16113442fb987d26e4004913",
"reference": "ab3f5364d01f2c2c16113442fb987d26e4004913",
"url": "https://api.github.com/repos/FakerPHP/Faker/zipball/ed22aee8d17c7b396f74a58b1e7fefa4f90d5ef1",
"reference": "ed22aee8d17c7b396f74a58b1e7fefa4f90d5ef1",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
"php": "^7.1 || ^8.0",
"psr/container": "^1.0",
"symfony/deprecation-contracts": "^2.2"
},
"conflict": {
"fzaninotto/faker": "*"
@ -3078,9 +3079,20 @@
"require-dev": {
"bamarni/composer-bin-plugin": "^1.4.1",
"ext-intl": "*",
"phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.4.2"
"symfony/phpunit-bridge": "^4.4 || ^5.2"
},
"suggest": {
"ext-curl": "Required by Faker\\Provider\\Image to download images.",
"ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.",
"ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.",
"ext-mbstring": "Required for multibyte Unicode string functionality."
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "v1.15-dev"
}
},
"autoload": {
"psr-4": {
"Faker\\": "src/Faker/"
@ -3103,9 +3115,9 @@
],
"support": {
"issues": "https://github.com/FakerPHP/Faker/issues",
"source": "https://github.com/FakerPHP/Faker/tree/v1.13.0"
"source": "https://github.com/FakerPHP/Faker/tree/v.1.14.1"
},
"time": "2020-12-18T16:50:48+00:00"
"time": "2021-03-30T06:27:33+00:00"
},
{
"name": "guzzlehttp/psr7",
@ -5409,16 +5421,16 @@
},
{
"name": "symfony/console",
"version": "v5.2.5",
"version": "v5.2.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "938ebbadae1b0a9c9d1ec313f87f9708609f1b79"
"reference": "35f039df40a3b335ebf310f244cb242b3a83ac8d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/938ebbadae1b0a9c9d1ec313f87f9708609f1b79",
"reference": "938ebbadae1b0a9c9d1ec313f87f9708609f1b79",
"url": "https://api.github.com/repos/symfony/console/zipball/35f039df40a3b335ebf310f244cb242b3a83ac8d",
"reference": "35f039df40a3b335ebf310f244cb242b3a83ac8d",
"shasum": ""
},
"require": {
@ -5486,7 +5498,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v5.2.5"
"source": "https://github.com/symfony/console/tree/v5.2.6"
},
"funding": [
{
@ -5502,7 +5514,7 @@
"type": "tidelift"
}
],
"time": "2021-03-06T13:42:15+00:00"
"time": "2021-03-28T09:42:18+00:00"
},
{
"name": "symfony/css-selector",
@ -6400,16 +6412,16 @@
},
{
"name": "symfony/string",
"version": "v5.2.4",
"version": "v5.2.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "4e78d7d47061fa183639927ec40d607973699609"
"reference": "ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/4e78d7d47061fa183639927ec40d607973699609",
"reference": "4e78d7d47061fa183639927ec40d607973699609",
"url": "https://api.github.com/repos/symfony/string/zipball/ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572",
"reference": "ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572",
"shasum": ""
},
"require": {
@ -6463,7 +6475,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v5.2.4"
"source": "https://github.com/symfony/string/tree/v5.2.6"
},
"funding": [
{
@ -6479,7 +6491,7 @@
"type": "tidelift"
}
],
"time": "2021-02-16T10:20:28+00:00"
"time": "2021-03-17T17:12:15+00:00"
},
{
"name": "symfony/yaml",

View File

@ -4,6 +4,7 @@ $config = [
'id' => 'skillparts',
'basePath' => dirname(__DIR__),
'bootstrap' => ['log'],
'defaultRoute' => 'main',
'aliases' => [
'@vendor' => dirname(__DIR__) . '/../../../vendor',
'@bower' => '@vendor/bower-asset',
@ -76,7 +77,7 @@ $config = [
'controller' => 'main'
],
'product/<catn:[^/]+>' => 'product/index',
'product/<catn:[^/]+>/<action:(write|edit|delete)>/<target:(title|catn|desc|image)>' => 'product/<action>-<target>',
'product/<catn:[^/]+>/<action:(write|edit|delete)>/<target:(title|catn|dscr|image)>' => 'product/<action>-<target>',
'orders' => 'order/index'
],
],

View File

@ -138,7 +138,7 @@ class ProductController extends Controller
}
}
public function actionEditDesc(string $catn): array|string|null
public function actionEditDscr(string $catn): array|string|null
{
// Инициализация
$return = [
@ -159,7 +159,7 @@ class ProductController extends Controller
// Инициализация
$text = yii::$app->request->get('text') ?? yii::$app->request->post('text') ?? 'Без названия';
$product->desc = $text;
$product->dscr = $text;
if ($product->save()) {
// Товар обновлён
@ -188,6 +188,63 @@ class ProductController extends Controller
}
}
public function actionEditDmns(string $catn): array|string|null
{
// Инициализация
$return = [
'_csrf' => yii::$app->request->getCsrfToken()
];
if (is_null($catn)) {
// Не получен артикул
yii::$app->response->statusCode = 500;
goto end;
}
if ($product = Product::searchByCatn($catn)) {
// Товар найден
// Инициализация
$text = yii::$app->request->post('text') ?? yii::$app->request->get('text') ?? '0';
$text or $text = '0';
$dimension = yii::$app->request->post('dimension') ?? yii::$app->request->get('dimension') ?? 'x';
$product->dmns = array_merge(
$product->dmns ?? [],
[
$dimension => $text
]
);
if ($product->save()) {
// Товар обновлён
$return['dimension'] = $text;
}
}
/**
* Конец алгоритма
*/
end:
if (yii::$app->request->isPost) {
// POST-запрос
yii::$app->response->format = Response::FORMAT_JSON;
return $return;
}
if ($model = Product::searchByCatn($catn)) {
return $this->render('index', compact('model'));
} else {
return $this->redirect('/');
}
}
public function actionWriteImage(string $catn): array|string|null
{
// Инициализация
@ -203,17 +260,137 @@ class ProductController extends Controller
goto end;
}
if ($model = Product::searchByCatn($catn)) {
if ($product = Product::searchByCatn($catn)) {
// Товар найден
// Инициализация
$model->file_image = UploadedFile::getInstancesByName('images');
$model->scenario = $model::SCENARIO_IMPORT_IMAGE;
$product->file_image = UploadedFile::getInstancesByName('images');
$product->scenario = $product::SCENARIO_IMPORT_IMAGE;
if ($model->importImages() > 0) {
if ($product->importImages() > 0) {
// Товар обновлён
$return['main'] = $this->renderPartial('index', compact('model'));
$return['main'] = $this->renderPartial('index', ['model' => $product]);
}
}
/**
* Конец алгоритма
*/
end:
if (yii::$app->request->isPost) {
// POST-запрос
yii::$app->response->format = Response::FORMAT_JSON;
return $return;
}
if ($model = Product::searchByCatn($catn)) {
return $this->render('index', compact('model'));
} else {
return $this->redirect('/');
}
}
public function actionDeleteImage(string $catn): array|string|null
{
// Инициализация
$return = [
'_csrf' => yii::$app->request->getCsrfToken()
];
$index = yii::$app->request->post('index') ?? yii::$app->request->get('index');
if (is_null($catn) || is_null($index)) {
// Не получены обязательные параметры
yii::$app->response->statusCode = 500;
goto end;
}
if ($product = Product::searchByCatn($catn)) {
// Товар найден
// Инициализация (буфер нужен из-за кривых сеттеров)
$buffer = $product->imgs;
// Удаление
unset($buffer[$index]);
// Запись
$product->imgs = $buffer;
if ($product->save()) {
// Товар обновлён
$return['main'] = $this->renderPartial('index', ['model' => $product]);
}
}
/**
* Конец алгоритма
*/
end:
if (yii::$app->request->isPost) {
// POST-запрос
yii::$app->response->format = Response::FORMAT_JSON;
return $return;
}
if ($model = Product::searchByCatn($catn)) {
return $this->render('index', compact('model'));
} else {
return $this->redirect('/');
}
}
public function actionWriteCover(string $catn): array|string|null
{
// Инициализация
$return = [
'_csrf' => yii::$app->request->getCsrfToken()
];
$index = yii::$app->request->post('index') ?? yii::$app->request->get('index');
if (is_null($catn) || is_null($index)) {
// Не получены обязательные параметры
yii::$app->response->statusCode = 500;
goto end;
}
if ($product = Product::searchByCatn($catn)) {
// Товар найден
// Инициализация (буфер нужен из-за кривых сеттеров)
$buffer = $product->imgs;
foreach($buffer as $image_index => &$image) {
// Перебор изображений
if ($image_index === (int) $index) {
// Найдено запрашиваемое изображение
// Установка обложки
$image['covr'] = true;
} else {
$image['covr'] = false;
}
}
// Запись
$product->imgs = $buffer;
if ($product->save()) {
// Товар обновлён
$return['main'] = $this->renderPartial('index', ['model' => $product]);
}
}

View File

@ -59,7 +59,7 @@ class SearchController extends Controller
$timer = 0;
// Период пропуска запросов (в секундах)
$period = 5;
$period = 3;
$keep_connect = false;
$sanction = false;
$sanction_condition = ($session['last_request'] + $period - time()) < $period;
@ -128,7 +128,17 @@ class SearchController extends Controller
$limit = yii::$app->request->isPost ? 10 : 20;
if ($response = Product::searchByPartialCatn($query, $limit, ['catn' => 'catn', '_key' => '_key'])) {
if ($response = Product::searchByPartialCatn($query, $limit, [
'_key' => '_key',
'catn' => 'catn',
'name' => 'name',
// Баг с названием DESC
'dscr' => 'dscr',
'catg' => 'catg',
'imgs' => 'imgs',
'prod' => 'prod',
'dmns' => 'dmns',
])) {
// Данные найдены по поиску в полях Каталожного номера
foreach ($response as &$row) {
@ -205,7 +215,9 @@ class SearchController extends Controller
goto keep_connect_wait;
}
return $this->render('/search/index', compact('response', 'timer'));
$advanced = true;
return $this->render('/search/index', compact('response', 'timer', 'advanced'));
}
}

View File

@ -157,12 +157,39 @@ abstract class Document extends ActiveRecord
/**
* Проверка на то, что в свойство передан массив
*/
public function arrayValidator(string $attribute, array $params = null): bool
public function arrayValidator(string $attribute, array $params = null): void
{
if (is_array($this->$attribute)) {
return true;
return;
} else {
$this->addError($attribute, 'Передан не массив');
}
return false;
$this->addError($attribute, 'Неизвестная ошибка');
}
/**
* Проверка на то, что в свойство передан массив и он хранит циферные значения
*/
public function arrayWithNumbersValidator(string $attribute, array $params = null): void
{
try {
if (is_array($this->$attribute)) {
foreach ($this->$attribute as $value) {
if (!(bool) preg_match('/^[0-9]*$/m', $value)) {
$this->addError($attribute, 'В массиве найдены запрещённые символы');
}
}
return;
} else {
$this->addError($attribute, 'Передан не массив');
}
} catch (Exception $e) {
$this->addError($attribute, $e->getMessage());
}
$this->addError($attribute, 'Неизвестная ошибка');
}
}

View File

@ -38,6 +38,11 @@ class Notification extends Document
*/
const TYPE_WARNING = 'warning';
/**
* Тип уведомления: ошибка
*/
const TYPE_ERROR = 'error';
/**
* Цель для отправки уведомления
*

View File

@ -9,7 +9,7 @@ use yii\web\UploadedFile;
use yii\imagine\Image;
use app\models\traits\SearchByEdge;
use Exception;
use moonland\phpexcel\Excel;
/**
@ -75,12 +75,12 @@ class Product extends Document
[
'catn',
'name',
'desc',
'ocid',
// В библеотеке есть баг на название DESC (неизвестно в моей или нет)
'dscr',
'prod',
'dmns',
'imgs',
'time',
'oemn',
'cost'
'time'
]
);
}
@ -95,12 +95,11 @@ class Product extends Document
[
'catn' => 'Каталожный номер (catn)',
'name' => 'Название (name)',
'desc' => 'Описание (desc)',
'ocid' => 'Идентификатор 1C (ocid)',
'dscr' => 'Описание (dscr)',
'prod' => 'Производитель (prod)',
'dmns' => 'Габариты (dmns)',
'imgs' => 'Изображения (imgs)',
'time' => 'Срок доставки (time)',
'oemn' => 'OEM номера (oemn)',
'cost' => 'Стоимость (cost)',
'file_excel' => 'Документ (file_excel)',
'file_image' => 'Изображение (file_image)',
'group' => 'Группа (group)'
@ -129,12 +128,14 @@ class Product extends Document
'message' => '{attribute} должен быть строкой'
],
[
[
'oemn',
'imgs'
],
'imgs',
'arrayValidator',
'message' => '{attribute} должен быть массивом.'
'message' => '{attribute} должен быть массивом'
],
[
'dmns',
'arrayWithNumbersValidator',
'message' => '{attribute} должен быть массивом и хранить циферные значения'
],
[
'file_excel',
@ -176,52 +177,6 @@ class Product extends Document
);
}
/**
* Инициализация продукта
*
* @param string $catn Артикул, каталожный номер
*/
public static function initEmpty(string $catn): self|array
{
$oemn = self::searchOemn($catn);
if (count($oemn) === 1) {
// Передан только один артикул
if ($model = self::searchByCatn($catn)) {
// Продукт уже существует
return $model;
}
// Запись пустого продукта
return self::writeEmpty($catn);
}
// Инициализация
$models = [];
foreach ($oemn as $catn) {
// Перебор всех найденных артикулов
if ($model = self::searchByCatn($catn)) {
// Продукт уже существует
continue;
}
// Запись
if ($model = self::writeEmpty($catn)) {
// Записано
// Запись в массив сохранённых моделей
$models[] = $model;
}
}
return $models;
}
/**
* Запись пустого продукта
*/
@ -237,28 +192,6 @@ class Product extends Document
return $model->save() ? $model : null;
}
/**
* Поиск OEM номеров
*
* @param string $oemn Необработанная строка с OEM-номерами
* @param string $delimiters Разделители
*
* @todo НЕ ЗАБЫТЬ СДЕЛАТЬ НАСТРОЙКУ РАЗДЕЛИТЕЛЕЙ
*
* @return array OEM-номера
*/
public static function searchOemn(string $oemn, string $delimiters = '\s\+\/,'): array
{
// Инициализация
$catn = [];
// Конвертация
preg_match_all("/[^$delimiters]+/", $oemn, $catn);
return $catn[0];
}
/**
* Импорт изображений
*
@ -266,12 +199,12 @@ class Product extends Document
*/
public function importImages(): int
{
if ($this->validate()) {
// Проверка пройдена
// Инициализация
$amount = 0;
if ($this->validate()) {
// Проверка пройдена
foreach ($this->file_image as $file) {
// Перебор обрабатываемых изображений
@ -307,16 +240,19 @@ class Product extends Document
Image::resize(YII_PATH_PUBLIC . $catalog . '/' . $file->baseName . '.' . $file->extension, 150, 150)
->save(YII_PATH_PUBLIC . $catalog_h150 . '/' . $file->baseName . '.' . $file->extension, ['quality' => 80]);
// Инициализация
$this->imgs ?? $this->imgs = [];
// Запись в базу данных
$this->imgs = array_merge(
$this->imgs ?? [],
$this->imgs,
[[
'covr' => count($this->imgs) === 0 ? true : false,
'orig' => $catalog . '/' . $file->baseName . '.' . $file->extension,
'h150' => $catalog_h150 . '/' . $file->baseName . '.' . $file->extension
]]
);
$this->scenario = self::SCENARIO_WRITE;
if ($this->save()) {
@ -328,6 +264,22 @@ class Product extends Document
}
}
if ($this->hasErrors()) {
// Получены ошибки
foreach ($this->getErrors() as $attribute => $errors) {
// Перебор атрибутов
foreach ($errors as $error) {
// Перебор ошибок атрибутов
$label = $this->getAttributeLabel($attribute);
Notification::_write("$label: $error", type: Notification::TYPE_ERROR);
}
}
}
return $amount;
}

View File

@ -52,7 +52,9 @@ class Supply extends Product implements ProductInterface
return array_merge(
parent::attributes(),
[
'onec'
'cost',
'onec',
'ocid'
]
);
}
@ -65,7 +67,28 @@ class Supply extends Product implements ProductInterface
return array_merge(
parent::attributeLabels(),
[
'onec' => 'Данные 1С'
'cost' => 'Стоимость (cost)',
'onec' => 'Данные 1С',
'ocid' => 'Идентификатор 1C (ocid)'
]
);
}
/**
* Правила
*/
public function rules(): array
{
return array_merge(
parent::rules(),
[
// [
// [
// 'oemn'
// ],
// 'arrayValidator',
// 'message' => '{attribute} должен быть массивом.'
// ]
]
);
}
@ -281,7 +304,7 @@ class Supply extends Product implements ProductInterface
// Создание продукта (временно заблокировано)
// // Инициализация п̸̨͇͑͋͠р̷̬̂́̀̊о̸̜̯̹̅͒͘͝д̴̨̨̨̟̈́̆у̴̨̭̮̠́͋̈́к̴̭͊̋̎т̵̛̣͈̔̐͆а̵̨͖͑
// $product = Product::initEmpty($this->catn);
// $product = self::initEmpty($this->catn);
// if (!is_array($product)) {
// // Создался только один товар и вернулся в виде модели
@ -296,7 +319,7 @@ class Supply extends Product implements ProductInterface
// // Перебор артикулов из массива ОЕМ-номеров
// // Инициализация и запись
// $product[] = Product::initEmpty($oem);
// $product[] = self::initEmpty($oem);
// }
// }
@ -380,6 +403,73 @@ class Supply extends Product implements ProductInterface
return null;
}
/**
* Инициализация продукта
*
* @param string $catn Артикул, каталожный номер
*/
public static function initEmpty(string $catn): self|array
{
$oemn = self::searchOemn($catn);
if (count($oemn) === 1) {
// Передан только один артикул
if ($model = Product::searchByCatn($catn)) {
// Продукт уже существует
return $model;
}
// Запись пустого продукта
return Product::writeEmpty($catn);
}
// Инициализация
$models = [];
foreach ($oemn as $catn) {
// Перебор всех найденных артикулов
if ($model = Product::searchByCatn($catn)) {
// Продукт уже существует
continue;
}
// Запись
if ($model = Product::writeEmpty($catn)) {
// Записано
// Запись в массив сохранённых моделей
$models[] = $model;
}
}
return $models;
}
/**
* Поиск OEM номеров
*
* @param string $oemn Необработанная строка с OEM-номерами
* @param string $delimiters Разделители
*
* @todo НЕ ЗАБЫТЬ СДЕЛАТЬ НАСТРОЙКУ РАЗДЕЛИТЕЛЕЙ
*
* @return array OEM-номера
*/
public static function searchOemn(string $oemn, string $delimiters = '\s\+\/,'): array
{
// Инициализация
$catn = [];
// Конвертация
preg_match_all("/[^$delimiters]+/", $oemn, $catn);
return $catn[0];
}
/**
* Запись цены из 1С
*/

View File

@ -6,11 +6,12 @@ $this->title = $name;
?>
<div id="page_error" class="container py-3">
<h1><?= Html::encode($this->title) ?></h1>
<div id="page_error" class="container d-flex flex-column">
<div class="my-auto">
<h1 class="text-center mb-4 gilroy"><b><?= Html::encode($this->title) ?></b></h1>
<div class="alert alert-danger">
<div class="col-md-8 col-lg-6 mx-auto alert alert_white">
<?= nl2br(Html::encode($message)) ?>
</div>
</div>
</div>

View File

@ -16,16 +16,34 @@ use app\models\Product;
<div id="product_slider" class="row px-3 profile_panel">
<div class="col-1 product_slider_preview p-0 pr-3 mb-3">
<?php
foreach ($model['imgs'] ?? [null] as $key => $image) {
// Перебор изображений
// Инициализация
$covr_not_found = true;
// Проверка на то, что обложка установлена
foreach (!empty($model['imgs']) && is_array($model['imgs']) ? $model['imgs'] : [] as $image) {
if ($image['covr'] ?? false) {
$covr_not_found = false;
}
}
foreach (!empty($model['imgs']) && is_array($model['imgs']) ? $model['imgs'] : [] as $key => $image) {
// Перебор изображений для генерации обложек
// Инициализация
$h150 = $image['h150'] ?? '/img/covers/h150/product.png';
// Генерация предпросмотра изображения
if (($image['covr'] ?? false) || ($covr_not_found && $key === 0)) {
echo <<<HTML
<label class="p-0 mb-2" for="product_slider_image_$key">
<img class="img-fluid rounded" src="$h150"/>
<label class="p-0 mb-2 rounded unselectable active" for="product_slider_image_$key" onclick="return product_image_choose('$key', this.parentElement);">
HTML;
} else {
echo <<<HTML
<label class="p-0 mb-2 rounded unselectable" for="product_slider_image_$key" onclick="return product_image_choose('$key', this.parentElement);">
HTML;
}
echo <<<HTML
<img class="img-fluid" src="$h150"/>
</label>
HTML;
}
@ -37,39 +55,57 @@ use app\models\Product;
|| yii::$app->user->identity->type === 'moderator')
) : ?>
<input id="product_slider_image_new" type="file" class="d-none" onchange="return product_panel_images_write('<?= $model['catn'] ?>', this);" multiple />
<label class="p-0 mb-2" for="product_slider_image_new" role="button">
<label class="p-0 mb-2 button_write unselectable" for="product_slider_image_new" role="button">
<img class="img-fluid rounded" src="/img/covers/h150/product_new.png" />
</label>
<?php endif ?>
</div>
<div class="product_slider_image">
<?php
// Инициализация
$imgs = $model['imgs'] ?? [null];
$checked = '';
foreach ($model['imgs'] ?? [null] as $key => $image) {
// Перебор изображений
foreach (!empty($model['imgs']) && is_array($model['imgs']) ? $model['imgs'] : [] as $key => $image) {
// Перебор изображений для генерации полных версий
// Инициализация
$name = $image['name'] ?? 'Без названия';
$orig = $image['orig'] ?? '/img/covers/product.png';
$covr = $image['covr'] ?? false;
if ($covr || count($imgs) < 2) {
// Если это изображение является обложкой
if (($image['covr'] ?? false) || ($covr_not_found && $key === 0)) {
// Если это изображение является обложкой или обложка не найдена
// Реинициализация
$checked = 'checked';
}
// Генерация изображения
if (
!yii::$app->user->isGuest
&& (yii::$app->user->identity->type === 'administrator'
|| yii::$app->user->identity->type === 'moderator')
) {
$catn = $model['catn'];
echo <<<HTML
<input type="radio" id="product_slider_image_$key" name="slider" $checked/>
<div class="col p-0">
<div class="col p-0 unselectable">
<img class="mb-3 img-fluid w-100 rounded" src="$orig"/>
<div class="d-flex justify-content-center">
<button class="flex-grow-1 mr-3 btn button_blue button_clean" onclick="return product_panel_images_cover_write('$catn', '$key');">Сделать основной обложкой</button>
<button class="btn button_red button_clean" onclick="return product_panel_images_delete('$catn', '$key');">Удалить</button>
</div>
</div>
HTML;
} else {
echo <<<HTML
<input type="radio" id="product_slider_image_$key" name="slider" $checked/>
<div class="col p-0 unselectable">
<img class="img-fluid rounded" src="$orig"/>
</div>
HTML;
}
// Деинициализация
$checked = '';
@ -87,14 +123,16 @@ use app\models\Product;
<?php else : ?>
<h3 id="title_<?= $model['catn'] ?>" class="my-auto"><?= $model['name'] ?? 'Без названия' ?></h3>
<?php endif ?>
</div>
<div class="row mb-2">
<?php if (
!yii::$app->user->isGuest
&& (yii::$app->user->identity->type === 'administrator'
|| yii::$app->user->identity->type === 'moderator')
) : ?>
<h5 id="catn_<?= $model['catn'] ?>" class="ml-auto my-auto d-flex pointer-event" role="button" onclick="return product_panel_catn_edit('<?= $model['catn'] ?>', this, true);">
<?= $model['catn'] ?>
<h6 id="catn_<?= $model['catn'] ?>" class="mr-auto my-auto pointer-event" role="button" onclick="return product_panel_catn_edit('<?= $model['catn'] ?>', this, true);">
<?= $model['catn'] ?? '' ?>
<!-- <small class="d-flex">
<a class="text-dark my-auto ml-3" title="Редактировать" role="button" onclick="return product_panel_edit('<?= $model['catn'] ?>');">
<i class="fas fa-edit"></i>
@ -103,21 +141,60 @@ use app\models\Product;
<i class="fas fa-trash-alt"></i>
</a>
</small> -->
</h5>
</h6>
<h6 id="prod_<?= $model['catn'] ?>" class="my-0">
<?= $model['prod'] ?? 'Неизвестно' ?>
</h6>
<?php else : ?>
<h5 id="catn_<?= $model['catn'] ?>" class="ml-auto my-auto d-flex">
<h6 id="catn_<?= $model['catn'] ?>" class="mr-auto my-0">
<?= $model['catn'] ?>
</h5>
</h6>
<h6 id="prod_<?= $model['catn'] ?>" class="my-0">
<?= $model['prod'] ?? 'Неизвестно' ?>
</h6>
<?php endif ?>
</div>
<div class="dropdown-divider px-0 mb-3"></div>
<div class="dropdown-divider px-0 mb-2"></div>
<?php if (
!yii::$app->user->isGuest
&& (yii::$app->user->identity->type === 'administrator'
|| yii::$app->user->identity->type === 'moderator')
) : ?>
<section id="prod_<?= $model['catn'] ?>_info" class="col mb-1">
<p class="form-control-sm">
<b class="mr-1">Габариты:</b>
<span id="prod_<?= $model['catn'] ?>_dmns_x" class="ml-1 pointer-event" role="button" onclick="return product_panel_dimensions_edit('<?= $model['catn'] ?>', this, 'x');"><?= empty($model['dmns']['x']) ? '0' : $model['dmns']['x'] ?></span>
<span class="mr-1">см</span>
<small>x</small>
<span id="prod_<?= $model['catn'] ?>_dmns_y" class="ml-1 pointer-event" role="button" onclick="return product_panel_dimensions_edit('<?= $model['catn'] ?>', this, 'y');"><?= empty($model['dmns']['y']) ? '0' : $model['dmns']['y'] ?></span>
<span class="mr-1">см</span>
<small>x</small>
<span id="prod_<?= $model['catn'] ?>_dmns_z" class="ml-1 pointer-event" role="button" onclick="return product_panel_dimensions_edit('<?= $model['catn'] ?>', this, 'z');"><?= empty($model['dmns']['z']) ? '0' : $model['dmns']['z'] ?></span>
<span class="mr-1">см</span>
</p>
</section>
<?php else : ?>
<section id="prod_<?= $model['catn'] ?>_info" class="col mb-1">
<p class="form-control-sm">
<b class="mr-1">Габариты:</b>
<span id="prod_<?= $model['catn'] ?>_dmns_x" class="mx-1"><?= $model['dmns']['x'] ?? '0' ?></span>
<small>x</small>
<span id="prod_<?= $model['catn'] ?>_dmns_y" class="mx-1"><?= $model['dmns']['y'] ?? '0' ?></span>
<small>x</small>
<span id="prod_<?= $model['catn'] ?>_dmns_z" class="mx-1"><?= $model['dmns']['z'] ?? '0' ?></span>
</p>
</section>
<?php endif ?>
<div class="row mb-3 h-100 product_panel d-flex flex-column">
<?php if (
!yii::$app->user->isGuest
&& (yii::$app->user->identity->type === 'administrator'
|| yii::$app->user->identity->type === 'moderator')
) : ?>
<p id="description_<?= $model['catn'] ?>" class="mt-0 ml-0 text-break pointer-event product_description" role="button" onclick="return product_panel_description_edit('<?= $model['catn'] ?>', this);"><?= $model['desc'] ?? 'Без описания' ?></p>
<p id="description_<?= $model['catn'] ?>" class="mt-0 ml-0 text-break pointer-event product_description" role="button" onclick="return product_panel_description_edit('<?= $model['catn'] ?>', this);"><?= $model['dscr'] ?? 'Без описания' ?></p>
<?php else : ?>
<p id="description_<?= $model['catn'] ?>" class="mt-0 ml-0 text-break product_description"><?= $model['name'] ?? 'Без описания' ?></p>
<?php endif ?>

View File

@ -1,11 +1,10 @@
<link href="/css/pages/search.css" rel="stylesheet">
<div id="page_search" class="container h-100">
<div class="row py-3">
<div id="page_search" class="container flex-grow-1 d-flex">
<?php if (isset($timer) && $timer > 0) : ?>
<div class="row py-3 w-100">
<section class="col">
<?php
if (isset($timer) && $timer > 0) {
echo <<<HTML
<div class="d-flex flex-column mx-auto">
<p class="px-2 mb-1 text-center d-flex justify-content-center">Слишком частые запросы, повторите попытку через: $timer секунд</p>
<small class="mb-3 text-center d-flex justify-content-center">Подождите или нажмите на кнопку вручную</small>
@ -14,28 +13,56 @@
<script type="text/javascript">
setTimeout('window.location.reload()', $timer + '000');
</script>
HTML;
} else {
if (isset($response) && is_array($response) && $response) {
<?php else : ?>
<?php if (isset($response) && is_array($response) && $response) : ?>
<div class="row py-3 w-100">
<section class="col">
<?php
// Инициализация
$covr_not_found = true;
$i = 0;
$amount = count($response);
?>
foreach ($response as $row) {
// $name = $row['name'];
// $catn = $row['catn'];
<?php foreach ($response as $row) : ?>
<?php
// Инициализация
extract($row);
isset($name) ?: $name = 'Без названия';
isset($catg) ?: $catg = 'Категория';
isset($covr) ?: $covr = '/img/covers/h150/product.png';
$name ?? $name = 'Без названия';
// $dscr ?? $dscr = 'Описание';
$catg ?? $catg = 'Категория';
$covr = null;
foreach ($imgs ?? [] as $img) {
// Перебор изображений для обложки
if ($img['covr'] ?? false) {
// Обложка найдена
$covr = $img['h150'];
break;
}
}
if (is_null($covr)) {
// Обложка не инициализирована
if (!$covr = $imgs[0]['h150'] ?? false) {
$covr = '/img/covers/h150/product.png';
}
}
$supplies_html = '';
?>
<?php foreach ($supplies as $supply) : ?>
foreach ($supplies as $supply) {
// Конкатенация HTML кода
<?php
// Инициализация артикула
$catn = $supply['catn'] ?? $supply['onec']['Артикул'];
@ -52,7 +79,6 @@
$amount .= ' шт';
}
if ($amount_raw < 1 || $price_raw < 1) {
// Нет в наличии или цена 0 рублей
@ -69,7 +95,6 @@
HTML;
}
$supplies_html .= <<<HTML
<div class="row my-auto m-0 h-100 text-right">
<small class="col-2 my-auto ml-auto">
@ -84,67 +109,58 @@
$button_cart
</div>
HTML;
}
?>
<?php endforeach ?>
echo <<<HTML
<div class="col pb-2">
<div class="row p-2 rounded">
<img class="ml-0 h-100 img-fluid rounded" src="$covr"/>
<img class="ml-0 h-100 img-fluid rounded" src="<?= $covr ?>" />
<div class="col-3 ml-3 p-0 d-flex flex-column">
<a class="my-auto text-dark" href="/product/$catn">
<h5 class="m-0">$name</h5>
<h6 class="m-0"><small>$catn</small></h6>
<a class="my-auto text-dark" href="/product/<?= $catn ?>">
<h5 class="m-0"><?= $name ?></h5>
<h6 class="m-0"><small><?= $catn ?></small></h6>
</a>
</div>
<div class="col-1 ml-2 p-0 d-flex flex-column">
<a class="my-auto text-dark" href="/product/$catn">
<small>$catg</small>
<a class="my-auto text-dark" href="/product/<?= $catn ?>">
<small><?= $catg ?></small>
</a>
</div>
<div class="col ml-2 p-0 d-flex flex-column">
$supplies_html
<?= $supplies_html ?>
</div>
</div>
</div>
HTML;
// echo <<<HTML
// <div class="col-3 p-2 d-flex flex-column search_card rounded">
// <div class="col">
// <div class="row mb-2">
// <img class="w-100 img-fluid rounded" src="$covr"/>
// </div>
// <div class="row mb-2">
// <a class="mx-auto text-dark" href="/product/$catn"><h5 class="m-0">$catn</h5></a>
// </div>
// <div class="row px-2 mb-2">
// <small class="mt-auto">{$amnt}шт</small>
// <p class="mr-0">1000р</p>
// </div>
// <div class="row">
// <button class="btn button_grey button_clean w-100 text-white">В корзину</button>
// </div>
// </div>
// </div>
// HTML;
<?php endforeach ?>
// if (++$i % 4 === 0 && $amount - $i !== 0) {
// echo <<<HTML
// <div class="dropdown-divider my-3 mx-4 w-100 "></div>
// HTML;
// }
}
} else {
echo <<<HTML
</section>
</div>
<?php else : ?>
<?php if ($advanced ?? false) : ?>
<h1 class="m-auto gilroy text-center"><b>Ничего не найдено</b></h1>
<?php else : ?>
<div class="row py-3 w-100">
<section class="col">
<div class="d-flex flex-column mx-auto">
<p class="m-0 py-2 d-flex justify-content-center">Ничего не найдено</p>
</div>
HTML;
}
}
?>
</section>
</div>
<?php endif ?>
<?php endif ?>
</section>
</div>
<?php endif ?>
</div>
<script src="/js/cart.js" defer></script>

View File

@ -1,4 +1,4 @@
<link href="/css/loading.css" rel="stylesheet">
<p class="my-2 d-flex justify-content-center"><i class="loading mr-2"></i><br>Поиск</p>
<p class="my-2 d-flex justify-content-center"><i class="loading mr-2"></i>Поиск</p>

View File

@ -40,6 +40,15 @@ main {
background-color: #f0eefb;
}
.unselectable {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.inactive,
.inactive:hover,
.inactive:focus,
@ -153,6 +162,10 @@ main {
transition : 0s;
}
.alert_white {
background: #fbf9ff;
}
.d-inline-block {
display: inline-block;
}

View File

@ -13,25 +13,64 @@
}
#page_product #product_slider>.product_slider_image {
max-width: 25vw;
width: 25vw;
}
#page_product #product_slider>.product_slider_preview>label {
position: relative;
border : none;
overflow: hidden;
transition: .1s;
}
#page_product #product_slider>.product_slider_preview>label:hover>img {
#page_product #product_slider>.product_slider_preview>label>div {
display: none;
position: absolute;
}
#page_product #product_slider>.product_slider_preview>label:hover>div {
display: block;
}
#page_product #product_slider>.product_slider_preview>label>img {
cursor: pointer;
filter: brightness(0.9) contrast(1.1);
filter: brightness(.85);
}
#page_product #product_slider>.product_slider_preview>label:hover>img,
#page_product #product_slider>.product_slider_preview>label:active>img,
#page_product #product_slider>.product_slider_preview>label:focus>img {
cursor: pointer;
filter: brightness(0.8) contrast(1.3);
#page_product #product_slider>.product_slider_preview>label:focus>img,
#page_product #product_slider>.product_slider_preview>label.active>img,
#page_product #product_slider>.product_slider_preview>label.button_write>img {
filter: none;
}
#page_product #product_slider>.product_slider_preview>label.active {
left: -5px;
/* border: 3px solid #123eab; */
}
#page_product #product_slider>.product_slider_preview>label.button_write:hover>img {
filter: brightness(0.85) contrast(1.2);
}
#page_product #product_slider>.product_slider_preview>label.button_write:active>img,
#page_product #product_slider>.product_slider_preview>label.button_write:focus>img {
filter: brightness(0.8) contrast(1.25);
}
#page_product article .product_description {
display: contents;
}
#page_product article .product_options_edit {
width: 100px;
height: 1rem;
border: none;
}
#page_product article .product_options_edit_small {
width: 60px;
height: 1rem;
border: none;
}

View File

@ -6,6 +6,11 @@
transition : 100ms ease-in;
}
#page_search section>div>div>img {
object-fit: cover;
width : calc(65px - 1rem);
}
/* #page_search nav > div, #page_search section > div:hover > div {
height: 68px;
border-right: 6px solid #e1e1ea;

View File

@ -15,7 +15,7 @@ function notification_last() {
};
// Проверка уведомлений
setInterval(notification_last, 5000);
setInterval(notification_last, 10000);
function notification_popup_create(html, id) {
// Инициализация

View File

@ -1,3 +1,26 @@
function product_image_choose(key, wrap) {
if (key !== undefined && key !== null && wrap !== undefined && wrap !== null) {
// Инициализация
let labels = wrap.getElementsByTagName('label');
for (let i = 0; i <= labels.length; i++) {
// Перебор всех элементов label (обложки изображений)
// Инициализация
let label = labels[i];
if (label.getAttribute('for') === 'product_slider_image_' + key) {
label.classList.add('active');
label.checked = 'true';
} else {
label.classList.remove('active');
label.checked = 'false';
}
}
}
}
function product_response_success(data, status) {
product_response(data, status);
}

View File

@ -101,6 +101,76 @@ function product_panel_catn_save(catn, element, redirect = false) {
return true;
}
function product_panel_dimensions_edit(catn, element, dimension) {
if (catn !== null && catn !== undefined && element !== null && element !== undefined) {
let input = document.createElement('input');
input.setAttribute('id', element.id);
input.setAttribute('class', 'ml-1 text-center product_options_edit_small');
input.setAttribute('type', 'number');
input.setAttribute('value', element.innerText);
input.setAttribute('aria-invalid', 'false');
element.replaceWith(input);
product_panel_handler_save(product_panel_dimensions_save, catn, input, dimension);
return false;
}
return true;
}
function product_panel_dimensions_save(catn, element, dimension) {
if (catn !== null && catn !== undefined && element !== null && element !== undefined) {
// Инициализация
let text = element.value;
let span = document.createElement('span');
span.setAttribute('id', element.id);
span.setAttribute('class', 'ml-1 pointer-event');
span.setAttribute('role', 'button');
span.setAttribute('onclick', 'return product_panel_dimensions_edit(\'' + catn + '\', this, \'' + dimension + '\');');
if (text.length === 0) {
text = '0';
}
span.innerText = text;
element.replaceWith(span);
$.ajax({
url: '/product/' + catn + '/edit/dmns',
type: 'post',
dataType: 'json',
data: {
'_csrf': yii.getCsrfToken(),
'text': text,
'dimension': dimension
},
success: function (data, status) {
// Заголовок
if (data.dimension !== undefined && span !== null && span !== undefined) {
// Обновление заголовка
span.innerText = data.dimension;
// Запись аттрибута
span.setAttribute('onclick', 'return product_panel_dimensions_edit(\'' + catn + '\', this, \'' + dimension + '\');');
};
product_response_success(data, status);
},
error: product_response_error
});
return false;
}
return true;
}
function product_panel_description_edit(catn, element) {
if (catn !== null && catn !== undefined && element !== null && element !== undefined) {
element.innerHTML = '<textarea class="form-control" cols="50" rows="5" onchange = "return product_panel_description_save(\'' + catn + '\', this.parentElement)">' + element.innerText + '</textarea>';
@ -125,7 +195,7 @@ function product_panel_description_save(catn, element) {
element.setAttribute('onclick', 'return product_panel_description_edit(\'' + catn + '\', this);');
$.ajax({
url: '/product/' + catn + '/edit/desc',
url: '/product/' + catn + '/edit/dscr',
type: 'post',
dataType: 'json',
data: {
@ -179,3 +249,56 @@ function product_panel_images_write(catn, element) {
return true;
}
function product_panel_images_delete(catn, index) {
if (catn !== null && catn !== undefined && index !== null && index !== undefined) {
$.ajax({
url: '/product/' + catn + '/delete/image',
type: 'post',
dataType: 'json',
data: {
'_csrf': yii.getCsrfToken(),
'index': index
},
success: product_response_success,
error: product_response_error
});
return false;
}
return true;
}
function product_panel_images_cover_write(catn, index) {
if (catn !== null && catn !== undefined && index !== null && index !== undefined) {
$.ajax({
url: '/product/' + catn + '/write/cover',
type: 'post',
dataType: 'json',
data: {
'_csrf': yii.getCsrfToken(),
'index': index
},
success: product_response_success,
error: product_response_error
});
return false;
}
return true;
}
function product_panel_handler_save(save, catn, element, ...vars) {
if (save !== undefined && save !== null) {
let product_panel_handler_save_function = function (event) {
if (event.target.id !== element.id) {
save(catn, element, ...vars);
document.body.removeEventListener('click', product_panel_handler_save_function, true);
}
}
document.body.addEventListener('click', product_panel_handler_save_function, true);
}
}