Что, если мы создадим такой интерфейс в 1С, чтобы он был удобнее, чем в Excel? Да не просто удобнее — а чтобы сотрудники сказали: «Ого, это же как игра!» С вами снова Ведущий специалист модуля разработки 1С Михеев Антон. Давайте вместе сделаем эту игру идею реальностью
Представьте, что Excel — это склад, вид сверху. Синие ячейки — стеллажи, в них лежат товары. Да, на множестве складов топология нарисована именно в Excel. Сотрудникам так понятнее и удобнее: закрашивать ячейки в таблице куда проще, чем разбираться, как заполнять справочники с кучей непонятных цифр в 1С.

Задачка: на стеллаже три полки. На первой — конфеты, на второй — печеньки, на третьей — сиропы. Как пользователю понять, что нужно взять печеньку со второй полки?
Ответ: рисовать всю эту красоту в изометрии! Изометрия — это старая добрая технология, которая:
· не нагружает процессор (в отличие от 3D, где ваш ПК может начать мечтать о пенсии);
· позволяет рассмотреть предмет под углом в 30∘;
даёт возможность переключать углы обзора (ну, или хотя бы сделать четыре фиксированных вида — этого хватит).

Как это сделать в 1С?
Здесь нам поможет встроенный редактор HTML внутри 1С. Если вы не знали, то 1С использует платформу WebKit, позаимствованную у айфонов (лучшие мировые практики, да).
Шаг 1. Создаём внешнюю обработку и добавляем в неё ПолеHTML документа.

Шаг 2. Создаём три макета: HTML, JS и CSS.

· Макет HTML — это каркас страницы. Вставляем код:
html
<!DOCTYPE html>
<HTML>
<HEAD>@CSS</HEAD>
<BODY>
<h1>Изометрическая сетка 10 × 10 с перетаскиваемыми объектами</h1>
<div id='grid-container'></div>
@JS
</BODY>
</HTML>
· Макет JS — это логика. Здесь мы:
o создаём сетку из ячеек;
o расставляем объекты (конфеты, печеньки, сиропы);
o добавляем возможность перетаскивать их мышкой (drag & drop);
o подсвечиваем ячейки при наведении;
o проверяем, занята ли ячейка, прежде чем положить туда новый объект.

