Панель настроек, асинхронные запросы, калькулятор лазерной резки

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2021-12-16 09:09:08 +10:00
parent fdd6e010ec
commit 503e25647c
29 changed files with 1787 additions and 232 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
./vendor

View File

@ -0,0 +1,157 @@
<?php
declare(strict_types=1);
namespace mirzaev\calculator\controllers;
use mirzaev\calculator\controllers\core;
use mirzaev\calculator\models\accounts_model as accounts;
/**
* Контроллер пользователей
*
* @package mirzaev\calculator\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class accounts_controller extends core
{
/**
* Страница профиля
*
* @param array $params
* @return void
*/
public function index(array $vars = []): ?string
{
return null;
}
/**
* Регистрация
*
* @param array $vars Параметры запроса
*
* @return string|null HTML-документ
*/
public function registration(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = ['account' => []];
if ($vars['account'] = accounts::registration(email: $vars['email'] ?? null, password: $vars['password'] ?? null, errors: $vars['errors']['account'])) {
// Удалось зарегистрироваться
if ($vars['account'] = accounts::authentication($vars['email'] ?? null, $vars['password'] ?? null, (bool) ($vars['remember'] ?? false), $vars)) {
// Удалось аутентифицироваться
} else {
// Не удалось аутентифицироваться
// Запись кода ответа
http_response_code(401);
}
} else {
// Не удалось зарегистрироваться
// Запись кода ответа
http_response_code(401);
}
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'main' . DIRECTORY_SEPARATOR . 'index.html', $vars);
}
/**
* Аутентификация
*
* @param array $vars Параметры запроса
*
* @return string|null HTML-документ
*/
public function authentication(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = ['account' => []];
if ($vars['account'] = accounts::authentication($vars['email'] ?? null, $vars['password'] ?? null, (bool) ($vars['remember'] ?? false), errors: $vars['errors']['account'])) {
// Удалось аутентифицироваться
} else {
// Не удалось аутентифицироваться
// Запись кода ответа
http_response_code(401);
}
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'main' . DIRECTORY_SEPARATOR . 'index.html', $vars);
}
/**
* Деаутентификация
*
* @param array $vars Параметры запроса
*
* @return string|null HTML-документ
*/
public function deauthentication(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = ['account' => []];
if (accounts::deauthentication(errors: $vars['errors']['account'])) {
// Удалось деаутентифицироваться
// Деинициализация аккаунта
$vars['account'] = null;
} else {
// Не удалось деаутентифицироваться
// Запись кода ответа
http_response_code(500);
}
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'main' . DIRECTORY_SEPARATOR . 'index.html', $vars);
}
/**
* Данные аккаунта
*
* Если информацию запрашивает администратор, то вернётся вся, иначе только разрешённая публично
*
* @param array $vars Параметры запроса
*
* @return string JSON-документ
*/
public function data(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = ['account' => []];
if ($account = accounts::read(['id' => $vars['id']], $vars['errors'])) {
// Найдены данные запрашиваемого аккаунта
// Инициализация аккаунта
$vars['account'] = accounts::account($vars['errors']);
if ($vars['account'] && $vars['account']['permissions']['accounts'] ?? 0 === 1) {
// Удалось аутентифицироваться и пройдена проверка авторизации
} else {
// Не удалось аутентифицироваться
// Удаление запрещённых к публикации полей
$account['password'] = $account['hash'] = $account['time'] = null;
}
// Генерация ответа
return json_encode($account ?? '');
} else {
// Не найдены данные запрашиваемого аккаунта
// Запись кода ответа
http_response_code(404);
}
return null;
}
}

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace mirzaev\calculator\controllers; namespace mirzaev\calculator\controllers;
use mirzaev\calculator\controllers\core; use mirzaev\calculator\controllers\core;
use mirzaev\calculator\models\calculators_model as calculators;
use Twig\Loader\FilesystemLoader; use Twig\Loader\FilesystemLoader;
use Twig\Environment as view; use Twig\Environment as view;
@ -119,4 +120,71 @@ final class calculator_controller extends core
// Генерация представления // Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'calculators' . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . 'calculators' . DIRECTORY_SEPARATOR . 'laser.html', $vars); return $this->view->render(DIRECTORY_SEPARATOR . 'calculators' . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . 'calculators' . DIRECTORY_SEPARATOR . 'laser.html', $vars);
} }
/**
* Рассчёт
*
* Генерирует ответ в виде ['expenses' => 0, 'income' => 0, 'profit' => 0]
*
* @param array $vars Параметры
*
* @todo
* 1. Отправлять данные в зависимости от разрешения (обычным пользователям только expenses)
*/
public function calculate(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = ['calculators' => []];
// Инициализация калькуляторов из тела запроса (подразумевается, что там массивы с параметрами)
$calculators = json_decode(file_get_contents('php://input'), true);
// Инициализация переменных для буфера вывода
$machines = $managers = $engineers = $operators = 0;
foreach ($calculators as $i => $calculator) {
// Перебор калькуляторов
foreach (['type'] as $parameter) {
// Перебор мета-параметров
// Инициализация общего параметра
extract([$parameter => $calculator[$parameter] ?? null]);
// Инициализация параметра для обработчика калькулятора
unset($calculator[$parameter]);
}
// Инициализация номера калькулятора в его категории
$number = count($vars['errors']['calculators'][$type] ?? []);
// Инициализация журнала ошибок для калькулятора
$calculator['errors'] = [];
// Инициализация журнала ошибок для буфера вывода
$vars['errors']['calculators'][$type][$number] = &$calculator['errors'];
// Инициализация буфера параметров
$parameters = [];
// Инициализация параметра типа покупателя (подразумевается, что если не "entity", то "individual")
$parameters['company'] = $calculator['buyer'] === 'entity';
unset($calculator['buyer']);
// Перенос остальных параметров в буфер параметров
$parameters += $calculator;
// var_dump($parameters);
// Расчёт
[$machines, $managers, $engineers, $operators] = calculators::$type(...$parameters);
}
return json_encode([
'machines' => $machines,
'managers' => $managers,
'engineers' => $engineers,
'operators' => $operators
]);
}
} }

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace mirzaev\calculator\controllers; namespace mirzaev\calculator\controllers;
use mirzaev\calculator\views\manager; use mirzaev\calculator\views\manager;
use mirzaev\calculator\models\core as models;
use mirzaev\minimal\controller; use mirzaev\minimal\controller;
@ -25,6 +26,9 @@ class core extends controller
{ {
parent::__construct(); parent::__construct();
// Инициализация ядра моделей (соединение с базой данных...)
new models();
$this->view = new manager; $this->view = new manager;
} }
} }

View File

