Создание корзины

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2021-03-07 20:29:19 +10:00
parent b8c1f7cef0
commit 53d7e2a048
34 changed files with 1248 additions and 396 deletions

View File

@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace app\controllers;
use app\models\AccountEdgeNotification;
use yii;
use yii\filters\AccessControl;
use yii\web\Controller;
use yii\web\Response;
use app\models\Order;
use app\models\AccountEdgeOrder;
use Exception;
class CartController extends Controller
{
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::class,
'only' => ['index'],
'rules' => [
[
'allow' => true,
'roles' => ['@']
]
]
]
];
}
/**
* Страница: "Корзина"
*
* @see $this->behaviors Доступ только аутентифицированным
*/
public function actionIndex(): string|array|null
{
// Инициализация настроек страницы
$page = yii::$app->request->get('page') ?? 1;
// Инициализация корзина
$model = Order::search();
// Инициализация содержимого корзины
$supplies = $model->content(10, $page);
// Инициализация реестра дубликатов
$registry = [];
// Подсчёт и перестройка массива
foreach ($supplies as $key => &$supply) {
// Перебор поставок
if (in_array($supply->catn, $registry)) {
// Если данная поставка найдена в реестре
// Удаление
unset($supplies[$key]);
// Пропуск итерации
continue;
}
$amount = 0;
// Повторный перебор для поиска дубликатов
foreach ($supplies as &$supply4check) {
if ($supply == $supply4check) {
// Найден дубликат
// Постинкрементация счётчика
$amount++;
// Запись в реестр
$registry []= $supply4check->catn;
}
}
// Запись количества
$supply->amnt = $amount;
}
// Инициализация возврата по умолчанию
$return = [
'_csrf' => yii::$app->request->getCsrfToken()
];
if (yii::$app->request->isPost) {
// POST-запрос
yii::$app->response->format = Response::FORMAT_JSON;
$return = array_merge($return, [
'main' => $this->renderPartial('index', compact('model', 'supplies')),
'title' => 'Корзина',
'redirect' => '/cart'
]);
} else {
// GET-запрос (подразумевается)
return $this->render('index', compact('model', 'supplies'));
}
return $return;
}
}

View File

