Конфигурация, артикул и стоимость
Оглавление серии «Форма» · Урок 7 из 10.
Мы умеем показывать лампу, выбирать её детали и менять отделку. Теперь оформим параметры как самостоятельную конфигурацию продукта. Добавим стандартный и широкий плафон, сформируем условный артикул и покажем учебную стоимость в обычном HTML. Ожидаемый результат — форма, изображение и резюме выражают одну комплектацию. Цена в этой лаборатории придумана для примера: оформление заказа, склад и расчёт настоящей продажи не входят в курс.
Состояние выбранной детали не является конфигурацией. Можно осматривать основание, пока у лампы остаётся зелёный плафон. Можно изменить ракурс, не меняя артикул. Разделим данные на предметные параметры и параметры представления. В предметных данных храним версию, отделку плафона, отделку основания и размер. В представлении остаются положение камеры и сообщение о выбранной детали. Это различие поможет позднее восстановить комплектацию без неожиданного сброса удобного ракурса.
Ограниченная схема вместо произвольного объекта
Модуль config.js уже содержит normalize. Теперь используем весь его контракт. Допустимы только известные варианты: три цвета плафона, две отделки основания, два размера. Версия схемы равна единице. Нормализация возвращает новый объект с этими полями. Неизвестное значение даёт null, после чего интерфейс сохраняет прежнее состояние. Такой выбор предпочтительнее случайного добавления неподдерживаемого варианта, который форма покажет, а изображение или расчёт не поймут.
Ограничения интерфейса и ограничения данных нужны одновременно. select предлагает только правильные варианты, но состояние в будущем придёт из URL или хранилища браузера. Оно может быть устаревшим или отредактированным вручную. Проверка в одном месте позволяет подключать разные источники одинаково. Не стоит считать локальные данные правильными только потому, что они находятся на компьютере пользователя: сохранение переживает смену версии приложения и может содержать значения старой схемы.
export const palette = Object.freeze({violet:'#8044ca',green:'#44b46d',sand:'#d6bd97',charcoal:'#272532',pale:'#dad8e2'});
export const defaults = Object.freeze({v:1,shade:'violet',base:'charcoal',size:'standard'});
export function normalize(value) {
if (!value || value.v !== 1 || !['violet','green','sand'].includes(value.shade)
|| !['charcoal','pale'].includes(value.base) || !['standard','wide'].includes(value.size)) return null;
return {v:1,shade:value.shade,base:value.base,size:value.size};
}
export function quote(config) {
const price = 9900 + (config.base === 'pale' ? 400 : 0) + (config.size === 'wide' ? 1200 : 0);
return {sku:`FORMA-${config.shade}-${config.base}-${config.size}`,price};
}
Функция quote не обращается к DOM, сцене или сети. Она получает нормализованную конфигурацию и возвращает артикул со стоимостью. Такая функция называется чистой в смысле отсутствия зависимых побочных действий: одинаковые входные данные дают одинаковый результат. Здесь это удобно для объяснения предметных правил. Артикул строится из имён вариантов, а цена складывается из базовой величины и двух известных надбавок. Цвет плафона пока не влияет на стоимость.
Один путь обновления
Функция update синхронизирует значения полей, применяет конфигурацию к модели и обновляет резюме. В обработчике изменения поля сначала строим кандидат, затем нормализуем его, затем присваиваем состояние и вызываем update. Порядок важен: изображение не должно получать данные, которые расчёт отвергнет. После подключения нового источника, например ссылки, он должен прийти к той же нормализованной конфигурации и вызвать тот же путь обновления.
Резюме выводится через textContent, а не собирается из произвольного HTML. Артикул и сумма являются текстовыми данными, поэтому браузеру не требуется интерпретировать их как разметку. Эта привычка полезна даже для полностью локального примера. Позднее в описание могут попасть названия из каталога или параметры URL. Вывод текста сохраняет понятную границу: значения не становятся командами оформления страницы и не получают возможности создавать неожиданные элементы.
Исходный артикул включает violet, charcoal и standard. При выборе широкого плафона последняя часть меняется на wide, а учебная сумма увеличивается на 1200. Светлое основание добавляет ещё 400. Это не универсальная схема товарных артикулов. В реальном магазине артикул обычно связан с собственным каталогом и наличием. Сейчас явная строка помогает увидеть, какие параметры относятся к товару и каким образом изменение одного поля отражается в итоговом состоянии.
Размер модели и размер варианта
Широкий плафон увеличивает масштаб его узла по горизонтальным осям в 1,25 раза. Высота сохраняется. Исходные масштабы 0,75 и 0,6 известны из авторского glTF. Поэтому apply каждый раз вычисляет новое значение от исходной величины, а не умножает уже изменённый масштаб. Если при каждом событии просто умножать текущую ширину на 1,25, повторный выбор того же варианта постепенно раздувал бы модель. Установка абсолютного значения исключает такую накопительную ошибку.
Этот пример подходит нашей простой лампе, но не всякая коммерческая модель допускает масштабирование одной детали. У реального товара могут изменяться соединения, толщина стенок и расположение креплений. Тогда вариант размера потребует отдельной геометрии или подготовленной параметрической модели. Важен контракт конфигурации: wide обозначает предметный вариант, а способ его изображения можно поменять, не заставляя остальные части приложения знать подробности построения mesh.
Стоимость не вычисляется из размеров геометрии. Размеры изображения и цена являются разными представлениями конфигурации. Если художник уточнит ширину модели, это не должно само по себе менять сумму. И наоборот, изменение коммерческого правила не обязано перестраивать геометрию. Одна конфигурация связывает эти результаты, но не делает один результат источником другого. Такая архитектура упрощает развитие каталога и помогает объяснить, где именно находится правило расчёта.
Граница учебного расчёта
В браузере можно изменить любое локальное значение, включая функцию quote. Поэтому отображаемая сумма не является доверенным основанием для списания денег. В полноценной продаже сервер должен проверить допустимость комплектации, наличие и собственные цены перед созданием заказа. Даже если браузер показывает правильный итог, между выбором и оформлением могут измениться остатки или стоимость. Эта серия пока не содержит серверного договора продажи; рядом с резюме явно написано, что заказ не реализован.
Сумма хранится целым числом рублей. Для других валют или дробных значений понадобится явная модель минимальных денежных единиц и форматирование. Использование произвольной арифметики с дробями может создать неожиданное округление. Здесь целые величины позволяют сосредоточиться на связности параметров. Если расширять пример до магазина, сначала определите валюту, правила округления, скидки и источник доверенной цены, затем меняйте формат отображения.
Наблюдение за согласованностью
Самостоятельно выберите зелёный широкий плафон и светлое основание. Ожидается артикул FORMA-green-pale-wide и учебная сумма 11500 рублей. Плафон должен стать шире, основание — светлым. После выбора другой детали через raycaster эти параметры и артикул должны сохраниться. Затем верните стандартный размер: ширина должна точно вернуться к исходным масштабам, а сумма уменьшиться на соответствующую надбавку. Пример при подготовке не запускался; это ожидаемый результат правил исходника.
Для дальнейшего развития можно добавить несовместимые сочетания. Например, широкий плафон потребует другого основания. Но тогда нормализация должна различать допустимость значений и допустимость их сочетания, а форма — объяснять причину ограничения. Нельзя молча менять выбранное основание, не сообщив пользователю. Управление преобразованиями объекта описано в Object3D. Следующий урок сохранит именно предметную конфигурацию и даст ссылку, по которой другой читатель увидит тот же набор вариантов.
Исходники этого шага
Архив серии содержит полную папку lesson-07. Возьмите именно её; переносить файлы из соседних папок не требуется. Версии и самостоятельная подготовка описаны в README. Пример, установка зависимостей и его выполнение при написании не запускались; результаты в тексте ожидаются по разбору исходников. Материал сохранён локально как Markdown-черновик.