@ -19,12 +19,18 @@ final class errors_controller extends core
{ {
public function error404() public function error404()
{ {
// Запись кода ответа
http_response_code(404);
// Генерация представления // Генерация представления
return 'Не найдено (404)'; return 'Не найдено (404)';
} }
public function error500() public function error500()
{ {
// Запись кода ответа
http_response_code(500);
// Генерация представления // Генерация представления
return 'Внутренняя ошибка (500)'; return 'Внутренняя ошибка (500)';
} }

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace mirzaev\calculator\controllers; namespace mirzaev\calculator\controllers;
use mirzaev\calculator\controllers\core; use mirzaev\calculator\controllers\core;
use mirzaev\calculator\models\accounts_model as accounts;
use Twig\Loader\FilesystemLoader; use Twig\Loader\FilesystemLoader;
use Twig\Environment as view; use Twig\Environment as view;
@ -17,9 +18,15 @@ use Twig\Environment as view;
*/ */
final class main_controller extends core final class main_controller extends core
{ {
public function index(array $params = []) public function index(array $vars = [])
{ {
// Инициализация журнала ошибок
$vars['errors'] = ['account' => []];
// Проверка аутентифицированности
$vars['account'] = accounts::account($vars['errors']);
// Генерация представления // Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'main' . DIRECTORY_SEPARATOR . 'index.html'); return $this->view->render(DIRECTORY_SEPARATOR . 'main' . DIRECTORY_SEPARATOR . 'index.html', $vars);
} }
} }

View File

@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace mirzaev\calculator\controllers;
use mirzaev\calculator\controllers\core;
use mirzaev\calculator\models\accounts_model as accounts;
use mirzaev\calculator\models\settings_model as settings;
/**
* Контроллер страницы настроек
*
* @package mirzaev\calculator\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class settings_controller extends core
{
/**
* Настройки (страница)
*
* HTML-документ со списком настроек
*
* @param array $vars Параметры
*/
public function index(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = ['settings' => []];
// Инициализация аккаунта
$vars['account'] = accounts::account($vars['errors']);
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'settings' . DIRECTORY_SEPARATOR . 'index.html', $vars);
}
/**
* Записать
*
* @param array $vars Параметры
*/
public function write(array $vars = []): ?bool
{
// Инициализация журнала ошибок
$vars['errors'] = ['settings' => []];
// Инициализация аккаунта
$vars['account'] = accounts::account($vars['errors']);
if ($vars['account'] && $vars['account']['permissions']['settings'] ?? 0 === 1) {
// Удалось аутентифицироваться и пройдена проверка авторизации
foreach ($vars['settings'] ?? [] as $name => $value) {
// Перебор настроек
settings::write($name, $value);
return true;
}
}
return false;
}
/**
* Прочитать
*
* @param array $vars Параметры
*/
public function read(array $vars = []): ?string
{
// Инициализация журнала ошибок
$vars['errors'] = ['settings' => []];
// Инициализация аккаунта
$vars['account'] = accounts::account($vars['errors']);
// Инициализация буфера вывода
$settings = [];
foreach ($vars['settings'] ?? [] as $name) {
// Перебор настроек
$settings[] = settings::read($name, $vars['account'] && $vars['account']['permissions']['settings'] ?? 0 === 1);
}
return json_encode($settings);
}
}

View File