@ -30,6 +30,9 @@ class NotificationController extends Controller
]; ];
} }
/**
* @todo Перенести логику в модель
*/
public function actionIndex() public function actionIndex()
{ {
if (yii::$app->request->isPost) { if (yii::$app->request->isPost) {
@ -84,12 +87,17 @@ class NotificationController extends Controller
/** /**
* Поиск рёбер: (УВЕДОМЛЕНИЕ)? -> ПОЛЬЗОВАТЕЛЬ * Поиск рёбер: (УВЕДОМЛЕНИЕ)? -> ПОЛЬЗОВАТЕЛЬ
* *
* @param bool $check Активация проверки получения * @param bool $check Активация проверки на то, что уведомление не получено
*/ */
$search = function (bool $check = false) use ($model, $type, $let, $limit): array { $search = function (bool $check = false) use ($model, $type, $let, $limit): array {
return $model::searchByAccount( return $model::searchByEdge(
from: 'account',
to: 'notification',
params: $check ? $let->getBindVars() : [], params: $check ? $let->getBindVars() : [],
where: [ subquery_where: [
[
'account._id' => yii::$app->user->id
],
[ [
[ [
'notification.html' => null 'notification.html' => null
@ -101,10 +109,10 @@ class NotificationController extends Controller
] ]
], ],
let: [ let: [
$model::collectionName() . '_edge_account', 'notification_edge_account',
'(' . (string) $let . ')' '(' . (string) $let . ')'
], ],
post_where: $check ? [ where: $check ? [
'account_edge_notification[0]._to' => null 'account_edge_notification[0]._to' => null
] : [], ] : [],
limit: $limit, limit: $limit,
@ -126,7 +134,7 @@ class NotificationController extends Controller
if (empty($notifications)) { if (empty($notifications)) {
// Уведомления не найдены // Уведомления не найдены
yii::$app->response->statusCode = 404; yii::$app->response->statusCode = 100;
goto end; goto end;
} }

View File

@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
namespace app\controllers;
use app\models\AccountEdgeNotification;
use yii;
use yii\filters\AccessControl;
use yii\web\Controller;
use yii\web\Response;
use app\models\Order;
use app\models\AccountEdgeOrder;
use Exception;
class OrderController extends Controller
{
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::class,
'only' => ['index'],
'rules' => [
[
'allow' => true,
'roles' => ['@']
]
]
]
];
}
public function actionIndex()
{
}
public function actionWrite(): string|array|null {
if (yii::$app->request->isPost) {
// POST-запрос
// Инициализация входных данных
$account = yii::$app->user;
$supplies = yii::$app->request->post('supplies');
yii::$app->response->format = Response::FORMAT_JSON;
// Инициализация возврата по умолчанию
$return = [
'_csrf' => yii::$app->request->getCsrfToken()
];
if (is_null($supplies)) {
// 501 Not Implemented
yii::$app->response->statusCode = 501;
}
if (!is_null($supplies)) {
// Переданы поставки для записи
if (!$model = Order::search($account)) {
// Корзина не найдена (текущий заказ)
// Инициализация
$model = new Order();
$model->save() or throw new Exception('Не удалось инициализировать заказ');
// Запись в аккаунт
AccountEdgeOrder::write($account->id, $model->readId(), 'current') or $model->addError('errors', 'Не удалось инициализировать ребро: АККАУНТ -> ЗАКАЗ');
}
// Проверка входных данных
if (!is_array($supplies)) {
// Неверные входные данные
// Запись ошибки
$model->addError('errors', 'Переменная должна быть массивом');
}
// Если запись не удалась, то вернуть код: 500 Internal Server Error
$model->write($supplies) or yii::$app->response->statusCode = 500;
}
return $return;
}
yii::$app->response->statusCode = 500;
return null;
}
}

View File

@ -14,28 +14,28 @@ use app\models\Product;
class ProductController extends Controller class ProductController extends Controller
{ {
/** // /**
* {@inheritdoc} // * {@inheritdoc}
*/ // */
public function behaviors() // public function behaviors()
{ // {
return [ // return [
'access' => [ // 'access' => [
'class' => AccessControl::class, // 'class' => AccessControl::class,
'rules' => [ // 'rules' => [
[ // [
'allow' => true, // 'allow' => true,
'actions' => ['index'], // 'actions' => ['index'],
'roles' => ['@'] // 'roles' => ['@']
], // ],
[ // [
'allow' => false, // 'allow' => false,
'roles' => ['?'] // 'roles' => ['?']
], // ],
] // ]
] // ]
]; // ];
} // }
public function actionIndex(string $catn) public function actionIndex(string $catn)
{ {

View File

@ -85,7 +85,7 @@ class ProfileController extends Controller
{ {
// Инициализация // Инициализация
$model = yii::$app->user->identity; $model = yii::$app->user->identity;
$attributes = Supply::searchByAccount(yii::$app->user->id); $supplies = Supply::searchByAccount(select: 'supply.onec["ЗначенияСвойств"]');
if ($vars = yii::$app->request->post('Account') ?? yii::$app->request->get('Account')) { if ($vars = yii::$app->request->post('Account') ?? yii::$app->request->get('Account')) {
// Обнаружены входные параметры // Обнаружены входные параметры
@ -116,13 +116,13 @@ class ProfileController extends Controller
yii::$app->response->format = Response::FORMAT_JSON; yii::$app->response->format = Response::FORMAT_JSON;
return [ return [
'main' => $this->renderPartial('index', compact('model', 'sidebar', 'attributes')), 'main' => $this->renderPartial('index', compact('model', 'sidebar', 'supplies')),
'redirect' => '/profile', 'redirect' => '/profile',
'_csrf' => yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
} }
return $this->render('index', compact('model', 'sidebar', 'attributes')); return $this->render('index', compact('model', 'sidebar', 'supplies'));
} }
/** /**
@ -137,7 +137,7 @@ class ProfileController extends Controller
$sidebar = $this->renderPartial('sidebar'); $sidebar = $this->renderPartial('sidebar');
if (yii::$app->request->isPost) { if (yii::$app->request->isPost) {
// AJAX-POST-запрос // POST-запрос
yii::$app->response->format = Response::FORMAT_JSON; yii::$app->response->format = Response::FORMAT_JSON;

View File

@ -9,6 +9,7 @@ use yii\web\Controller;
use yii\web\Response; use yii\web\Response;
use app\models\Product; use app\models\Product;
use app\models\Supply;
use app\models\Search; use app\models\Search;
class SearchController extends Controller class SearchController extends Controller
@ -18,6 +19,19 @@ class SearchController extends Controller
*/ */
public function actionIndex(): array|string public function actionIndex(): array|string
{ {
// Инициализация параметров
$auth_only = false;
if ($auth_only && yii::$app->user->isGuest) {
// Если активирован режим "Поиск только аутентифицированным" и запрос пришел не от аутентифицированного
// Запись кода ответа: 401 (необходима авторизация)
yii::$app->response->statusCode = 401;
// Переход к концу обработки
goto skip_search;
}
// Инициализация // Инициализация
$query = yii::$app->request->post('request') ?? yii::$app->request->get('q'); $query = yii::$app->request->post('request') ?? yii::$app->request->get('q');
@ -30,7 +44,7 @@ class SearchController extends Controller
yii::$app->response->format = Response::FORMAT_JSON; yii::$app->response->format = Response::FORMAT_JSON;
return [ return [
'search_line_window' => $this->renderPartial('/search/panel', ['history' => true]), 'panel' => $this->renderPartial('/search/panel', ['history' => true]),
'_csrf' => yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
} }
@ -46,7 +60,7 @@ class SearchController extends Controller
$timer = 0; $timer = 0;
// Период пропуска запросов (в секундах) // Период пропуска запросов (в секундах)
$period = 3; $period = 1;
$keep_connect = true; $keep_connect = true;
$sanction = false; $sanction = false;
$sanction_condition = ($session['last_request'] + $period - time()) < $period; $sanction_condition = ($session['last_request'] + $period - time()) < $period;
@ -68,39 +82,45 @@ class SearchController extends Controller
// Инициализация // Инициализация
$session['last_request'] = time(); $session['last_request'] = time();
// Пропуск проверок
goto first_request; goto first_request;
} }
// Метка: "Повтор обработки поиска" (для создания непрерывного соединения)
keep_connect_wait: keep_connect_wait:
// Запись времени последнего запроса и вычисление об истечении таймера // Запись времени последнего запроса и вычисление об истечении таймера
$timer = $session['last_request'] + $period - time(); $timer = $session['last_request'] + $period - time();
if ($timer > 0) { if ($timer > 0) {
// Ожидание перед повторным запросом при условии что старых запросов нет // Время блокировки не истекло
// Запись кода ответа: 202 (запрос не обработан)
yii::$app->response->statusCode = 202; yii::$app->response->statusCode = 202;
$return = [ $return = [
'timer' => $timer, 'timer' => $timer,
'search_line_window' => $this->renderPartial('/loading'), 'panel' => $this->renderPartial('/search/loading'),
'_csrf' => yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
} else { } else {
// Повторный запрос по истечению ожидания // Запрос
// Очистка времени последнего запроса // Очистка времени последнего запроса
unset($session['last_request']); unset($session['last_request']);
// Метка: "Первый запрос"
first_request: first_request:
if (strlen($query) < $query_min) { if (strlen($query) < $query_min) {
// Выход за ограничения длины с недостатком // Выход за ограничения длины с недостатком
// Пропуск поиска
goto skip_query; goto skip_query;
} else if (strlen($query) > $query_max) { } else if (strlen($query) > $query_max) {
// Выход за ограничения длины с превышением // Выход за ограничения длины с превышением
// Пропуск поиска
goto skip_query; goto skip_query;
} }
@ -109,12 +129,39 @@ class SearchController extends Controller
$limit = yii::$app->request->isPost ? 10 : 20; $limit = yii::$app->request->isPost ? 10 : 20;
if ($response = Product::searchByCatn($query, $limit, [])) { if ($response = Product::searchByCatn($query, $limit, ['catn' => 'catn', '_key' => '_key'])) {
// Данные найдены по поиску в полях Каталожного номера // Данные найдены по поиску в полях Каталожного номера
foreach ($response as &$row) {
// Перебор продуктов
// Поиск поставок привязанных к продуктам
$row['supplies'] = Supply::searchByEdge(
from: 'product',
to: 'supply',
edge: 'supply_edge_product',
limit: 11,
direction: 'OUTBOUND',
subquery_where: [
['product._key' => $row['_key']],
['supply.catn == product.catn'],
['supply_edge_product.type' => 'connect']
],
where: 'supply._id == supply_edge_product[0]._from',
select: 'supply_edge_product[0]'
);
if (count($row['supplies']) === 11) {
// Если в базе данных хранится много поставок
// Инициализация
$row['overload'] = true;
}
}
// Запись ответа // Запись ответа
$return = [ $return = [
'search_line_window' => $this->renderPartial('/search/panel', compact('response')), 'panel' => $this->renderPartial('/search/panel', compact('response')),
'_csrf' => yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
@ -123,16 +170,18 @@ class SearchController extends Controller
// Запись ответа // Запись ответа
$return['main'] = $this->renderPartial('/search/index', compact('response')); $return['main'] = $this->renderPartial('/search/index', compact('response'));
$return['search_line_window_hide'] = 1; $return['hide'] = 1;
$return['redirect'] = '/search?type=product&q=' . $query; $return['redirect'] = '/search?type=product&q=' . $query;
} }
} else { } else {
// Данные не найдены // Данные не найдены
// Запись кода ответа: 404 (запрашиваемые данные не найдены)
yii::$app->response->statusCode = 404; yii::$app->response->statusCode = 404;
} }
} }
// Метка: "Пропуск запроса" (для пропуска самого поиска и возврата ответа)
skip_query: skip_query:
if (yii::$app->request->isPost) { if (yii::$app->request->isPost) {
@ -141,7 +190,7 @@ class SearchController extends Controller
yii::$app->response->format = Response::FORMAT_JSON; yii::$app->response->format = Response::FORMAT_JSON;
return $return ?? [ return $return ?? [
'search_line_window' => $this->renderPartial('/search/panel'), 'panel' => $this->renderPartial('/search/panel'),
'_csrf' => yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
} else { } else {
@ -153,6 +202,7 @@ class SearchController extends Controller
// Ожидание // Ожидание
sleep($timer); sleep($timer);
// Повтор обработки
goto keep_connect_wait; goto keep_connect_wait;
} }
@ -160,9 +210,22 @@ class SearchController extends Controller
} }
} }
if (yii::$app->user->isGuest) { // Метка: "Пропуск обработки"
return $this->render('/search/index', ['error_auth' => true]); skip_search:
if (yii::$app->request->isPost) {
// POST-запрос
yii::$app->response->format = Response::FORMAT_JSON;
return [
'panel' => $this->renderPartial('/search/panel'),
'hide' => (int) $auth_only,
'_csrf' => yii::$app->request->getCsrfToken()
];
} else { } else {
// GET-запрос
return $this->render('/search/index'); return $this->render('/search/index');
} }
} }

View File

@ -0,0 +1,16 @@
<?php
use mirzaev\yii2\arangodb\Migration;
class m210303_192326_create_order_collection extends Migration
{
public function up()
{
$this->createCollection('order', []);
}
public function down()
{
$this->dropCollection('order');
}
}

View File

@ -0,0 +1,16 @@
<?php
use mirzaev\yii2\arangodb\Migration;
class m210303_192451_create_order_edge_supply_collection extends Migration
{
public function up()
{
$this->createCollection('order_edge_supply', ['type' => 3]);
}
public function down()
{
$this->dropCollection('order_edge_supply');
}
}

View File

@ -0,0 +1,16 @@
<?php
use mirzaev\yii2\arangodb\Migration;
class m210307_000210_create_account_edge_order_collection extends Migration
{
public function up()
{
$this->createCollection('account_edge_order', ['type' => 3]);
}
public function down()
{
$this->dropCollection('account_edge_order');
}
}

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace app\models;
class AccountEdgeOrder extends Edge
{
public static function collectionName(): string
{
return 'account_edge_order';
}
}

View File

@ -113,7 +113,7 @@ abstract class Document extends ActiveRecord
/** /**
* Проверка на то, что в свойство передан массив * Проверка на то, что в свойство передан массив
*/ */
protected function arrayValidator(string $attribute, array $params): bool public function arrayValidator(string $attribute, array $params = null): bool
{ {
if (is_array($this->$attribute)) { if (is_array($this->$attribute)) {
return true; return true;

View File

@ -7,7 +7,9 @@ namespace app\models;
use yii; use yii;
use yii\web\IdentityInterface; use yii\web\IdentityInterface;
use app\models\traits\SearchByAccount; use app\models\traits\SearchByEdge;
use Exception;
/** /**
* Поиск * Поиск
@ -16,7 +18,7 @@ use app\models\traits\SearchByAccount;
*/ */
class Notification extends Document class Notification extends Document
{ {
use SearchByAccount; use SearchByEdge;
/** /**
* Сценарий для доверенного пользователя с созданием уведомления * Сценарий для доверенного пользователя с созданием уведомления
@ -116,9 +118,9 @@ class Notification extends Document
public function write(string $html = null, IdentityInterface|array|string $trgt = null, string $type = 'notice'): self|array|null public function write(string $html = null, IdentityInterface|array|string $trgt = null, string $type = 'notice'): self|array|null
{ {
// Инициализация // Инициализация
isset($this->html) ? $html = $this->html : null; $html ?? $html = $this->html ?? throw new Exception('Не удалось инициализировать содержимое');
isset($this->trgt) ? $trgt = $this->trgt : null; $trgt ?? $trgt = yii::$app->user ?? throw new Exception('Не удалось инициализировать получателя');
isset($this->type) ? $type = $this->type : null; $type ?? $trgt = $this->type ?? throw new Exception('Не удалось инициализировать тип');
// Инициализация уведомления // Инициализация уведомления
if (isset($html) && (bool) (int) $html) { if (isset($html) && (bool) (int) $html) {
@ -193,15 +195,6 @@ class Notification extends Document
return AccountEdgeNotification::writeSafe($this->readId(), $target->readId(), $type) ? $this : null; return AccountEdgeNotification::writeSafe($this->readId(), $target->readId(), $type) ? $this : null;
} }
// "or" имеет приоритет ниже чем у "||" относительно "="
//
// if ($target = $buffer = Account::searchById($target) or
// $target = Account::searchById(Account::collectionName() . '/' . $buffer)
// ) {
//
// if (($target = Account::searchById($target)) ||
// ($target = Account::searchById(Account::collectionName() . '/' . $target))
// ) {
if ($target = Account::searchById(Account::collectionName() . '/' . $target)) { if ($target = Account::searchById(Account::collectionName() . '/' . $target)) {
// Аккаунт найден // Аккаунт найден

View File

@ -0,0 +1,186 @@
<?php
declare(strict_types=1);
namespace app\models;
use yii;
use yii\web\User as Account;
use app\models\traits\SearchByEdge;
use Exception;
/**
* Заказ
*
* @see Account Заказчик
* @see Supply Поставки для заказа
*/
class Order extends Document
{
use SearchByEdge;
/**
* Поставки для записи
*/
public array $supplies;
/**
* Имя коллекции
*/
public static function collectionName(): string
{
return 'order';
}
/**
* Свойства
*/
public function attributes(): array
{
return array_merge(
parent::attributes(),
[]
);
}
/**
* Метки свойств
*/
public function attributeLabels(): array
{
return array_merge(
parent::attributeLabels(),
[]
);
}
/**
* Правила
*/
public function rules(): array
{
return array_merge(
parent::rules(),
[]
);
}
/**
* Запись
*
* $supply = [ Supply $supply, int $amount = 1 ]
*
* @param Supply|array $supply Поставка
* @param Account $trgt Заказчик
*
* @todo Создать параметр разделителя для администрации
*/
public function write(Supply|array $supply, Account $trgt = null): self|null
{
// Инициализация
$trgt ?? $trgt = yii::$app->user ?? throw new Exception('Не удалось инициализировать заказчика');
if ($supply instanceof Supply) {
// Передана инстанция класса поставки
// Унификация входных данных
$supply = [$supply, 1];
}
if (is_null($this->_key)) {
// Корзина не инициализирована
// Инициализация
if (!$this->save()) {
// Инициализация заказа не удалась
throw new Exception('Ошибка при записи заказа в базу данных');
}
// Инициализация ребра: АККАУНТ -> ЗАКАЗ
if (!AccountEdgeOrder::write($trgt->readId(), $this->readId(), 'create')) {
// Инициализация не удалась
throw new Exception('Ошибка при записи ребра от аккаунта до заказа в базу данных');
}
}
foreach ($supply as $supply_raw) {
// Перебор поставок
for ($i = 0; $i < $supply_raw[1]; $i++) {
// Создание рёбер соразмерно запросу (добавление нескольких продуктов в корзину)
// Инициализация ребра: ЗАКАЗ -> ПОСТАВКА
if (!$supply = Supply::searchByCatn($supply_raw[0]) or !OrderEdgeSupply::write($this->readId(), $supply->readId(), 'add')) {
// Поставка не найдена или инициализация ребра не удалась
continue;
}
}
}
// Отправка на сервер базы данных
return $this->save() ? $this : null;
}
/**
* Поиск заказа
*/
public static function search(Account $account = null, string $type = 'current', int $limit = 1): self|array
{
$account or $account = yii::$app->user ?? throw new Exception('Не удалось инициализировать пользователя');
$return = self::searchByEdge(
from: 'account',
to: 'order',
subquery_where: [
[
'account._id' => $account->id
],
[
'account_edge_order.type' => $type
]
],
limit: $limit,
sort: ['DESC'],
direction: 'INBOUND'
);
return $limit === 1 ? $return[0] : $return;
}
/**
* Поиск содержимого заказа
*
* @todo В будущем возможно заказ не только поставок реализовать
*/
public function content(int $limit = 1, int $page = 1): Supply|array
{
// Генерация сдвига по запрашиваемым данным (пагинация)
$offset = $limit * ($page - 1);
// Поиск рёбер: ЗАКАЗ -> ПОСТАВКА
$return = Supply::searchByEdge(
from: 'order',
to: 'supply',
edge: 'order_edge_supply',
subquery_where: [
[
'order._id' => $this->readId()
],
[
'order_edge_supply.type' => 'add'
]
],
foreach: ['edge' => 'order_edge_supply'],
where: 'edge._to == supply._id',
limit: $limit,
offset: $offset
);
return $limit === 1 ? $return[0] : $return;
}
}

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace app\models;
class OrderEdgeSupply extends Edge
{
public static function collectionName(): string
{
return 'order_edge_supply';
}
}

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace app\models; namespace app\models;
use moonland\phpexcel\Excel; use moonland\phpexcel\Excel;
use app\models\traits\SearchByEdge;
/** /**
* Продукт (в ассортименте магазина) * Продукт (в ассортименте магазина)
@ -15,6 +16,8 @@ use moonland\phpexcel\Excel;
*/ */
class Product extends Document class Product extends Document
{ {
use SearchByEdge;
/** /**
* Сценарий импорта из .excel документа * Сценарий импорта из .excel документа
*/ */
@ -52,13 +55,13 @@ class Product extends Document
parent::attributes(), parent::attributes(),
[ [
'name', 'name',
'desc',
'ocid', 'ocid',
'catn', 'catn',
'imgs', 'imgs',
'oemn' 'time',
// 'data', 'oemn',
// 'cost', 'cost'
// 'time'
] ]
); );
} }
@ -71,14 +74,14 @@ class Product extends Document
return array_merge( return array_merge(
parent::attributeLabels(), parent::attributeLabels(),
[ [
'name' => 'Название (name)', 'name' => 'Название',
'ocid' => 'Идентификатор 1C (ocid)', 'desc' => 'Описание',
'catn' => 'Каталожный номер (catn)', 'ocid' => 'Идентификатор 1C',
'imgs' => 'Изображения (imgs)', 'catn' => 'Каталожный номер',
'oemn' => 'OEM номера (oemn)', 'imgs' => 'Изображения',
// 'data' => 'Данные товара (data)', 'time' => 'Срок доставки',
// 'cost' => 'Цены (cost)', 'oemn' => 'OEM номера',
// 'time' => 'Сроки доставки (time)', 'cost' => 'Стоимость',
'file' => 'Документ', 'file' => 'Документ',
'group' => 'Группа' 'group' => 'Группа'
] ]
@ -284,7 +287,7 @@ class Product extends Document
* *
* @todo Переделать нормально * @todo Переделать нормально
*/ */
public static function searchByCatn(string $catn, int $limit = 1, array $select = ['catn' => 'catn']): static|array|null public static function searchByCatn(string $catn, int $limit = 1, array $select = []): static|array|null
{ {
if ($limit <= 1) { if ($limit <= 1) {
return static::findOne(['catn' => $catn]); return static::findOne(['catn' => $catn]);

View File

@ -7,7 +7,7 @@ namespace app\models;
use yii; use yii;
use yii\web\IdentityInterface; use yii\web\IdentityInterface;
use app\models\traits\SearchByAccount; use app\models\traits\SearchByEdge;
/** /**
* Поиск * Поиск
@ -16,7 +16,7 @@ use app\models\traits\SearchByAccount;
*/ */
class Search extends Document class Search extends Document
{ {
use SearchByAccount; use SearchByEdge;
/** /**
* Имя коллекции * Имя коллекции

View File

@ -24,11 +24,15 @@ use exception;
*/ */
class Supply extends Product implements ProductInterface class Supply extends Product implements ProductInterface
{ {
/**
* Метод для конвертации XML в Array
*/
use Xml2Array; use Xml2Array;
/**
* Количество
*
* Используется при выводе в корзине
*/
public int $amnt = 0;
/** /**
* Имя коллекции * Имя коллекции
*/ */
@ -68,14 +72,14 @@ class Supply extends Product implements ProductInterface
*/ */
public function afterSave($data, $vars): void public function afterSave($data, $vars): void
{ {
if (AccountEdgeSupply::searchByVertex(Yii::$app->user->id, $this->readId())) { if (AccountEdgeSupply::searchByVertex(yii::$app->user->id, $this->readId())) {
// Ребро уже существует // Ребро уже существует
} else { } else {
// Ребра не существует // Ребра не существует
// Запись ребра: АККАУНТ -> ПОСТАВКА // Запись ребра: АККАУНТ -> ПОСТАВКА
(new AccountEdgeSupply)->write(Yii::$app->user->id, $this->readId(), 'import'); (new AccountEdgeSupply)->write(yii::$app->user->id, $this->readId(), 'import');
} }
} }
@ -100,6 +104,39 @@ class Supply extends Product implements ProductInterface
return true; return true;
} }
/**
* Поиск через связь с аккаунтом
*
* @param string $id Идентификатор пользователя
* @param bool $select Запрашиваемые значения
*/
public static function searchByAccount(string $id = null, string|array $select = []): array
{
isset($id) ?: $id = yii::$app->user->id ?? throw new Exception('Не найден идентификатор');
return self::searchByEdge(
from: 'account',
to: 'supply',
subquery_where: [
[
'account._id' => $id
]
],
where: 'supply._id == account_edge_supply[0]._to AND supply.onec["ЗначенияСвойств"] != null',
select: $select,
// handle: function ($request) {
// $response = $request->createCommand()->execute()->getAll();
// foreach ($response as &$attribute) {
// // Приведение всех свойств в массив и очистка от лишних данных
// $attribute = $attribute->getAll();
// }
// return $response;
// }
);
}
/** /**
* Запись данных свойств по UUID 1C * Запись данных свойств по UUID 1C
@ -112,7 +149,7 @@ class Supply extends Product implements ProductInterface
public static function createProperties1c($properties): void public static function createProperties1c($properties): void
{ {
// Инициализация // Инициализация
$models = self::searchByAccount(Yii::$app->user->id, true); $models = self::searchByAccount(select: 'supply.onec["ЗначенияСвойств"]');
$properties = self::xml2array($properties->xml); $properties = self::xml2array($properties->xml);
// for ($i = 0; $i <= count($models); $i++) // for ($i = 0; $i <= count($models); $i++)
@ -269,6 +306,14 @@ class Supply extends Product implements ProductInterface
foreach ($product as $product) { foreach ($product as $product) {
// Перебор всех инициализированных продуктов // Перебор всех инициализированных продуктов
if ($this->catn !== $product->catn) {
// Каталожные номера не соответствуют друг другу
continue;
}
// Код ниже скорее всего устарел
if (SupplyEdgeProduct::searchByVertex($this->readId(), $product->readId())) { if (SupplyEdgeProduct::searchByVertex($this->readId(), $product->readId())) {
// Ребро уже существует // Ребро уже существует
@ -279,7 +324,7 @@ class Supply extends Product implements ProductInterface
$return = (new SupplyEdgeProduct)->write( $return = (new SupplyEdgeProduct)->write(
$this->readId(), $this->readId(),
$product->readId(), $product->readId(),
'sell', 'connect',
[ [
'onec' => self::xml2array($offer->xml) 'onec' => self::xml2array($offer->xml)
] ]
@ -304,6 +349,7 @@ class Supply extends Product implements ProductInterface
// Настройка // Настройка
$model->ocid = $id ?? null; $model->ocid = $id ?? null;
$model->catn = (string) $product->Артикул; $model->catn = (string) $product->Артикул;
$model->desc = (string) $product->Описание;
$model->onec = self::xml2array($product->xml); $model->onec = self::xml2array($product->xml);
if (isset($model->onec['ЗначенияСвойств'])) { if (isset($model->onec['ЗначенияСвойств'])) {
@ -378,43 +424,12 @@ class Supply extends Product implements ProductInterface
} }
/** /**
* Поиск через связь с аккаунтом * Поиск по OEM-номерам
* *
* @param string $id Идентификатор пользователя * @todo Реализовать с помощью LIKE
* @param bool $full Возврат всех значений (иначе только свойства)
*/ */
public static function searchByAccount(string $id = null, bool $full = false): array public static function searchByOemn(): array
{ {
if (!isset($id)) { return [];
$id = yii::$app->user->id ?? throw new exception('Не найден идентификатор');
}
$subquery = static::find()
->for(['account', 'account_edge_supply'])
->traversal('supply')->in('account_edge_supply')
->where(['account._id' => $id])
->select('account_edge_supply')
->createCommand();
$query = static::find()
->params($subquery->getBindVars())
->let('account_edge_supply', '(' . (string) $subquery . ')')
->where('supply._id == account_edge_supply[0]._to')
->andWhere('supply.onec["ЗначенияСвойств"] != null');
if ($full) {
// Если указан полный поиск
return $query->select('supply')->all();
}
$query = $query->select('supply.onec["ЗначенияСвойств"]')->createCommand()->execute()->getAll();
foreach ($query as &$attribute) {
// Приведение всех свойств в массив и очистка от лишних данных
$attribute = $attribute->getAll();
}
return $query;
} }
} }

View File

@ -1,74 +0,0 @@
<?php
declare(strict_types=1);
namespace app\models\traits;
use yii;
use exception;
trait SearchByAccount
{
/**
* Поиск через связи рёбрами с аккаунтом
*
* @param string $id Идентификатор пользователя
* @param int $limit Количество
* @param int $offset Сдвиг
* @param string $sort Сортировка
*/
public static function searchByAccount(
string $id = null,
int $limit = 10,
int $offset = 0,
array $sort = ['ASC'],
string|array $where = [],
string|array $post_where = [],
string $direction = 'ANY',
array $let = [],
array $params = []
): ?array {
if (!isset($id)) {
$id = yii::$app->user->id ?? throw new exception('Не найден идентификатор');
}
$subquery = static::find()
->for(['account', 'account_edge_' . self::collectionName()])
->traversal(self::collectionName(), $direction)
->in('account_edge_' . self::collectionName())
->where(['account._id' => $id]);
if (!empty($where)) {
// Переданы дополнительные условия фильтрации
$subquery->where($where);
}
$subquery = $subquery->select('account_edge_' . self::collectionName())
->createCommand();
$query = static::find()
->params($params, $subquery->getBindVars())
->let('account_edge_' . self::collectionName(), '(' . (string) $subquery . ')')
->limit($limit)
->offset($offset)
->orderBy($sort);
if (!empty($let)) {
// Переданы дополнительные условия фильтрации
$query->let(...$let);
}
if (isset($post_where) && $post_where) {
// Если переданы дополнительные условия фильтрации
$query->where($post_where);
} else {
$query->where(self::collectionName() . '._id == account_edge_' . self::collectionName() . '[0]._to');
}
return $query->select(self::collectionName())->all();
}
}

View File

@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace app\models\traits;
use yii;
use exception;
trait SearchByEdge
{
/**
* Поиск через связи рёбрами с аккаунтом
*
* @param string $id Идентификатор пользователя
* @param int $limit Количество
* @param int $offset Сдвиг
* @param string $sort Сортировка
*/
public static function searchByEdge(
string $from,
string $to,
string $edge = null,
int $limit = 10,
int $offset = 0,
array $sort = ['ASC'],
string|array $subquery_where = [],
array $foreach = [],
string|array $where = [],
string $direction = 'ANY',
array $let = [],
string|array $select = null,
callable $handle = null,
array &$params = []
): ?array {
$subquery = static::find()
->for([$from, $edge ?? $from . '_edge_' . $to])
->traversal($to, $direction)
->in($edge ?? $from . '_edge_' . $to)
->where($subquery_where);
$subquery = $subquery->select($edge ?? $from . '_edge_' . $to)
->createCommand();
$query = static::find()
->params($params, $subquery->getBindVars())
->let($edge ?? $from . '_edge_' . $to, '(' . (string) $subquery . ')')
->limit($limit)
->offset($offset)
->orderBy($sort);
if (!empty($let)) {
// Переданы дополнительные условия фильтрации
$query->let(...$let);
}
// Запрос
$request = $query
->foreach($foreach)
->where($where)
->select($select ?? $to);
if (isset($handle)) {
// Передана функция для постобработки
return $handle($request);
} else if (isset($select)) {
$response = $request->createCommand()->execute()->getAll();
foreach ($response as &$attribute) {
// Приведение всех свойств в массив и очистка от лишних данных
$attribute = $attribute->getAll();
}
return $response;
} else {
return $request->all();
}
}
}

View File

@ -5,12 +5,12 @@ declare(strict_types=1);
use yii; use yii;
?> ?>
<a class="text-dark my-auto mr-2" href="/cart"><i class="fas fa-shopping-cart mx-2"></i></a> <a class="text-dark my-auto mr-2" title="Корзина" href="/cart" role="button" onclick="return page_cart();"><i class="fas fa-shopping-cart mx-2"></i></a>
<a class="text-dark my-auto mr-2" href="/orders"><i class="fas fa-list mx-2"></i></a> <a class="text-dark my-auto mr-2" title="Заказы" href="/orders" role="button" onclick="return page_orders();"><i class="fas fa-list mx-2"></i></a>
<div class="btn-group my-auto"> <div class="btn-group my-auto">
<a class="btn m-0 px-0 text-dark button_clean" title="Личный кабинет" href="/profile" role="button" onclick="return page_profile();">Личный кабинет</a> <a class="btn m-0 px-0 text-dark button_clean" title="Личный кабинет" href="/profile" role="button" onclick="return page_profile();">Личный кабинет</a>
<button id="profile_button" class="btn pr-0 dropdown-toggle dropdown-toggle-split button_clean" type="button" data-toggle="dropdown" onmouseover="$('#profile_button').dropdown('show')"></button> <button id="profile_button" class="btn pr-0 dropdown-toggle dropdown-toggle-split button_clean" type="button" data-toggle="dropdown" onmouseover="$('#profile_button').dropdown('show')"></button>
<div class="dropdown-menu dropdown-menu-long dropdown-menu-right p-3" aria-labelledby="profile_button" onmouseout="$('#profile_button').dropdown('show')"> <div id="profile_button_panel" class="dropdown-menu dropdown-menu-long dropdown-menu-right p-3" aria-labelledby="profile_button" onmouseout="$('#profile_button').dropdown('show')">
<h5 class="mb-3 text-center">Аутентификация</h5> <h5 class="mb-3 text-center">Аутентификация</h5>
<?= yii::$app->controller->renderPartial('/account/index', compact('model')) ?> <?= yii::$app->controller->renderPartial('/account/index', compact('model')) ?>
<!-- <a class="dropdown-item-text text-center px-0 py-2" href="#"><small>Восстановление пароля</small></a> --> <!-- <a class="dropdown-item-text text-center px-0 py-2" href="#"><small>Восстановление пароля</small></a> -->

View File

@ -17,8 +17,8 @@ if (!yii::$app->user->isGuest) {
HTML; HTML;
} }
?> ?>
<a class="text-dark my-auto mr-2" href="/cart"><i class="fas fa-shopping-cart mx-2"></i></a> <a class="text-dark my-auto mr-2" title="Корзина" href="/cart" role="button" onclick="return page_cart();"><i class="fas fa-shopping-cart mx-2"></i></a>
<a class="text-dark my-auto mr-2" href="/orders"><i class="fas fa-list mx-2"></i></a> <a class="text-dark my-auto mr-2" title="Заказы" href="/orders" role="button" onclick="return page_orders();"><i class="fas fa-list mx-2"></i></a>
<div class="btn-group my-auto"> <div class="btn-group my-auto">
<a class="btn m-0 px-0 text-dark button_clean" title="Личный кабинет" href="/profile" role="button" onclick="return page_profile();">Личный кабинет</a> <a class="btn m-0 px-0 text-dark button_clean" title="Личный кабинет" href="/profile" role="button" onclick="return page_profile();">Личный кабинет</a>
<button id="profile_button" class="btn pr-0 dropdown-toggle dropdown-toggle-split button_clean" type="button" data-toggle="dropdown" onmouseover="$('#profile_button').dropdown('show')"></button> <button id="profile_button" class="btn pr-0 dropdown-toggle dropdown-toggle-split button_clean" type="button" data-toggle="dropdown" onmouseover="$('#profile_button').dropdown('show')"></button>

View File

@ -0,0 +1,56 @@
<link href="/css/pages/cart.css" rel="stylesheet">
<div id="page_cart" class="container h-100">
<article class="my-3 p-3 h-100">
<h4 class="ml-4 mt-3 mb-4"><i class="fas fa-shopping-cart mr-2"></i>Корзина</h4>
<div class="col list">
<div class="row py-2">
<div class="col-1">
<input id="checkbox_cart_all" type="checkbox"/>
</div>
<div class="col">
<span>Артикул</span>
</div>
<div class="col">
<span>Описание</span>
</div>
<div class="col">
<span>Количество</span>
</div>
<div class="col">
<span>Доставка</span>
</div>
<div class="col">
<span>Стоимость </span>
</div>
</div>
<?php
foreach ($supplies as $supply) {
echo <<<HTML
<div class="row py-2">
<div class="col-1">
<input id="checkbox_cart_$supply->catn" type="checkbox"/>
</div>
<div class="col">
$supply->catn
</div>
<div class="col">
$supply->desc
</div>
<div class="col">
$supply->amnt
</div>
<div class="col">
$supply->time
</div>
<div class="col">
$supply->cost
</div>
</div>
HTML;
}
?>
</div>
</article>
</div>

View File

@ -69,22 +69,25 @@ AppAsset::register($this);
<label class="mb-0 px-3 px-md-4 py-1" for="catalog_search_panel_button_3">Третья кнопка</label> --> <label class="mb-0 px-3 px-md-4 py-1" for="catalog_search_panel_button_3">Третья кнопка</label> -->
<form class="d-flex catalog_search" onsubmit="return false;"> <form class="d-flex catalog_search" onsubmit="return false;">
<div class="position-relative col-sm-8 col-lg-10 px-0"> <div class="position-relative col-sm-8 col-lg-10 px-0">
<input id="search_line" type="text" class="form-control col-12 catalog_search_line button_clean" placeholder="Введите номер запчасти, например: 45223503481" oninput="$('#search_line').dropdown('hide'); product_search(this);" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" autocomplete="off"> <input id="search_line" type="text" class="form-control col-12 catalog_search_line button_clean" placeholder="Введите номер запчасти, например: 45223503481" oninput="$('#search_line').dropdown('hide'); product_search(this.value);" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" autocomplete="off">
<?php <?php
if (!yii::$app->user->isGuest && $search_panel = $search_panel ?? yii::$app->controller->renderPartial('/search/panel', ['history' => true])) { // Сделать системные настройки и по ним работать
$search_panel = yii::$app->controller->renderPartial('/search/panel', ['history' => true]);
// if (!yii::$app->user->isGuest && $search_panel = $search_panel ?? yii::$app->controller->renderPartial('/search/panel', ['history' => true])) {
echo <<<HTML echo <<<HTML
<div id="search_line_window" class="dropdown-menu w-100" aria-labelledby="search_line"> <div id="search_line_window" class="dropdown-menu w-100" aria-labelledby="search_line">
$search_panel $search_panel
</div> </div>
HTML; HTML;
} else { // } else {
echo <<<HTML // echo <<<HTML
<div id="search_line_window" class="dropdown-menu w-100 d-none" aria-labelledby="search_line"></div> // <div id="search_line_window" class="dropdown-menu w-100" style="display: none" aria-labelledby="search_line"></div>
HTML; // HTML;
} // }
?> ?>
</div> </div>
<button type="submit" class="col btn button_clean catalog_search_button" onclick="product_search(this.parentElement.getElementsByTagName('input')[0], 1)">ПОИСК</button> <button type="submit" class="col btn button_clean catalog_search_button" onclick="product_search(this.parentElement.getElementsByTagName('input')[0].value, 1)">ПОИСК</button>
</form> </form>
</div> </div>
</div> </div>

View File

@ -9,7 +9,7 @@ use app\models\Product;
<link href="/css/pages/product.css" rel="stylesheet"> <link href="/css/pages/product.css" rel="stylesheet">
<div id="page_product" class="h-100"> <div id="page_product" class="h-100">
<div class="container h-100"> <!-- <div class="container h-100"> -->
<div class="row h-100 py-3"> <div class="row h-100 py-3">
<article class="col-12"> <article class="col-12">
<div class="h-100 p-3 d-flex flex-column rounded"> <div class="h-100 p-3 d-flex flex-column rounded">
@ -126,5 +126,5 @@ use app\models\Product;
</div> </div>
</article> </article>
</div> </div>
</div> <!-- </div> -->
</div> </div>

View File

@ -57,13 +57,14 @@ use app\models\Supply;
// Инициализация // Инициализация
isset($model) or $model = yii::$app->user->identity; isset($model) or $model = yii::$app->user->identity;
isset($attributes) or $attributes = Supply::searchByAccount(yii::$app->user->id); isset($supplies) or $supplies = Supply::searchByAccount(select: 'supply.onec["ЗначенияСвойств"]');
$list = []; $list = [];
// Перебор свойств поставок // Перебор свойств поставок
foreach ($attributes as $attribute) { foreach ($supplies as $supply) {
// Инициализация // Инициализация
$id = $attribute['ЗначенияСвойства']['Ид'];
$id = $supply['ЗначенияСвойства']['Ид'];
if (in_array($id, $list, true)) { if (in_array($id, $list, true)) {
// Если встретился дубликат (вызывается очень часто) // Если встретился дубликат (вызывается очень часто)
@ -71,7 +72,7 @@ use app\models\Supply;
} }
// Генерация // Генерация
$list[$id] = $attribute['ЗначенияСвойства']['Наименование']; $list[$id] = $supply['ЗначенияСвойства']['Наименование'];
} }
// Инициализация текущего значения параметра в начале массива // Инициализация текущего значения параметра в начале массива

View File

@ -2,16 +2,10 @@
<div id="page_search" class="container h-100"> <div id="page_search" class="container h-100">
<div class="row py-3"> <div class="row py-3">
<nav class="col-3"> <article class="col">
<div class="p-3 rounded"> <?php
if (isset($timer) && $timer > 0) {
</div> echo <<<HTML
</nav>
<article class="col-9">
<div class="row mx-0 p-3 rounded">
<?php
if (isset($timer) && $timer > 0) {
echo <<<HTML
<div class="d-flex flex-column mx-auto"> <div class="d-flex flex-column mx-auto">
<p class="px-2 mb-1 text-center d-flex justify-content-center">Слишком частые запросы, повторите попытку через: $timer секунд</p> <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> <small class="mb-3 text-center d-flex justify-content-center">Подождите или нажмите на кнопку вручную</small>
@ -21,58 +15,119 @@
setTimeout('window.location.reload()', $timer + '000'); setTimeout('window.location.reload()', $timer + '000');
</script> </script>
HTML; HTML;
} else { } else {
if (isset($response) && is_array($response) && $response) { if (isset($response) && is_array($response) && $response) {
$i = 0; $i = 0;
$amount = count($response); $amount = count($response);
foreach ($response as $row) { foreach ($response as $row) {
$catn = $row['catn']; // $name = $row['name'];
$covr = $row['covr'] ?? '/img/covers/h150/product.png'; // $catn = $row['catn'];
$amnt = $row['amnt'] ?? 1;
// Инициализация
extract($row);
isset($name) ?: $name = 'Без названия';
isset($catg) ?: $catg = 'Категория';
isset($covr) ?: $covr = '/img/covers/h150/product.png';
$supplies_html = '';
// <button class="button_clean_full d-flex p-0"> foreach ($supplies as $supply) {
// <i class="fas fa-plus-square search_card_cart_icon my-auto"></i> // Конкатенация HTML кода
// </button>
echo <<<HTML // Инициализация артикула
<div class="col-3 p-2 d-flex flex-column search_card rounded"> $catn = $supply['catn'] ?? $supply['onec']['Артикул'];
<div class="col">
<div class="row mb-2"> // Инициализация количества
<img class="w-100 img-fluid rounded" src="$covr"/> $amount = $supply['amnt'] ?? $supply['onec']['Количество'];
</div>
<div class="row mb-2"> if (empty($amount) || $amount < 1) {
<a class="mx-auto text-dark" href="/product/$catn"><h5 class="m-0">$catn</h5></a> $amount = 'Под заказ';
</div> } else {
<div class="row px-2 mb-2"> $amount .= ' шт';
<small class="mt-auto">{$amnt}шт</small> }
<p class="mr-0">1000р</p>
</div> // Инициализация цена
<div class="row"> $price = $supply['prce'] ?? $supply['onec']['Цены']['Цена']['ЦенаЗаЕдиницу'] . ' ' . $supply['onec']['Цены']['Цена']['Валюта'];
<button class="btn button_grey button_clean w-100 text-white">В корзину</button>
</div> $supplies_html .= <<<HTML
</div> <div class="row my-auto m-0 h-100 text-right">
<small class="col-2 my-auto ml-auto">
$amount
</small>
<small class="col-2 my-auto ml-1">
Доставка
</small>
<b class="col-2 my-auto">
$price
</b>
<a class="col-1 h-100 text-dark d-flex" title="Добавить $catn в корзину" href="/cart" role="button" onclick="return cart_write('$catn');">
<i class="fas fa-cart-arrow-down my-auto"></i>
</a>
</div> </div>
HTML; HTML;
if (++$i % 4 === 0 && $amount - $i !== 0) {
echo <<<HTML
<div class="dropdown-divider my-3 mx-4 w-100 "></div>
HTML;
}
} }
} else {
echo <<<HTML echo <<<HTML
<div class="col pb-2">
<div class="row p-2 rounded">
<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>
</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>
</div>
<div class="col ml-2 p-0 d-flex flex-column">
$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;
// if (++$i % 4 === 0 && $amount - $i !== 0) {
// echo <<<HTML
// <div class="dropdown-divider my-3 mx-4 w-100 "></div>
// HTML;
// }
}
} else {
echo <<<HTML
<div class="d-flex flex-column mx-auto"> <div class="d-flex flex-column mx-auto">
<p class="m-0 py-2 d-flex justify-content-center">Ничего не найдено</p> <p class="m-0 py-2 d-flex justify-content-center">Ничего не найдено</p>
</div> </div>
HTML; HTML;
}
} }
?> }
</div> ?>
</article> </article>
</div> </div>
</div> </div>
<script src="/js/cart.js" defer></script>

View File

@ -7,7 +7,19 @@ use app\models\Search;
if (isset($history) && $history) { if (isset($history) && $history) {
// Отображение истории // Отображение истории
if (!yii::$app->user->isGuest && $rows = Search::searchByAccount(sort: ['DESC'])) { if (yii::$app->user->isGuest) {
// Не аутентифицирован
// В будущем выводить историю из cookie
echo <<<HTML
<p class="m-0 py-2 d-flex justify-content-center">Войдите для просмотра истории поиска</p>
HTML;
} else if ($rows = Search::searchByEdge(
from: 'account',
to: 'search',
sort: ['DESC']
)) {
// История поиска существует // История поиска существует
foreach ($rows as $row) { foreach ($rows as $row) {

View File

@ -0,0 +1,11 @@
#page_cart article {
background-color: #fff;
}
#page_cart article .list .row:nth-child(2n+1) {
background-color: #f5f2fa;
}
#page_cart article .list .row:first-child {
background-color: #edeaf2;
}

View File

@ -1,12 +1,22 @@
#page_search nav > div, #page_search article > div { #page_search nav>div,
#page_search article>div>div {
height : 65px;
background-color: #fff; background-color: #fff;
border-right : 0 solid #fff;
transition : 100ms ease-in;
} }
#page_search .search_card:hover { /* #page_search nav > div, #page_search article > div:hover > div {
height: 68px;
border-right: 6px solid #e1e1ea;
border-radius: .25rem 0 0 .25rem !important;
} */
#page_search .search_card:hover {
background-color: #eaeaf0; background-color: #eaeaf0;
} }
#page_search .search_card:hover img { #page_search .search_card:hover img {
border: 1px solid #e1e1ea; border: 1px solid #e1e1ea;
} }

View File

@ -57,6 +57,13 @@ function authentication(form) {
// Обновление документа // Обновление документа
$('meta[name=csrf-token]').prop("content", data._csrf); $('meta[name=csrf-token]').prop("content", data._csrf);
} }
// Реинициализация панели поиска
// Перенести в отдельный файл который подгружается в зависимости от настроек
// search_panel_show();
// Обновление панели поиска
product_search();
}, },
error: function (data, status) { error: function (data, status) {
if (data.responseJSON.main !== undefined) { if (data.responseJSON.main !== undefined) {
@ -108,6 +115,13 @@ function deauthentication() {
// Обновление документа // Обновление документа
$('meta[name=csrf-token]').prop("content", data._csrf); $('meta[name=csrf-token]').prop("content", data._csrf);
} }
// Реинициализация панели поиска
// Перенести в отдельный файл который подгружается в зависимости от настроек
// search_panel_hide();
// Обновление панели поиска
product_search();
} }
}); });
}; };

View File

@ -0,0 +1,61 @@
function cart_write(catn, amount = 1) {
$.ajax({
url: '/order/write',
type: 'post',
dataType: 'json',
data: {
'_csrf': yii.getCsrfToken(),
'supplies': [
catn,
amount
]
},
success: cart_success,
error: cart_error
});
return false;
}
function cart_success(data, status) {
// Обработка ответов от удавшихся запросов
// Основной блок
if (data.main !== undefined) {
main = document.getElementsByTagName('main')[0];
// Обновление документа
main.innerHTML = data.main;
// Реинициализация
reinitialization(main);
}
// CSRF-токен
if (data._csrf !== undefined) {
// Обновление документа
$('meta[name=csrf-token]').prop("content", data._csrf);
}
}
function cart_error(data, status) {
// Обработка ответов от неудавшихся запросов
// Основной блок
if (data.responseJSON.main !== undefined) {
main = document.getElementsByTagName('main')[0];
// Обновление окна результатов поиска
main.innerHTML = data.main;
// Реинициализация
reinitialization(main);
}
// CSRF-токен
if (data.responseJSON._csrf !== undefined) {
// Обновление документа
$('meta[name=csrf-token]').prop("content", data.responseJSON._csrf);
}
}

View File

@ -7,97 +7,44 @@ function page_main() {
type: 'post', type: 'post',
dataType: 'json', dataType: 'json',
data: { '_csrf': yii.getCsrfToken() }, data: { '_csrf': yii.getCsrfToken() },
success: function (data, status) { success: menu_success,
if (data.main !== undefined) { error: menu_error
main = document.getElementsByTagName('main')[0];
// Обновление документа
main.innerHTML = data.main;
// Реинициализация
reinitialization(main);
}
if (data.redirect !== undefined) {
// Перенаправление
history.pushState({}, document.title, data.redirect);
}
if (data._csrf !== undefined) {
// Обновление документа
$('meta[name=csrf-token]').prop("content", data._csrf);
}
},
error: function (data, status) {
if (data.responseJSON.main !== undefined) {
main = document.getElementsByTagName('main')[0];
// Обновление документа
main.innerHTML = data.responseJSON.main;
// Реинициализация
reinitialization(main);
}
if (data.responseJSON.redirect !== undefined) {
// Перенаправление
history.pushState({}, document.title, data.responseJSON.redirect);
}
if (data.responseJSON._csrf !== undefined) {
// Обновление документа
$('meta[name=csrf-token]').prop("content", data.responseJSON._csrf);
}
}
}); });
} }
return false; return false;
}; }
function page_profile() { function page_profile() {
url = '/profile' if (document.getElementById('page_profile') === null) {
url = '/profile'
$.ajax({ $.ajax({
url: url, url: url,
type: 'post', type: 'post',
dataType: 'json', dataType: 'json',
data: { '_csrf': yii.getCsrfToken() }, data: { '_csrf': yii.getCsrfToken() },
success: function (data, status) { success: menu_success,
if (data.main !== undefined) { error: menu_error
main = document.getElementsByTagName('main')[0]; });
}
// Обновление документа return false;
main.innerHTML = data.main; }
// Реинициализация function page_cart() {
reinitialization(main); if (document.getElementById('page_cart') === null) {
} url = '/cart'
if (data.redirect !== undefined) {
// Перенаправление
history.pushState({}, document.title, data.redirect);
}
if (data._csrf !== undefined) {
// Обновление документа
$('meta[name=csrf-token]').prop("content", data._csrf);
}
},
error: function (data, status) {
if (data.responseJSON.main !== undefined) {
main = document.getElementsByTagName('main')[0];
// Обновление документа $.ajax({
main.innerHTML = data.responseJSON.main; url: url,
type: 'post',
// Реинициализация dataType: 'json',
reinitialization(main); data: { '_csrf': yii.getCsrfToken() },
} success: menu_success,
if (data.responseJSON.redirect !== undefined) { error: menu_error
// Перенаправление });
history.pushState({}, document.title, data.responseJSON.redirect); }
}
if (data.responseJSON._csrf !== undefined) {
// Обновление документа
$('meta[name=csrf-token]').prop("content", data.responseJSON._csrf);
}
}
});
return false; return false;
}; };
@ -106,3 +53,75 @@ function notifications() {
return false; return false;
} }
function menu_success(data, status) {
// Обработка ответов от удавшихся запросов
// Основной блок
if (data.main !== undefined) {
// Инициализация
main = document.getElementsByTagName('main')[0];
// Обновление документа
main.innerHTML = data.main;
// Реинициализация
reinitialization(main);
}
// Заголовок
if (data.title !== undefined) {
// Запись
document.title = data.title;
}
// Перенаправление
if (data.redirect !== undefined) {
// Перенаправление
history.pushState({}, document.title, data.redirect);
}
// CSRF-токен
if (data._csrf !== undefined) {
// Обновление документа
$('meta[name=csrf-token]').prop("content", data._csrf);
}
}
function menu_error(data, status) {
// Обработка ответов от неудавшихся запросов
// Инициализация
data = data.responseJSON;
// Основной блок
if (data.main !== undefined) {
// Инициализация
main = document.getElementsByTagName('main')[0];
// Обновление окна результатов поиска
main.innerHTML = data.main;
// Реинициализация
reinitialization(main);
}
// Заголовок
if (data.title !== undefined) {
// Запись
document.title = data.title;
}
// Перенаправление
if (data.redirect !== undefined) {
// Перенаправление
history.pushState({}, document.title, data.redirect);
}
// CSRF-токен
if (data._csrf !== undefined) {
// Обновление документа
$('meta[name=csrf-token]').prop("content", data._csrf);
}
}