-
Добавим кода в макет:
window.onload = function() { -
const gridContainer = document.getElementById(‘grid-container’);
-
const occupiedCells = new Set(); // Хранит занятые ячейки
-
-
-
// Массив контейнеров с параметрами
-
const containers = [@Containers];
-
-
// Создаём ячейки сетки
-
for (let i = 0; i < 10; i++) {
-
for (let j = 0; j < 10; j++) {
-
const cell = document.createElement(‘div’);
-
cell.className = ‘cell’;
-
cell.style.left =
${j * 90}px; -
cell.style.top =
${i * 50}px; -
cell.dataset.row = i;
-
cell.dataset.col = j;
-
// Добавляем параметры x и y в качестве атрибутов ячейки
-
cell.setAttribute(‘data-x’, i);
-
cell.setAttribute(‘data-y’, j);
-
gridContainer.appendChild(cell);
-
}
-
}
-
-
// Расставляем объекты из массива контейнеров
-
containers.forEach(container => {
-
const item = document.createElement(‘div’);
-
item.className = ‘draggable-item’;
-
item.textContent = container.text;
-
item.draggable = true;
-
item.style.backgroundColor = container.color;
-
item.style.zIndex = parseInt(container.x / container.y * 100,10);
-
-
const Top = document.createElement(‘div’);
-
Top.className = ‘top’;
-
Top.textContent = container.text;
-
Top.style.backgroundColor = container.color;
-
-
const Front = document.createElement(‘div’);
-
Front.className = ‘front’;
-
Front.style.backgroundColor = container.color;
-
-
const Right = document.createElement(‘div’);
-
Right.className = ‘right’;
-
Right.textContent = container.text;
-
Right.style.backgroundColor = container.color;
-
-
item.appendChild(Top);
-
item.appendChild(Front);
-
item.appendChild(Right);
-
-
// Добавляем все параметры в качестве атрибутов draggable-item
-
item.setAttribute(‘data-color’, container.color);
-
item.setAttribute(‘data-text’, container.text);
-
item.setAttribute(‘data-x’, container.x);
-
item.setAttribute(‘data-y’, container.y);
-
item.setAttribute(‘data-z’, container.z);
-
-
// Помещаем объект в ячейку согласно координатам x и y
-
const targetCell = document.querySelector(
.cell[data-row="${container.x}"][data-col="${container.y}"]); -
if (targetCell) {
-
targetCell.appendChild(item);
-
// Помечаем ячейку как занятую
-
occupiedCells.add(
${container.x},${container.y}); -
}
-
});
-
-
let draggedItem = null;
-
let originalCell = null;
-
-
// Обработчик начала перетаскивания
-
function handleDragStart(e) {
-
draggedItem = this;
-
originalCell = this.parentElement;
-
e.dataTransfer.setData(‘text/plain’, null);
-
-
// Невидимость Ghost при перетаскивании
-
var img = document.createElement(«img»);
-
img.src = «»;
-
e.dataTransfer.setDragImage(img, 0, 0);
-
}
-
-
// Обработчик окончания перетаскивания
-
document.addEventListener(‘dragend’, () => {
-
if (draggedItem) {
-
draggedItem = null;
-
originalCell = null;
-
}
-
});
-
-
// Обработчики для всех ячеек сетки
-
document.querySelectorAll(‘.cell’).forEach(cell => {
-
// Обработчик наведения — выделение фона и границы
-
cell.addEventListener(‘mouseenter’, () => {
-
cell.style.backgroundColor = ‘rgba(135, 206, 235, 0.3)’; // Полупрозрачный голубой
-
cell.style.borderColor = ‘blue’;
-
cell.style.zIndex = ’10’; // Выводим поверх других элементов
-
cell.style.boxShadow = ‘0 0 8px rgba(0, 123, 255, 0.5)’; // Дополнительная подсветка
-
});
-
-
cell.addEventListener(‘mouseleave’, () => {
-
// Восстанавливаем исходный вид
-
cell.style.backgroundColor = »;
-
cell.style.borderColor = ‘#999’;
-
cell.style.zIndex = »;
-
cell.style.boxShadow = »;
-
});
-
-
cell.addEventListener(‘dragover’, (e) => e.preventDefault());
-
-
cell.addEventListener(‘drop’, (e) => {
-
e.preventDefault();
-
if (!draggedItem) return;
-
-
const row = cell.dataset.row;
-
const col = cell.dataset.col;
-
const cellKey =
${row},${col}; -
-
// Проверяем, занята ли ячейка
-
if (occupiedCells.has(cellKey)) {
-
alert(‘Ячейка уже занята! Выберите другую.’);
-
return;
-
}
-
-
// Освобождаем предыдущую ячейку
-
if (originalCell) {
-
const origRow = originalCell.dataset.row;
-
const origCol = originalCell.dataset.col;
-
occupiedCells.delete(
${origRow},${origCol}); -
}
-
-
// Обновляем атрибуты draggable-item с новыми координатами
-
draggedItem.setAttribute(‘data-x’, row);
-
draggedItem.setAttribute(‘data-y’, col);
-
-
// Помещаем объект в новую ячейку — он автоматически занимает всю площадь ячейки
-
cell.appendChild(draggedItem);
-
occupiedCells.add(cellKey);
-
});
-
});
-
-
// Инициализируем обработчики перетаскивания для всех объектов на поле
-
document.querySelectorAll(‘.draggable-item’).forEach(item => {
-
item.addEventListener(‘dragstart’, handleDragStart);
-
});
-
-
document.querySelectorAll(‘.draggable-item’).forEach(item => {
-
item.addEventListener(‘mouseenter’, () => {
-
document.querySelectorAll(‘.draggable-item’).forEach(otherItem => {
-
if (otherItem !== item) {
-
otherItem.classList.add(‘dimmed’);
-
}
-
});
-
});
-
-
item.addEventListener(‘mouseleave’, () => {
-
document.querySelectorAll(‘.draggable-item’).forEach(otherItem => {
-
otherItem.classList.remove(‘dimmed’);
-
});
-
});
-
});
-
-
-
// Запрещаем контекстное меню по правой кнопке мыши
-
document.addEventListener(‘contextmenu’, e => e.preventDefault());
-
}
-
·
Макет CSS — это стиль. Здесь мы задаём цвета, размеры, тени, углы поворота и прочие красоты. Изометрия достигается через transform: rotate(-30deg) skewX(30deg) — магия, а не код!
Код на сервере: магия данных
В обработчике ПриСозданииНаСервере пишем код, который:
1. Берёт макеты HTML, JS и CSS.
2. Подставляет CSS и JS в HTML (через @CSS и @JS).
3. Делает запрос к регистру накопления, чтобы получить координаты ячеек и информацию о контейнерах.
4. Формирует массив контейнеров с параметрами (цвет, текст, координаты).
5. Подставляет этот массив в HTML (вместо @Containers).
Добавим код ПриСозданииНаСервере:
bsl
ОбъектВнешнейОбработки = РеквизитФормыВЗначение("Объект");
HTML = ОбъектВнешнейОбработки.ПолучитьМакет("МакетHTML").ПолучитьТекст();
JS = "<script>" + ОбъектВнешнейОбработки.ПолучитьМакет("МакетJS").ПолучитьТекст() + "</script>";
CSS = "<style>" + ОбъектВнешнейОбработки.ПолучитьМакет("МакетCSS").ПолучитьТекст() + "</style>";
HTML = СтрЗаменить(HTML,"@CSS",CSS);
HTML = СтрЗаменить(HTML,"@JS",JS);
//Тут пишем запрос который получит Координаты ячейки и Контейнер в ней
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| укЗаполненностьЯчеекОстатки.Ячейка.Ряд КАК X,
| укЗаполненностьЯчеекОстатки.Ячейка.Ярус КАК Z,
| укЗаполненностьЯчеекОстатки.Ячейка.Позиция КАК Y
|ИЗ
| РегистрНакопления.укЗаполненностьЯчеек.Остатки КАК укЗаполненностьЯчеекОстатки
|ГДЕ
| укЗаполненностьЯчеекОстатки.Ячейка.Ряд < 10
| И укЗаполненностьЯчеекОстатки.Ячейка.Ярус = 1
| И укЗаполненностьЯчеекОстатки.Ячейка.Позиция < 10
|
|УПОРЯДОЧИТЬ ПО
| X Возр,
| Y ВОЗР";
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Результат = "";
Пока Выборка.Следующий() Цикл
ТекСтрока = СтрШаблон("{ color: '#00008b', text: '%1', x: %2, y: %3, z: %4 },", "К-"+ Выборка.X + "-" + Выборка.Y , Выборка.X, Выборка.Y, Выборка.Z);
Результат = Результат + ТекСтрока;
КонецЦикла;
Результат = Лев(Результат, СтрДлина(Результат) - 1);
HTML = СтрЗаменить(HTML,"@Containers", Результат);
«Запрос в 1С — это как заклинание: если произнести правильно, получишь желаемое. Если нет — получишь ошибку и желание всё бросить».
Что в итоге?
На выходе получается склад с изометрией! Ого, скажете вы, и будете правы. Это реально работает внутри 1С и не тормозит (в отличие от 3D, которое может заставить ваш компьютер задуматься о смысле жизни).
Что можно добавить:
· Подсветку ячеек: красным — «Возьми отсюда», зелёным — «Поставь сюда».
· Динамическую подгрузку задач на перемещение.
· Отображение остатков прямо на контейнерах.
· Уведомления: «Внимание! На полке с печеньками осталось 2 штуки!».
Получается уже не просто 1С, а почти игра!

Почему не 3D?
Вы спросите: «А почему не использовать 3D?» Ответ прост:
· 1С не сможет быстро выполнять качественный рендеринг моделей — всё будет тормозить.
· Оптимизация 3D‑движка в 1С не даёт качественной детализации.
· Возможность крутить камеру только сбивает пользователя.
«3D в 1С — это как попытка запустить Cyberpunk 2077 на калькуляторе: идея крутая, но результат печальный».
Финальный аккорд
Зафиксируем угол для изометрии и прорисуем все объекты лишь под одним углом (максимум 4 картинки на объект). И вот он — весь склад перед вами, с хорошей детализацией, без тормозов и без необходимости изучать квантовую физику.


Как вам идейка? Удобно ли будет пользоваться детализированной интерактиивной топологией склада? 😉
ссылка на оригинал статьи https://habr.com/ru/articles/1051154/