@ -0,0 +1,531 @@
<?php
declare(strict_types=1);
namespace mirzaev\calculator\models;
use pdo;
use exception;
/**
* Модель регистрации, аутентификации и авторизации
*
* @package mirzaev\calculator\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class accounts_model extends core
{
/**
* Регистрация
*
* @param string $name Входной псевдоним
* @param string $email Почта
* @param string $password Пароль (password)
* @param bool $authentication Автоматическая аутентификация в случае успешной регистрации
* @param array &$errors Журнал ошибок
*
* @return array|bool Аккаунт, если удалось аутентифицироваться
*/
public static function registration(string $name = null, string $email = null, string $password, array &$errors = []): array
{
try {
if (static::account($errors)) {
// Аутентифицирован пользователь
// Запись ошибки
throw new exception('Уже аутентифицирован');
}
if (empty($account = static::read(['name' => $name]) or $account = static::read(['email' => $email]))) {
// Не удалось найти аккаунт
if (static::write($name, $email, $password, $errors)) {
// Удалось зарегистрироваться
return $account;
}
} else {
// Удалось найти аккаунт
return $account;
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = $e->getMessage();
}
return [];
}
/**
* Аутентификация
*
* @param string $login Входной псевдоним
* @param string $password Пароль (password)
* @param bool $remember Функция "Запомнить меня" - увеличенное время хранения cookies
* @param array &$errors Журнал ошибок
*
* @return array Аккаунт (если не найден, то пустой массив)
*/
public static function authentication(string $login, string $password, bool $remember = false, array &$errors = []): array
{
try {
if (static::account($errors)) {
// Аутентифицирован пользователь
// Запись ошибки
throw new exception('Уже аутентифицирован');
}
if (empty($account = static::read(['name' => $login]) or $account = static::read(['email' => $login]))) {
// Не удалось найти аккаунт
throw new exception('Не удалось найти аккаунт');
}
if (password_verify($password, $account['password'])) {
// Совпадают хеши паролей
// Инициализация идентификатора сессии
session_id($account['id']);
// Инициализация названия сессии
session_name('id');
// Инициализация сессии
session_start();
// Инициализация времени хранения хеша
$time = time() + ($remember ? 604800 : 86400);
// Инициализация хеша
$hash = static::hash((int) $account['id'], crypt($account['password'], time() . $account['id']), $time, $errors)['hash'];
// Инициализация cookies
setcookie("hash", $hash, $time, path: '/', secure: true);
return $account;
} else {
// Не совпадают хеши паролей
throw new exception('Неправильный пароль');
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = $e->getMessage();
}
return [];
}
/**
* Аутентификация
*
* @param array &$errors Журнал ошибок
*
* @return bool Удалось ли деаутентифицироваться
*/
public static function deauthentication(array &$errors = []): bool
{
try {
if ($account = static::account($errors)) {
// Аутентифицирован пользователь
// Инициализация запроса
$request = static::$db->prepare("UPDATE `accounts` SET `hash` = null, `time` = 0 WHERE `id` = :id");
// Параметры запроса
$params = [
":id" => $account['id'],
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
$request->fetch(pdo::FETCH_ASSOC);
// Деинициализация cookies
setcookie("id", '', 0, path: '/', secure: true);
setcookie("hash", '', 0, path: '/', secure: true);
return true;
} else {
// Не аутентифицирован пользователь
// Запись ошибки
throw new exception('Не аутентифицирован');
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = $e->getMessage();
}
return false;
}
/**
* Прочитать данные аккаунта, если пользователь аутентифицирован
*
* Можно использовать как проверку на аутентифицированность
*
* @param array &$errors Журнал ошибок
*
* @return array Аккаунт (если не найден, то пустой массив)
*
* @todo 1. Сделать в static::read() возможность передачи нескольких параметров и перенести туда непосредственно чтение аккаунта с проверкой хеша
*/
public static function account(array &$errors = []): array
{
try {
if (!empty($_COOKIE['id']) && !empty($_COOKIE['hash'])) {
// Аутентифицирован аккаунт (найдены cookie и они хранят значения - подразумевается, что не null или пустое)
if ($_COOKIE['hash'] === static::hash((int) $_COOKIE['id'], errors: $errors)['hash']) {
// Совпадает переданный хеш с тем, что хранится в базе данных
} else {
// Не совпадает переданный хеш с тем, что хранится в базе данных
// Генерация ошибки
throw new exception('Вы аутентифицированы с другого устройства (не совпадают хеши аутентификации)');
}
// Инициализация запроса
$request = static::$db->prepare("SELECT * FROM `accounts` WHERE `id` = :id && `hash` = :hash");
// Параметры запроса
$params = [
":id" => $_COOKIE['id'],
":hash" => $_COOKIE['hash'],
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
if (empty($account = $request->fetch(pdo::FETCH_ASSOC))) {
// Не найдена связка идентификатора с хешем
// Генерация ошибки
throw new exception('Не найден пользотватель или время аутентификации истекло');
}
// Чтение разрешений
$account['permissions'] = static::permissions((int) $account['id'], $errors);
return $account;
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = $e->getMessage();
}
return [];
}
/**
* Прочитать разрешения аккаунта
*
* @param int $id Идентификатор аккаунта
* @param array &$errors Журнал ошибок
*
* @return array Разрешения аккаунта, если найдены
*/
public static function permissions(int $id, array &$errors = []): array
{
try {
// Инициализация запроса
$request = static::$db->prepare("SELECT * FROM `permissions` WHERE `id` = :id");
// Параметры запроса
$params = [
":id" => $id
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
if (empty($response = $request->fetch(pdo::FETCH_ASSOC))) {
// Не найдены разрешения
// Генерация ошибки
throw new exception('Не найдены разрешения');
}
// Удаление ненужных данных
unset($response['id']);
return $response;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = $e->getMessage();
}
return [];
}
/**
* Запись пользователя в базу данных
*
* @param string|null $name Имя
* @param string|null $email Почта
* @param string|null $password Пароль
* @param array &$errors Журнал ошибок
*
* @return array Аккаунт (если не найден, то пустой массив)
*/
public static function write(string|null $name = null, string|null $email = null, string|null $password = null, array &$errors = []): array
{
try {
// Инициализация параметров запроса
$params = [];
if (isset($name)) {
try {
// Проверка параметра
if (iconv_strlen($name) < 3) throw new exception('Длина имени должна быть не менее 3 символов');
if (iconv_strlen($name) > 60) throw new exception('Длина имени должна быть не более 60 символов');
// Запись в буфер параметров запроса
$params[':name'] = $name;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = $e->getMessage();
goto end;
}
}
if (isset($email)) {
try {
// Проверка параметра
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) throw new exception('Не удалось распознать почту');
if (iconv_strlen($email) < 3) throw new exception('Длина почты должна быть не менее 3 символов');
if (iconv_strlen($email) > 60) throw new exception('Длина почты должна быть не более 80 символов');
// Запись в буфер параметров запроса
$params[':email'] = $email;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = $e->getMessage();
goto end;
}
}
if (isset($password)) {
try {
// Проверка параметра
if (iconv_strlen($password) < 3) throw new exception('Длина пароля должна быть не менее 3 символов');
if (iconv_strlen($password) > 60) throw new exception('Длина пароля должна быть не более 120 символов');
// Запись в буфер параметров запроса
$params[':password'] = password_hash($password, PASSWORD_BCRYPT);
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = $e->getMessage();
goto end;
}
}
// Инициализация запроса
$request = static::$db->prepare("INSERT INTO `accounts` (" . (isset($name) ? '`name`' : '') . (isset($name) && isset($email) ? ', ' : '') . (isset($email) ? '`email`' : '') . ((isset($name) || isset($email)) && isset($password) ? ', ' : '') . (isset($password) ? '`password`' : '') . ") VALUES (" . (isset($name) ? ':name' : '') . (isset($name) && isset($email) ? ', ' : '') . (isset($email) ? ':email' : '') . ((isset($name) || isset($email)) && isset($password) ? ', ' : '') . (isset($password) ? ':password' : '') . ")");
// Отправка запроса
$request->execute($params);
// Генерация ответа
$request->fetch(pdo::FETCH_ASSOC);
try {
if (isset($name)) {
// Передано имя аккаунта
// Чтение аккаунта
$account = static::read(['name' => $name]);
} else if (isset($email)) {
// Передана почта аккаунта
// Чтение аккаунта
$account = static::read(['email' => $email]);
} else {
// Не передано ни имя, ни почта
throw new exception('Не переданны данные для полноценной регистрации аккаунта');
}
// Инициализация запроса
$request = static::$db->prepare("INSERT INTO `permissions` (`id`) VALUES (:id)");
// Инициализация параметров
$params = [
':id' => $account['id']
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
$request->fetch(pdo::FETCH_ASSOC);
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = $e->getMessage();
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = $e->getMessage();
}
// Конец выполнения
end:
return isset($account) && $account ? $account : [];
}
/**
* Чтение пользователя из базы данных
*
* @param array $search Поиск ('поле' => 'значение'), работает только с одним полем
* @param array &$errors Журнал ошибок
*
* @return array Аккаунт, если найден
*/
public static function read(array $search, array &$errors = []): array
{
try {
// Инициализация данных для поиска
$field = array_keys($search)[0] ?? null;
$value = $search[$field] ?? null;
if (empty($field)) {
// Получено пустое значение поля
// Запись ошибки
throw new exception('Пустое значение поля для поиска');
}
// Инициализация запроса
$request = static::$db->prepare("SELECT * FROM `accounts` WHERE `$field` = :field LIMIT 1");
// Параметры запроса
$params = [
":field" => $value,
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
if ($account = $request->fetch(pdo::FETCH_ASSOC)) {
// Найден аккаунт
try {
if ($permissions = static::permissions((int) $account['id'], $errors)) {
// Найдены разрешения
// Запись в буфер данных аккаунта
$account['permissions'] = $permissions;
} else {
// Не найдены разрешения
throw new exception('Не удалось найти и прочитать разрешения');
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = $e->getMessage();
}
} else {
// Не найден аккаунт
throw new exception('Не удалось найти аккаунт');
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = $e->getMessage();
}
return isset($account) && $account ? $account : [];
}
/**
* Запись или чтение хеша из базы данных
*
* @param int $id Идентификатор аккаунта
* @param int|null $hash Хеш аутентифиакции
* @param string|null $time Время хранения хеша
* @param array &$errors Журнал ошибок
*
* @return array ['hash' => $hash, 'time' => $time]
*/
public static function hash(int $id, string|null $hash = null, int|null $time = null, array &$errors = []): array
{
try {
if (isset($hash, $time)) {
// Переданы хеш и его время хранения
// Инициализация запроса
$request = static::$db->prepare("UPDATE `accounts` SET `hash` = :hash, `time` = :time WHERE `id` = :id");
// Параметры запроса
$params = [
":id" => $id,
":hash" => $hash,
":time" => $time,
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
$request->fetch(pdo::FETCH_ASSOC);
} else {
// Не переданы хеш и его время хранения
// Инициализация запроса
$request = static::$db->prepare("SELECT `hash`, `time` FROM `accounts` WHERE `id` = :id");
// Параметры запроса
$params = [
":id" => $id,
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
extract((array) $request->fetch(pdo::FETCH_ASSOC));
if (!empty($response['time']) && $response['time'] <= time()) {
// Истекло время жизни хеша
// Инициализация запроса
$request = static::$db->prepare("UPDATE `accounts` SET `hash` = :hash, `time` = :time WHERE `id` = :id");
// Параметры запроса
$params = [
":id" => $id,
":hash" => null,
":time" => null,
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
$response = $request->fetch(pdo::FETCH_ASSOC);
// Генерация ошибки
throw new exception('Время аутентификации истекло');
}
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = $e->getMessage();
}
return ['hash' => $hash, 'time' => $time];
}
}

View File

@ -0,0 +1,186 @@
<?php
declare(strict_types=1);
namespace mirzaev\calculator\models;
use mirzaev\calculator\models\settings_model as settings;
use exception;
/**
* Модель калькуляторов
*
* @package mirzaev\calculator\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class calculators_model extends core
{
/**
* Рассчет калькулятора лазерной резки
*
* @param bool|int|string|null $company Юридическое лицо? Или физическое лицо?
* @param string|null $complexity Сложность ('easy', 'medium', 'hard')
* @param int|string|null $width Высота (мм)
* @param int|string|null $height Ширина (мм)
* @param int|string|null $lenght Длина (мм)
* @param int|string|null $amount Количество
* @param string|null $metal Тип металла
* @param int|string|null $holes Количество отверстий
* @param int|string|null $diameter Диаметр отверстий (мм)
* @param array &$errors Журнал ошибок
*
* @return array|bool Аккаунт, если удалось аутентифицироваться
*
* @todo
* 1. Значения по умолчанию брать из настроек в базе данных
*/
public static function laser(
bool|int|string|null $company = null,
string|null $complexity = null,
int|string|null $width = null,
int|string|null $height = null,
int|string|null $length = null,
int|string|null $amount = null,
string|null $metal = null,
int|string|null $holes = null,
int|string|null $diameter = null,
array &$errors = []
): array {
try {
// Инициализация переменных для буфера вывода
$machine = $manager = $engineer = $operator = 0;
// Инициализация значений по умолчанию (см. @todo)
$company = (bool) ($company ?? settings::read('default_buyer', $errors) === 'entity' ?? false);
$complexity = (string) ($complexity ?? settings::read('default_complexity', $errors) ?? 'medium');
$width = (int) ($width ?? settings::read('default_width', $errors) ?? 500);
$height = (int) ($height ?? settings::read('default_height', $errors) ?? 500);
$length = (int) ($length ?? settings::read('default_length', $errors) ?? 1);
$amount = (int) ($amount ?? settings::read('default_amount', $errors) ?? 1);
$metal = (string) ($metal ?? settings::read('default_metal', $errors) ?? 'stainless_steel');
$holes = (int) ($holes ?? settings::read('default_holes', $errors) ?? 0);
$diameter = (int) ($diameter ?? settings::read('default_diameter', $errors) ?? 0);
// Стоисмость киловатта электроэнергии
$electricity = settings::read('electricity', $errors) ?? 6.5;
// Потребляемая электроэнергия станком (квт/ч)
$power = settings::read('laser_power', $errors) ?? 2;
// 1 мм толщина = 220 мм/с рез у нержавеющей стали
$speed = 220;
// Вычисление площади
$area = $width * $height;
// Коэффициент сложности
$coefficient = ($area <= (settings::read('coefficient_area_less', $errors) ?? 10000) || $area >= (settings::read('coefficient_area_more', $errors) ?? 100000) ? (settings::read('coefficient_area_degree', $errors) ?? 0.2) : 0) + match ($complexity) {
'easy' => (settings::read('coefficient_complexity_easy', $errors) ?? 0.8),
'medium' => (settings::read('coefficient_complexity_medium', $errors) ?? 1),
'hard' => (settings::read('coefficient_complexity_hard', $errors) ?? 1.2),
default => (settings::read('coefficient_complexity_medium', $errors) ?? 1)
};
// Расчет длины реза (мм)
eval(settings::read('laser_formula_cutting', $errors) ?? '$cutting = 3.14 * $diameter * $holes + ($width * 2 + $height * 2) * $coefficient;');
// Скорость реза в час (мм/с)
eval(settings::read('laser_formula_speed', $errors) ?? '$speed = 3600 * $speed;');
// Стоимость реза в час
eval(settings::read('laser_formula_hour', $errors) ?? '$hour = $electricity * $power;');
// Стоимость 1 миллиметра реза
eval(settings::read('laser_formula_millimeter', $errors) ?? '$millimeter = $hour / $speed;');
// Cтанок (стоимость работы)
eval(settings::read('laser_formula_machine', $errors) ?? '$machine = $cutting * $millimeter * $amount;');
var_dump($company, $complexity, $width, $height, $length, $amount, $metal, $holes, $diameter);
if ($company) {
// Юридическое лицо
$min = settings::read('manager_entity_min', $errors) ?? 1;
$max = settings::read('manager_entity_max', $errors) ?? 7;
$average = ($min + $max) / 2;
// Менеджер (стоимость работы)
$manager = (settings::read('manager_entity_hour', $errors) ?? 200) * match ($complexity) {
'easy' => $min,
'medium' => $average,
'hard' => $max,
default => $average
};
$min = settings::read('engineer_entity_min', $errors) ?? 2;
$max = settings::read('engineer_entity_max', $errors) ?? 72;
$average = ($min + $max) / 2;
// Инженер (стоимость работы)
$engineer = (settings::read('engineer_entity_hour', $errors) ?? 400) * match ($complexity) {
'easy' => $min,
'medium' => $average,
'hard' => $max,
default => $average
};
$min = settings::read('operator_entity_min', $errors) ?? 0.33;
$max = settings::read('operator_entity_max', $errors) ?? 16;
$average = ($min + $max) / 2;
// Оператор
$operator = (settings::read('operator_entity_hour', $errors) ?? 200) * match ($complexity) {
'easy' => $min,
'medium' => $average,
'hard' => $max,
default => $average
};
} else {
// Физическое лицо
$min = settings::read('manager_individual_min', $errors) ?? 1;
$max = settings::read('manager_individual_max', $errors) ?? 3;
$average = ($min + $max) / 2;
// Менеджер (стоимость работы)
$manager = (settings::read('manager_individual_hour', $errors) ?? 200) * match ($complexity) {
'easy' => $min,
'medium' => $average,
'hard' => $max,
default => $average
};
$min = settings::read('manager_individual_min', $errors) ?? 2;
$max = settings::read('manager_individual_max', $errors) ?? 10;
$average = ($min + $max) / 2;
// Инженер (стоимость работы)
$engineer = (settings::read('engineer_individual_hour', $errors) ?? 300) * match ($complexity) {
'easy' => $min,
'medium' => $average,
'hard' => $max,
default => $average
};
$min = settings::read('manager_individual_min', $errors) ?? 0.33;
$max = settings::read('manager_individual_max', $errors) ?? 7;
$average = ($min + $max) / 2;
// Оператор (стоимость работы)
$operator = (settings::read('operator_individual_hour', $errors) ?? 200) * match ($complexity) {
'easy' => $min,
'medium' => $average,
'hard' => $max,
default => $average
};
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = $e->getMessage();
}
return [[$machine], [$manager], [$engineer], [$operator]];
}
}

View File

@ -0,0 +1,139 @@
<?php
declare(strict_types=1);
namespace mirzaev\calculator\models;
use mirzaev\minimal\model;
use pdo;
use pdoexception;
use exception;
/**
* Ядро моделей
*
* @package mirzaev\calculator\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
class core extends model
{
/**
* Соединение с базой данных
*/
protected static PDO $db ;
public function __construct(pdo $db = null)
{
if (isset($db)) {
// Получена инстанция соединения с базой данных
// Запись и инициализация соединения с базой данных
$this->__set('db', $db);
} else {
// Не получена инстанция соединения с базой данных
// Инициализация соединения с базой данных по умолчанию
$this->__get('db');
}
}
/**
* Записать свойство
*
* @param string $name Название
* @param mixed $value Значение
*/
public function __set(string $name, mixed $value = null): void
{
match ($name) {
'db' => (function () use ($value) {
if ($this->__isset('db')) {
// Свойство уже было инициализировано
// Выброс исключения (неудача)
throw new exception('Запрещено реинициализировать соединение с базой данных ($this->db)', 500);
} else {
// Свойство ещё не было инициализировано
if ($value instanceof pdo) {
// Передано подходящее значение
// Запись свойства (успех)
self::$db = $value;
} else {
// Передано неподходящее значение
// Выброс исключения (неудача)
throw new exception('Соединение с базой данных ($this->db) должен быть инстанцией PDO', 500);
}
}
})(),
default => parent::__set($name, $value)
};
}
/**
* Прочитать свойство
*
* @param string $name Название
*
* @return mixed Содержимое
*/
public function __get(string $name): mixed
{
return match ($name) {
'db' => (function () {
if (!$this->__isset('db')) {
// Свойство не инициализировано
// Инициализация значения по умолчанию исходя из настроек
$this->__set('db', new pdo(\TYPE . ':dbname=' . \BASE . ';host=' . \HOST, LOGIN, PASSWORD));
}
return self::$db;
})(),
default => parent::__get($name)
};
}
/**
* Проверить свойство на инициализированность
*
* @param string $name Название
*/
public function __isset(string $name): bool
{
return match ($name) {
default => parent::__isset($name)
};
}
/**
* Удалить свойство
*
* @param string $name Название
*/
public function __unset(string $name): void
{
match ($name) {
default => parent::__isset($name)
};
}
/**
* Статический вызов
*
* @param string $name Название
* @param array $arguments Параметры
*/
public static function __callStatic(string $name, array $arguments): mixed
{
match ($name) {
'db' => (new static)->__get('db'),
default => throw new exception("Не найдено свойство или функция: $name", 500)
};
}
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace mirzaev\calculator\models;
use pdo;
use exception;
/**
* Модель настроек
*
* @package mirzaev\calculator\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class settings_model extends core
{
/**
* Прочитать
*
* @param string $name Название параметра
* @param array &$errors Журнал ошибок
*
* @return array Аккаунт, если найден
*/
public static function read(string $name, array &$errors = []): int|float|string|array|null
{
try {
// Инициализация запроса
$request = static::$db->prepare("SELECT `$name` FROM `settings` WHERE `id` = 1 ORDER BY `id` DESC LIMIT 1");
// Отправка запроса
$request->execute();
// Генерация ответа
return $request->fetchColumn();
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = $e->getMessage();
}
return null;
}
}

View File

@ -1,8 +1,21 @@
#authentication>form { #authentication>:is(form, div) {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
#authentication .exit {
margin-top: 25px;
}
#authentication p {
margin: 0;
display: flex;
}
#authentication p>span {
margin-left: auto;
}
#authentication>form>input:is([type="text"], [type="password"]) { #authentication>form>input:is([type="text"], [type="password"]) {
margin-bottom: 12px; margin-bottom: 12px;
} }
@ -13,6 +26,7 @@
#authentication>form>.submit { #authentication>form>.submit {
margin-top: 6px; margin-top: 6px;
margin-bottom: 10px;
display: flex; display: flex;
} }
@ -28,3 +42,26 @@
border: unset; border: unset;
border-radius: 0 3px 3px 0; border-radius: 0 3px 3px 0;
} }
#authentication>form>input[type=submit].registration {
padding: 7px 20px;
background-color: #86781C;
}
#authentication>form>input[type=submit].registration:hover {
background-color: #9e8d20;
}
#authentication>form>input[type=submit].registration:is(:active, :focus) {
background-color: #776b19;
}
#authentication>form>ul.errors {
margin-top: 18px;
margin-bottom: 0px;
padding: 10px;
text-align: center;
list-style: none;
background-color: #ae8f8f;
}