View File

@ -1,5 +1,5 @@
function product_search(line, advanced = 0) { function product_search(text = '', advanced = 0) {
if (line.value.length > 1) { if (text.length > 1) {
// Проверка на длину запроса пройдена // Проверка на длину запроса пройдена
$.ajax({ $.ajax({
@ -10,13 +10,13 @@ function product_search(line, advanced = 0) {
'_csrf': yii.getCsrfToken(), '_csrf': yii.getCsrfToken(),
'type': 'product', 'type': 'product',
'advanced': advanced, 'advanced': advanced,
'request': line.value 'request': text
}, },
success: function (data, status) { success: function (data, status) {
search_panel_success(line, advanced, data, status); search_panel_success(text, advanced, data, status);
}, },
error: function (data, status) { error: function (data, status) {
search_panel_error(line, advanced, data, status); search_panel_error(text, advanced, data, status);
}, },
statusCode: search_panel_statusCode() statusCode: search_panel_statusCode()
}); });
@ -27,7 +27,7 @@ function product_search(line, advanced = 0) {
} }
}; };
function product_search_history(line, advanced = 0) { function product_search_history(text = '', advanced = 0) {
$.ajax({ $.ajax({
url: '/search', url: '/search',
type: 'post', type: 'post',
@ -38,45 +38,39 @@ function product_search_history(line, advanced = 0) {
'history': true 'history': true
}, },
success: function (data, status) { success: function (data, status) {
search_panel_success(line, advanced, data, status); search_panel_success(text, advanced, data, status);
}, },
error: function (data, status) { error: function (data, status) {
search_panel_error(line, advanced, data, status); search_panel_error(text, advanced, data, status);
}, },
statusCode: search_panel_statusCode() statusCode: search_panel_statusCode()
}); });
} }
function search_panel_success(line, advanced, data, status) { function search_panel_show(line = 'search_line', panel = 'search_line_window') {
if (data.search_line_window !== undefined) { // Показ панели поиска
search_line_window = document.getElementById('search_line_window');
// Обновление окна результатов поиска // Активация
search_line_window.innerHTML = data.search_line_window; document.getElementById(panel).style.display = '';
// Отображение окна (потом надо переделать) // Открытие
$('#search_line').dropdown('show'); $('#' + line).dropdown('show');
}
// Реинициализация function search_panel_hide(line = 'search_line', panel = 'search_line_window') {
reinitialization(search_line_window); // Сокрытие панели поиска
}
if (data.timer !== undefined) {
// Ожидание перед повторным запросом
if (getCookie('search') !== 'processing') { // Закрытие
// На данный момент нет других запросов поиска $('#' + line).dropdown('hide');
// Запись о существовании запроса // Деактивация
document.cookie = "search=processing; path=/"; document.getElementById(panel).style.display = 'none';
}
// Запрос function search_panel_success(text, advanced, data, status) {
setTimeout(product_search, data.timer + '000', line, advanced); // Обработка ответов от удавшихся запросов
}
} // Основной блок
if (data.search_line_window_hide === 1) {
// Скрытие окна (потом надо переделать)
$('#search_line').dropdown('hide');
}
if (data.main !== undefined) { if (data.main !== undefined) {
main = document.getElementsByTagName('main')[0]; main = document.getElementsByTagName('main')[0];
@ -86,29 +80,61 @@ function search_panel_success(line, advanced, data, status) {
// Реинициализация // Реинициализация
reinitialization(main); reinitialization(main);
} }
if (data.redirect !== undefined) {
// Перенаправление
history.pushState({}, document.title, data.redirect);
}
if (data._csrf !== undefined) {
// Обновление документа
$('meta[name=csrf-token]').prop("content", data._csrf);
}
};
function search_panel_error(line, advanced, data, status) { // Окно поиска
if (data.responseJSON.search_line_window !== undefined) { if (data.panel !== undefined) {
search_line_window = document.getElementById('search_line_window'); panel = document.getElementById('search_line_window');
// Обновление окна результатов поиска // Обновление окна результатов поиска
search_line_window.innerHTML = data.responseJSON.search_line_window; panel.innerHTML = data.panel;
// Отображение окна (потом надо переделать) // Отображение окна (потом надо переделать)
$('#search_line').dropdown('show'); $('#search_line').dropdown('show');
// Реинициализация // Реинициализация
reinitialization(search_line_window); reinitialization(panel);
} }
// Таймер для повтора запроса
if (data.timer !== undefined) {
// Ожидание перед повторным запросом
if (getCookie('search') !== 'processing') {
// На данный момент нет других запросов поиска
// Запись о существовании запроса
search_panel_statusCode_progress();
// Запрос
setTimeout(product_search, data.timer + '000', text, advanced);
}
}
// Перенаправление
if (data.redirect !== undefined) {
// Перенаправление
history.pushState({}, document.title, data.redirect);
}
if (data.responseJSON.hide === 1) {
}
// Сокрытие окна поиска
if (data.search_line_window_hide === 1) {
search_panel_hide();
}
// CSRF-токен
if (data._csrf !== undefined) {
// Обновление документа
$('meta[name=csrf-token]').prop("content", data._csrf);
}
}
function search_panel_error(text, advanced, data, status) {
// Обработка ответов от неудавшихся запросов
// Основной блок
if (data.responseJSON.main !== undefined) { if (data.responseJSON.main !== undefined) {
main = document.getElementsByTagName('main')[0]; main = document.getElementsByTagName('main')[0];
@ -118,10 +144,28 @@ function search_panel_error(line, advanced, data, status) {
// Реинициализация // Реинициализация
reinitialization(main); reinitialization(main);
} }
// Окно поиска
if (data.responseJSON.panel !== undefined) {
panel = document.getElementById('search_line_window');
// Обновление окна результатов поиска
panel.innerHTML = data.responseJSON.panel;
// Отображение окна (потом надо переделать)
$('#search_line').dropdown('show');
// Реинициализация
reinitialization(panel);
}
// Перенаправление
if (data.responseJSON.redirect !== undefined) { if (data.responseJSON.redirect !== undefined) {
// Перенаправление // Перенаправление
history.pushState({}, document.title, data.responseJSON.redirect); history.pushState({}, document.title, data.responseJSON.redirect);
} }
// Таймер для повтора запроса
if (data.responseJSON.timer !== undefined) { if (data.responseJSON.timer !== undefined) {
// Ожидание перед повторным запросом // Ожидание перед повторным запросом
@ -129,30 +173,40 @@ function search_panel_error(line, advanced, data, status) {
// На данный момент нет других запросов поиска // На данный момент нет других запросов поиска
// Запись о существовании запроса // Запись о существовании запроса
document.cookie = "search=processing; path=/"; search_panel_statusCode_progress();
// Запрос // Запрос
setTimeout(product_search, data.responseJSON.timer + '000', line, advanced); setTimeout(product_search, data.responseJSON.timer + '000', text, advanced);
} }
} }
if (data.responseJSON.search_line_window_hide === 1) {
// Скрытие окна (потом надо переделать) // Сокрытие окна поиска
$('#search_line').dropdown('hide'); if (data.responseJSON.hide === 1) {
search_panel_hide();
} }
// CSRF-токен
if (data.responseJSON._csrf !== undefined) { if (data.responseJSON._csrf !== undefined) {
// Обновление документа // Обновление документа
$('meta[name=csrf-token]').prop("content", data.responseJSON._csrf); $('meta[name=csrf-token]').prop("content", data.responseJSON._csrf);
} }
}; }
function search_panel_statusCode() { function search_panel_statusCode() {
return { return {
200: search_panel_statusCode_waiting, 200: search_panel_statusCode_waiting,
404: search_panel_statusCode_waiting, 404: search_panel_statusCode_waiting,
}; };
}; }
function search_panel_statusCode_waiting() { function search_panel_statusCode_waiting() {
// Ожидание нового запроса // Ожидание нового запроса
document.cookie = "search=waiting; path=/"; document.cookie = "search=waiting; path=/";
}; document.body.style.cursor = "unset";
}
function search_panel_statusCode_progress() {
// Выполнение запроса
document.cookie = "search=processing; path=/";
document.body.style.cursor = "progress";
}