View File

@ -32,26 +32,49 @@
margin: 8px auto 15px; margin: 8px auto 15px;
} }
#calculator>#result>:last-child { #calculator>#result>div {
width: 30%; width: 30%;
} }
#calculator>#result>nav {
margin-right: 30px;
width: 100%;
display: flex;
}
#calculator>#result>nav>a {
margin-top: auto;
}
#calculator>#result>nav>a#calculate {
padding: 10px 5px;
}
#calculator>#result, #calculator>#result,
#calculator>#result :last-child>p { #calculator>#result>div>p {
font-weight: bold; font-weight: bold;
display: flex; display: flex;
} }
#calculator>#result :last-child>p * { #calculator>#result>div>p * {
font-weight: normal; font-weight: normal;
} }
#calculator>#result>:last-child, #calculator>#result>div,
#calculator>#result :last-child>p>:is(#expenses, #income, #profit) { #calculator>#result>div>p>:is(#expenses, #income, #profit) {
margin-left: auto; margin-left: auto;
} }
#calculator>#result :last-child>p>:last-child { #calculator>#result>nav>a:first-child:not(:only-of-type),
#calculator>#result>div>p:first-child:not(:only-of-type) {
margin-top: 0;
}
#calculator>#result>nav>a:last-child,
#calculator>#result>div>p:last-child {
margin-bottom: 0;
}
#calculator>#result>div>p>:last-child {
margin-left: 4px; margin-left: 4px;
} }
@ -59,58 +82,58 @@
margin-top: 30px; margin-top: 30px;
} }
#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting']) { #calculator>.calculator {
padding: 5px 30px 10px 30px; padding: 5px 30px 10px 30px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting'])>div { #calculator>.calculator>div {
display: flex; display: flex;
} }
#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting'])>div>label { #calculator>.calculator>div>label {
font-weight: bold; font-weight: bold;
margin: auto 15px auto auto; margin: auto 15px auto auto;
} }
#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting'])>div:not(:last-child) { #calculator>.calculator>div:not(:last-child) {
margin-bottom: 15px; margin-bottom: 15px;
} }
#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting'])>div>div { #calculator>.calculator>div>div {
width: 60%; width: 60%;
display: flex; display: flex;
} }
#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting'])>div>div { #calculator>.calculator>div>div {
height: 30px; height: 30px;
} }
#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting'])>div>div>:is(input, small, span):not(.measured, :last-child) { #calculator>.calculator>div>div>:is(input, small, span):not(.measured, :last-child) {
margin-right: 5px !important; margin-right: 5px !important;
} }
#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting'])>div>div>:is(input, .unit) { #calculator>.calculator>div>div>:is(input, .unit) {
font-size: small; font-size: small;
} }
#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting'])>div>div>input { #calculator>.calculator>div>div>input {
width: 25px; width: 25px;
padding: 5px 10px; padding: 5px 10px;
text-align: center; text-align: center;
} }
#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting'])>div>div>input.measured { #calculator>.calculator>div>div>input.measured {
padding-right: 3px; padding-right: 3px;
text-align: right; text-align: right;
} }
#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting'])>div>div>small { #calculator>.calculator>div>div>small {
margin: auto 0; margin: auto 0;
} }
#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting'])>div>div>input+.unit { #calculator>.calculator>div>div>input+.unit {
margin: auto 0; margin: auto 0;
padding-top: unset; padding-top: unset;
padding-bottom: unset; padding-bottom: unset;

View File

@ -1,5 +1,6 @@
* { * {
font-family: "open sans"; font-family: "open sans";
text-decoration: none;
} }
body { body {
@ -69,7 +70,7 @@ header>nav {
top: 0; top: 0;
width: 100%; width: 100%;
height: 40px; height: 40px;
padding: 8px calc(18.5% - 20px); padding: 8px 20%;
position: sticky; position: sticky;
display: flex; display: flex;
pointer-events: all; pointer-events: all;

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 64 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -8,6 +8,11 @@ use mirzaev\minimal\core;
use mirzaev\minimal\router; use mirzaev\minimal\router;
define('VIEWS', realpath('..' . DIRECTORY_SEPARATOR . 'views')); define('VIEWS', realpath('..' . DIRECTORY_SEPARATOR . 'views'));
define('TYPE', 'mysql');
define('BASE', 'calculator');
define('HOST', '127.0.0.1');
define('LOGIN', 'root');
define('PASSWORD', '');
// Автозагрузка // Автозагрузка
require __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php'; require __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
@ -17,13 +22,22 @@ $router = new router;
// Запись маршрутов // Запись маршрутов
$router->write('/', 'main', 'index'); $router->write('/', 'main', 'index');
$router->write('/calculator', 'calculator', 'index'); $router->write('/account/registration', 'accounts', 'registration', 'POST');
$router->write('/calculator/generate/buyer', 'calculator', 'buyer'); $router->write('/account/authentication', 'accounts', 'authentication', 'POST');
$router->write('/calculator/generate/complexity', 'calculator', 'complexity'); $router->write('/account/deauthentication', 'accounts', 'deauthentication', 'POST');
$router->write('/calculator/generate/menu', 'calculator', 'menu'); $router->write('/account/deauthentication', 'accounts', 'deauthentication', 'GET');
$router->write('/calculator/generate/result', 'calculator', 'result'); $router->write('/account/data', 'accounts', 'data', 'POST');
$router->write('/calculator/generate/divider', 'calculator', 'divider'); $router->write('/calculator', 'calculator', 'index', 'POST');
$router->write('/calculator/generate/laser', 'calculator', 'laser'); $router->write('/calculator/generate/buyer', 'calculator', 'buyer', 'POST');
$router->write('/calculator/generate/complexity', 'calculator', 'complexity', 'POST');
$router->write('/calculator/generate/menu', 'calculator', 'menu', 'POST');
$router->write('/calculator/generate/result', 'calculator', 'result', 'POST');
$router->write('/calculator/generate/divider', 'calculator', 'divider', 'POST');
$router->write('/calculator/generate/laser', 'calculator', 'laser', 'POST');
$router->write('/calculator/calculate', 'calculator', 'calculate', 'POST');
$router->write('/settings', 'settings', 'index', 'GET');
$router->write('/settings/write', 'settings', 'write', 'POST');
$router->write('/settings/read', 'settings', 'read', 'POST');
// Инициализация ядра // Инициализация ядра
$core = new Core(namespace: __NAMESPACE__, router: $router); $core = new Core(namespace: __NAMESPACE__, router: $router);

View File

@ -0,0 +1,37 @@
'use strict';
function remember_switch(target) {
if (target.classList.contains('fa-unlock')) {
// Найден "открытый замок"
// Перезапись на "закрытый замок"
target.classList.remove('fa-unlock');
target.classList.add('fa-lock');
// Изменение отправляемого значения
document.querySelector('input[name=' + target.getAttribute('for') + ']').checked = true;
} else {
// Не найден "открытый замок", подразумевается, что найден "закрытый замок"
// Перезапись на "открытый замок"
target.classList.remove('fa-lock');
target.classList.add('fa-unlock');
// Изменение отправляемого значения
document.querySelector('input[name=' + target.getAttribute('for') + ']').checked = false;
}
}
function authentication(form) {
// Инициализация адреса отправки формы
form.action = '/account/authentication';
return true;
}
function registration(form) {
// Инициализация адреса отправки формы
form.action = '/account/registration';
return true;
}

View File

@ -3,32 +3,132 @@
let calculator = { let calculator = {
index: document.getElementById("calculator"), index: document.getElementById("calculator"),
calculators: [], calculators: [],
account: [],
settings: { settings: {
defaults: { defaults: {
buyer: 'individual', buyer: 'individual',
complexity: 'hard', complexity: 'medium',
} }
}, },
init() { init() {
// Инициализация калькулятора // Инициализация калькулятора
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ИЗБАВИТЬСЯ ОТ ТАЙМАУТОВ // !!!!!!!!!!!!!!!!! РАЗОБРАТЬСЯ С ПОРЯДКОМ ВЫПОЛНЕНИЯ
this.generate.buyer(this.settings.defaults.buyer); this.generate.buyer(this.settings.defaults.buyer)
setTimeout(() => { .then(this.generate.complexity(this.settings.defaults.complexity)
this.generate.complexity(this.settings.defaults.complexity); .then(this.generate.menu()
setTimeout(() => { .then(this.authenticate(cookie.read('id'))
this.generate.menu(); .then(success => {
setTimeout(() => { // Запись данных аккаунта
// this.calculate(); this.account = success;
this.generate.result();
}, 100); if (this.account !== undefined && typeof this.account === 'object' && this.account.permissions !== undefined) {
}, 100); // Найден аккаунт
}, 100);
if (this.account.permissions.calculate == 1) {
// Разрешено использовать калькулятор
this.generate.result();
}
}
}
)
)
)
);
console.log('[КАЛЬКУЛЯТОР] Инициализирован'); console.log('[КАЛЬКУЛЯТОР] Инициализирован');
}, },
calculate(repeated = false) { authenticate(id) {
// Запрос и генерация HTML с данными о типе покупателя (юр. лицо и физ. лицо)'
return fetch('/account/data?id=' + id, {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" }
}).then((response) => {
if (response.status === 200) {
return response.json().then(
success => {
console.log('[КАЛЬКУЛЯТОР] Загружены данные пользователя: ' + id);
return success;
},
error => {
console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить данные пользователя: ' + id);
});
}
});
},
calculate() {
// Запрос и генерация HTML с данными о рассчете со всех калькуляторов
// Инициализация буферов вывода
let expenses, income, profit;
// Инициализация буфера запроса
let query = {};
for (const number in this.calculators) {
// Перебор калькуляторов
// Инициализация буфера запроса для нового калькулятора
query[number] = {};
// Инициализация типа калькулятора
query[number]['type'] = this.calculators[number].getAttribute('data-calculator');
for (const buyer of this.index.querySelectorAll('input[name="buyer"]')) {
// Перебор полей с параметрами типа заказчика
if (buyer.checked) {
// Найдено выбранное поле
// Запись в буфер запроса
query[number]['buyer'] = buyer.value;
}
}
for (const complexity of this.index.querySelectorAll('input[name="complexity"]')) {
// Перебор полей с параметрами сложности
if (complexity.checked) {
// Найдено выбранное поле
// Запись в буфер запроса
query[number]['complexity'] = complexity.value;
}
}
for (const field of this.calculators[number].querySelectorAll('[data-calculator-parameter]')) {
// Перебор полей с параметрами
// Запись в буфер запроса
query[number][field.getAttribute('data-calculator-parameter')] = field.value ?? field.options[field.selectedIndex].text;
}
// Сортировка
query[number] = this.sort[query[number]['type']](query[number]);
}
fetch('/calculator/calculate', {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(query)
}).then((response) => {
if (response.status === 200) {
return response.json().then(
success => {
console.log('[КАЛЬКУЛЯТОР] Сгенерирован результат');
return success;
},
error => {
console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось сгенерировать результат');
});
}
});
let result = document.getElementById("result"); let result = document.getElementById("result");
if (result === null) { if (result === null) {
@ -36,121 +136,118 @@ let calculator = {
// Инициализия // Инициализия
this.generate.result(); this.generate.result();
if (repeated === false) {
// Это первое выполнение функции в потенциальном цикле
// Повтор операции
this.calculate(true);
}
} }
}, },
generate: { generate: {
buyer(value = 'individual') { buyer(value = 'individual') {
// Запрос и генерация HTML с данными о типе покупателя (юр. лицо и физ. лицо) // Запрос и генерация HTML с данными о типе покупателя (юр. лицо и физ. лицо)
const request = new XMLHttpRequest(); return fetch('/calculator/generate/buyer?value=' + value, {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" }
}).then((response) => {
if (response.status === 200) {
response.text().then(
success => {
calculator.index.insertAdjacentHTML('beforeend', success);
request.open('GET', '/calculator/generate/buyer?value=' + value); console.log('[КАЛЬКУЛЯТОР] Загружен элемент с кнопками выбора типа покупателя');
},
request.setRequestHeader('Content-Type', 'application/x-www-form-url'); error => {
console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить элемент с кнопками выбора типа покупателя');
request.addEventListener("readystatechange", () => { });
if (request.readyState === 4 && request.status === 200) {
calculator.index.insertAdjacentHTML('beforeend', request.responseText);
console.log('[КАЛЬКУЛЯТОР] Загружен элемент с кнопками выбора типа покупателя');
} }
}); });
request.send();
}, },
complexity(value = 'medium') { complexity(value = 'medium') {
// Запрос и генерация HTML с данными о сложности работы // Запрос и генерация HTML с данными о сложности работы
const request = new XMLHttpRequest(); return fetch('/calculator/generate/complexity?value=' + value, {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" }
}).then((response) => {
if (response.status === 200) {
response.text().then(
success => {
calculator.index.insertAdjacentHTML('beforeend', success);
request.open('GET', '/calculator/generate/complexity?value=' + value); console.log('[КАЛЬКУЛЯТОР] Загружен элемент с кнопками выбора сложности');
},
request.setRequestHeader('Content-Type', 'application/x-www-form-url'); error => {
console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить элемент с кнопками выбора сложности');
request.addEventListener("readystatechange", () => { });
if (request.readyState === 4 && request.status === 200) {
calculator.index.insertAdjacentHTML('beforeend', request.responseText);
console.log('[КАЛЬКУЛЯТОР] Загружен элемент с кнопками выбора сложности');
} }
}); });
request.send();
}, },
menu() { menu() {
// Запрос и генерация HTML с кнопками добавления калькулятора // Запрос и генерация HTML с кнопками добавления калькулятора
const request = new XMLHttpRequest(); return fetch('/calculator/generate/menu', {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" }
}).then((response) => {
if (response.status === 200) {
response.text().then(
success => {
calculator.index.insertAdjacentHTML('beforeend', success);
request.open('GET', '/calculator/generate/menu'); console.log('[КАЛЬКУЛЯТОР] Загружен элемент с кнопками добавления калькулятора');
},
request.setRequestHeader('Content-Type', 'application/x-www-form-url'); error => {
console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить элемент с кнопками добавления калькулятора');
request.addEventListener("readystatechange", () => { });
if (request.readyState === 4 && request.status === 200) {
calculator.index.insertAdjacentHTML('beforeend', request.responseText);
console.log('[КАЛЬКУЛЯТОР] Загружен элемент с кнопками добавления калькулятора');
} }
}); });
request.send();
}, },
result() { result() {
// Запрос и генерация HTML с данными о результате калькуляции // Запрос и генерация HTML с данными о результате калькуляции
const request = new XMLHttpRequest(); return fetch('/calculator/generate/result', {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" }
}).then((response) => {
if (response.status === 200) {
response.text().then(
success => {
calculator.index.insertAdjacentHTML('beforeend', success);
request.open('GET', '/calculator/generate/result'); console.log('[КАЛЬКУЛЯТОР] Загружен элемент с данными о результате калькуляции');
},
request.setRequestHeader('Content-Type', 'application/x-www-form-url'); error => {
console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить элемент с данными о результате калькуляции');
request.addEventListener("readystatechange", () => { });
if (request.readyState === 4 && request.status === 200) {
calculator.index.insertAdjacentHTML('beforeend', request.responseText);
console.log('[КАЛЬКУЛЯТОР] Загружен элемент с данными о результате калькуляции');
} }
}); });
request.send();
}, },
divider(element, position) { divider(element, position) {
// Запрос и генерация HTML с данными о результате калькуляции // Запрос и генерация HTML с данными о результате калькуляции
const request = new XMLHttpRequest(); return fetch('/calculator/generate/divider', {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" }
}).then((response) => {
if (response.status === 200) {
response.text().then(
success => {
if (element === undefined || position === undefined) {
// Не задан элемент и позиция добавляемого разделителя
request.open('GET', '/calculator/generate/divider'); // Запись разделителя в конце калькулятора
calculator.index.insertAdjacentHTML('beforeend', success);
} else {
// Задан элемент и позиция добавляемого разделителя
request.setRequestHeader('Content-Type', 'application/x-www-form-url'); // Запись разделителя по заданным параметрам
element.insertAdjacentHTML(position, success);
}
request.addEventListener("readystatechange", () => { console.log('[КАЛЬКУЛЯТОР] Загружен разделитель');
if (request.readyState === 4 && request.status === 200) { },
error => {
if (element === undefined || position === undefined) { console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить разделитель');
// Не задан элемент и позиция добавляемого разделителя });
// Запись разделителя в конце калькулятора
calculator.index.insertAdjacentHTML('beforeend', request.responseText);
} else {
// Задан элемент и позиция добавляемого разделителя
// Запись разделителя по заданным параметрам
element.insertAdjacentHTML(position, request.responseText);
}
console.log('[КАЛЬКУЛЯТОР] Загружен разделитель');
} }
}); });
request.send();
}, },
calculators: { calculators: {
laser() { laser() {
@ -158,100 +255,133 @@ let calculator = {
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ИЗБАВИТЬСЯ ОТ ТАЙМАУТОВ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ИЗБАВИТЬСЯ ОТ ТАЙМАУТОВ
const request = new XMLHttpRequest(); return fetch('/calculator/generate/laser', {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" }
}).then((response) => {
if (response.status === 200) {
response.text().then(
success => {
// Поиск последнего калькулятора
let last = calculator.calculators[calculator.calculators.length - 1];
request.open('GET', '/calculator/generate/laser'); if (last !== undefined && last !== null) {
// Найден калькулятор
request.setRequestHeader('Content-Type', 'application/x-www-form-url');
request.addEventListener("readystatechange", () => {
if (request.readyState === 4 && request.status === 200) {
// Поиск последнего калькулятора
let last = calculator.calculators[calculator.calculators.length - 1];
if (last !== undefined && last !== null) {
// Найден калькулятор
// Запись калькулятора после последнего калькулятора
last.insertAdjacentHTML('afterend', request.responseText);
setTimeout(() => {
// Инициализация разделителя перед меню
calculator.generate.divider(last, 'afterend');
}, 100);
} else {
// Не найден калькулятор
// Поиск меню
let menu = document.getElementById("menu");
if (menu !== null) {
// Найдено меню
// Инициализация разделителя перед меню
calculator.generate.divider(menu, 'beforebegin');
setTimeout(() => {
// Запись калькулятора перед меню
menu.insertAdjacentHTML('beforebegin', request.responseText);
}, 100);
} else {
// Не найдено меню
// Поиск результатов калькуляции
let result = document.getElementById("result");
if (result !== null) {
// Найден элемент с результатами калькуляции
// Инициализация разделителя перед меню // Инициализация разделителя перед меню
calculator.generate.divider(result, 'beforebegin'); calculator.generate.divider(last, 'afterend').then(
divider => {
// Запись калькулятора после последнего калькулятора
last.insertAdjacentHTML('afterend', success);
setTimeout(() => { // Поиск калькуляторов
// Запись калькулятора перед элементом с результатами калькуляции let calculators = calculator.index.querySelectorAll('section[data-calculator]');
result.insertAdjacentHTML('beforebegin', request.responseText);
}, 100); // Запись в реестр последнего калькулятора (подразумевается, что он новый и только что был записан)
calculator.calculators.push(calculators[calculators.length - 1]);
// Запись в журнал
console.log('[КАЛЬКУЛЯТОР] Инициализирован калькулятор лазерной резки');
}
);
} else { } else {
// Не найден элемент с результатами калькуляции // Не найден калькулятор
// Инициализация разделителя перед меню // Поиск меню
calculator.generate.divider(); let menu = document.getElementById("menu");
setTimeout(() => { if (menu !== null) {
// Запись калькулятора в конце калькулятора // Найдено меню
calculator.index.insertAdjacentHTML('beforeend', request.responseText);
}, 100); // Инициализация разделителя перед меню
calculator.generate.divider(menu, 'beforebegin').then(
divider => {
// Запись калькулятора перед меню
menu.insertAdjacentHTML('beforebegin', success);
// Поиск калькуляторов
let calculators = calculator.index.querySelectorAll('section[data-calculator]');
// Запись в реестр последнего калькулятора (подразумевается, что он новый и только что был записан)
calculator.calculators.push(calculators[calculators.length - 1]);
// Запись в журнал
console.log('[КАЛЬКУЛЯТОР] Инициализирован калькулятор лазерной резки');
}
);
} else {
// Не найдено меню
// Поиск результатов калькуляции
let result = document.getElementById("result");
if (result !== null) {
// Найден элемент с результатами калькуляции
// Инициализация разделителя перед меню
calculator.generate.divider(result, 'beforebegin').then(
divider => {
// Запись калькулятора перед элементом с результатами калькуляции
result.insertAdjacentHTML('beforebegin', success);
// Поиск калькуляторов
let calculators = calculator.index.querySelectorAll('section[data-calculator]');
// Запись в реестр последнего калькулятора (подразумевается, что он новый и только что был записан)
calculator.calculators.push(calculators[calculators.length - 1]);
// Запись в журнал
console.log('[КАЛЬКУЛЯТОР] Инициализирован калькулятор лазерной резки');
}
);
} else {
// Не найден элемент с результатами калькуляции
// Инициализация разделителя перед меню
calculator.generate.divider().then(
divider => {
// Запись калькулятора в конце калькулятора
calculator.index.insertAdjacentHTML('beforeend', success);
// Поиск калькуляторов
let calculators = calculator.index.querySelectorAll('section[data-calculator]');
// Запись в реестр последнего калькулятора (подразумевается, что он новый и только что был записан)
calculator.calculators.push(calculators[calculators.length - 1]);
// Запись в журнал
console.log('[КАЛЬКУЛЯТОР] Инициализирован калькулятор лазерной резки');
}
);
}
}
} }
} },
} error => {
// Запись в журнал
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ИЗБАВИТЬСЯ ОТ ТАЙМАУТОВ console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось инициализировать калькулятор лазерной резки');
});
setTimeout(() => {
// Поиск только что созданного калькулятора
let laser = document.getElementById('laser');
if (laser !== null) {
// Найден только что инициализированный (подразумевается) калькулятор лазерной резки
// Реинициализация идентификатора
laser.id = 'laser_' + calculator.calculators.length;
// Запись калькулятора в реестр
calculator.calculators.push(laser);
console.log('[КАЛЬКУЛЯТОР] Загружен калькулятор лазерной резки');
} else {
// Не найден только что инициализированный (подразумевается) калькулятор лазерной резки
console.log('[КАЛЬКУЛЯТОР] Не удалось инициализировать калькулятор лазерной резки');
}
}, 100);
} }
}); });
request.send();
} }
} }
},
sort: {
laser(parameters) {
// Сортировка параметров для отправки на сервер (динамически вызывается функция-обработчик)
return {
type: 'laser',
buyer: parameters['buyer'] ?? null,
complexity: parameters['complexity'] ?? null,
width: parameters['width'] ?? null,
height: parameters['width'] ?? null,
length: parameters['length'] ?? null,
amount: parameters['amount'] ?? null,
metal: parameters['metal'] ?? null,
holes: parameters['holes'] ?? null,
diameter: parameters['diameter'] ?? null,
};
}
} }
}; };

View File

@ -0,0 +1,55 @@
'use strict';
let cookie = {
read(name) {
// Поиск по регулярному выражению
let matches = document.cookie.match(new RegExp(
"(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)"
));
return matches ? decodeURIComponent(matches[1]) : undefined;
},
write(name, value, options = {}) {
// Инициализация параметров
options = {
path: '/',
...options
};
if (options.expires instanceof Date) {
// Передана инстанция Date
// Запись параметра истечения срока
options.expires = options.expires.toUTCString();
}
// Инициализация cookie
let updatedCookie = encodeURIComponent(name) + "=" + encodeURIComponent(value);
for (let optionKey in options) {
// Перебор параметров
// Запись в cookie названия параметра
updatedCookie += "; " + optionKey;
// Инициализация значения параметра
let optionValue = options[optionKey];
if (optionValue !== true) {
// Найдено значение параметра
// Запись в cookie значения параметра
updatedCookie += "=" + optionValue;
}
}
// Конкатенация нового cookie с остальными
document.cookie = updatedCookie;
},
delete(name) {
// Удаление
setCookie(name, "", {
'max-age': -1
})
}
};

View File

@ -1,26 +1,39 @@
<link href="/css/auth.css" rel="stylesheet"> <link href="/css/auth.css" rel="stylesheet">
<section id="authentication"> <section id="authentication">
{% if account is not empty %}
<h3>Аккаунт</h3>
<div>
<p><b>Почта:</b> <span>{{ account.email }}</span></p>
<a class="exit" type="button" href='/account/deauthentication'>Выход</a>
</div>
{% else %}
<h3>Аутентификация</h3> <h3>Аутентификация</h3>
<form method="post" accept-charset="UTF-8"> <form method="POST" accept-charset="UTF-8">
<input type="hidden" name="action" value="users/login"> <input type="text" name="email" placeholder="Почта">
<input type="password" name="password" placeholder="Пароль">
<input id="loginName" type="text" name="loginName" placeholder="Почта"
value="{{ craft.app.user.rememberedUsername }}">
<input id="password" type="password" name="password" placeholder="Пароль">
<div class="submit"> <div class="submit">
<label class="button unselectable" for="rememberMe"> <label class="button unselectable fas fa-unlock" for="remember"
<i class="fas fa-unlock"></i> onclick="return remember_switch(this);"></label>
</label> <input type="checkbox" name="remember" value="1">
<input type="checkbox" name="rememberMe" value="1"> <input type="submit" value="Войти" onclick="return authentication(this.parentElement.parentElement);">
<input type="submit" value="Войти">
</div> </div>
<input type="submit" class="registration" value="Зарегистрироваться"
onclick="return registration(this.parentElement);">
{% if errorMessage is defined %} {% if errors is not empty %}
<p>{{ errorMessage }}</p> {% if errors.account is not empty %}
<ul class="errors">
{% for error in errors.account %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{% endif %} {% endif %}
</form> </form>
{% endif %}
</section> </section>
<script type="text/javascript" src="/js/auth.js" defer></script>

View File

@ -5,6 +5,7 @@
<div class="divider"></div> <div class="divider"></div>
</section> </section>
<script type="text/javascript" src="/js/cookie.js" defer></script>
<script type="text/javascript" src="/js/calculator.js" defer></script> <script type="text/javascript" src="/js/calculator.js" defer></script>
<script> <script>
if (document.readyState === "complete") { if (document.readyState === "complete") {

View File

@ -1,4 +1,4 @@
<section id="buyer"> <section id="buyer" class="unselectable">
<input id="individual" type="radio" name="buyer" value="individual" {% if buyer is same as('individual') %}checked{% endif %}> <input id="individual" type="radio" name="buyer" value="individual" {% if buyer is same as('individual') %}checked{% endif %}>
<label type="button" for="individual">Физическое лицо</label> <label type="button" for="individual">Физическое лицо</label>
<input id="entity" type="radio" name="buyer" value="entity" {% if buyer is same as('entity') %}checked{% endif %}> <input id="entity" type="radio" name="buyer" value="entity" {% if buyer is same as('entity') %}checked{% endif %}>

View File

@ -1,9 +1,9 @@
<h3>Лазерная резка</h3> <h3>Лазерная резка</h3>
<section id="laser"> <section class="calculator" data-calculator="laser">
<div> <div>
<label>Металл</label> <label>Металл</label>
<div> <div>
<select name="metal"> <select name="metal" data-calculator-parameter="metal">
<option value="steel">Сталь</option> <option value="steel">Сталь</option>
<option value="stainless_steel" selected>Нержавеющая сталь</option> <option value="stainless_steel" selected>Нержавеющая сталь</option>
<option value="brass">Латунь</option> <option value="brass">Латунь</option>
@ -15,30 +15,30 @@
<div> <div>
<label>Размер</label> <label>Размер</label>
<div> <div>
<input type="text" class="measured" title="Длина" value="100"> <input data-calculator-parameter="width" type="text" class="measured" title="Длина" value="100">
<span class="unit unselectable">мм</span> <span class="unit unselectable">мм</span>
<small>x</small> <small>x</small>
<input type="text" class="measured" title="Ширина" value="100"> <input data-calculator-parameter="height" type="text" class="measured" title="Ширина" value="100">
<span class="unit unselectable">мм</span> <span class="unit unselectable">мм</span>
<small>x</small> <small>x</small>
<input type="text" class="measured" title="Толщина" value="1"> <input data-calculator-parameter="length" type="text" class="measured" title="Толщина" value="1">
<span class="unit unselectable">мм</span> <span class="unit unselectable">мм</span>
</div> </div>
</div> </div>
<div> <div>
<label>Отверстия</label> <label>Отверстия</label>
<div> <div>
<input type="text" class="measured" title="Количество" value="1"> <input data-calculator-parameter="holes" type="text" class="measured" title="Количество" value="1">
<span class="unit unselectable">шт</span> <span class="unit unselectable">шт</span>
<small>x</small> <small>x</small>
<input type="text" class="measured" title="Диаметр" value="1"> <input data-calculator-parameter="diameter" type="text" class="measured" title="Диаметр" value="1">
<span class="unit unselectable">мм</span> <span class="unit unselectable">мм</span>
</div> </div>
</div> </div>
<div> <div>
<label>Количество</label> <label>Количество</label>
<div> <div>
<input type="text" class="measured" title="Количество" value="1"> <input data-calculator-parameter="amount" type="text" class="measured" title="Количество" value="1">
<span class="unit unselectable">шт</span> <span class="unit unselectable">шт</span>
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
<section id="menu"> <section id="menu" class="unselectable">
<a type="button" onclick="calculator.generate.calculators.laser(); return false;"> <a type="button" onclick="calculator.generate.calculators.laser(); return false;">
<img src="/img/laser.png" title="Добавить лазерную резку"> <img src="/img/laser.png" title="Добавить лазерную резку">
Лазерная резка Лазерная резка

View File

@ -1,4 +1,4 @@
<section id="complexity"> <section id="complexity" class="unselectable">
<input id="easy" type="radio" name="complexity" value="easy" {% if complexity is same as('easy') %}checked{% endif %}> <input id="easy" type="radio" name="complexity" value="easy" {% if complexity is same as('easy') %}checked{% endif %}>
<label type="button" for="easy"> <label type="button" for="easy">
<img src="/img/easy.png" title="Лёгкий уровень сложности"> <img src="/img/easy.png" title="Лёгкий уровень сложности">

View File

@ -1 +1 @@
<div class="divider"></div> <div class="divider unselectable"></div>

View File

@ -1,7 +1,10 @@
<section id="result"> <section id="result">
<nav class="unselectable">
<a id="calculate" type="button" onclick="return calculator.calculate();">Рассчитать</a>
</nav>
<div> <div>
<p>Расходы: <span id="expenses">0</span><span>рублей</span></p> <p class="unselectable">Расходы: <span id="expenses">0</span><span>рублей</span></p>
<p>Доход: <span id="income">0</span><span>рублей</span></p> <p class="unselectable">Доход: <span id="income">0</span><span>рублей</span></p>
<p>Прибыль: <span id="profit">0</span><span>рублей</span></p> <p class="unselectable">Прибыль: <span id="profit">0</span><span>рублей</span></p>
</div> </div>
</section> </section>

View File

@ -5,5 +5,10 @@
</a> </a>
<a class="link" href="/contacts" title="Связь с администрацией">Контакты</a> <a class="link" href="/contacts" title="Связь с администрацией">Контакты</a>
<a class="link" href="/journal" title="Записи рассчётов">Журнал</a> <a class="link" href="/journal" title="Записи рассчётов">Журнал</a>
{% if account is not empty %}
{% if account.permissions.settings is defined and account.permissions.settings == 1 %}
<a class="link" href="/settings" title="Настройки калькуляторов">Настройки</a>
{% endif %}
{% endif %}
</nav> </nav>
</header> </header>