6.2 KiB
6.2 KiB
Архитектура системы элементов
Обзор
Система была переработана для обеспечения универсальности и расширяемости. Теперь все элементы регистрируются в едином реестре, что позволяет легко добавлять новые типы элементов без изменения основного кода.
Структура
1. Реестр элементов (src/lib/element-registry.ts)
Центральный реестр, который содержит все определения элементов:
export interface ElementDefinition<Config = any, Value = any> {
type: ElementType; // Уникальный тип элемента
label: string; // Человекочитаемое имя
icon: ReactNode; // Иконка для UI
Editor: React.FC<{ config: Config; onChange(c: Config): void }>;
Preview: React.FC<{ config: Config }>;
Render: React.FC<{ config: Config; value: Value; onChange?(v: Value): void }>;
defaultConfig: Config; // Конфигурация по умолчанию
mapToCells?(config: Config): CellTarget[]; // Маппинг на ячейки Excel
}
2. Базовые элементы (src/components/BasicElements/)
Стандартные элементы формы:
TextElement.tsx- текстовое полеSelectElement.tsx- выпадающий списокNumberElement.tsx- числовое полеDateElement.tsx- поле датыTextareaElement.tsx- многострочный текстCheckboxElement.tsx- чекбоксRadioElement.tsx- радиокнопки
3. Специальные элементы (src/components/StandardsElement/)
Сложные элементы с собственной логикой:
StandardsDialog.tsx- диалог выбора эталоновStandardsEditor.tsx- редактор настроекStandardsPreview.tsx- превью элементаdefinition.tsx- определение элемента
Как добавить новый элемент
1. Создать компоненты
// MyElement/MyElementEditor.tsx
export const MyElementEditor: React.FC<{ config: MyConfig; onChange: (config: MyConfig) => void }> = ({ config, onChange }) => {
// Логика редактирования
};
// MyElement/MyElementPreview.tsx
export const MyElementPreview: React.FC<{ config: MyConfig }> = ({ config }) => {
// Логика превью
};
// MyElement/MyElementRender.tsx
export const MyElementRender: React.FC<{ config: MyConfig; value: string; onChange?: (value: string) => void }> = ({ config, value, onChange }) => {
// Логика рендеринга
};
2. Создать определение
// MyElement/definition.tsx
export const myElementDefinition: ElementDefinition<MyConfig, string> = {
type: "my_element",
label: "Мой элемент",
icon: <MyIcon className="h-4 w-4" />,
defaultConfig: {
// конфигурация по умолчанию
},
Editor: MyElementEditor,
Preview: MyElementPreview,
Render: MyElementRender,
mapToCells: (config) => {
// логика маппинга на ячейки Excel
return [];
},
};
3. Зарегистрировать в реестре
// src/lib/element-registry-init.ts
import { myElementDefinition } from "@/components/MyElement/definition";
export function initializeElementRegistry() {
// ... существующие элементы
registerElement("my_element", myElementDefinition);
}
4. Добавить тип в types/template.ts
export type ElementType =
| 'text' | 'select' | 'radio' | 'checkbox' | 'number'
| 'textarea' | 'date' | 'standards'
| 'my_element'; // <-- новый тип
Особенности элемента "Измерительные эталоны"
Конфигурация
interface StandardsConfig {
registryNumber: string; // ГРСИ
sheet: string; // Лист Excel
startCell: string; // Первая ячейка (например "A10")
maxItems: number; // Обычно 7
targetCells: CellTarget[];
}
Логика маппинга
Элемент автоматически генерирует ячейки для записи выбранных эталонов:
mapToCells: (cfg) => {
const res: CellTarget[] = [];
const parsed = parseCellAddress(cfg.startCell);
if (!parsed) return res;
const { column, row } = parsed;
// Генерируем ячейки вертикально (A1, A2, A3...)
for (let i = 0; i < cfg.maxItems; i++) {
res.push({
sheet: cfg.sheet,
cell: `${column}${row + i}`,
displayName: `Эталон ${i + 1}`,
});
}
return res;
}
Ограничения
- Максимум 7 эталонов (настраивается)
- Автоматическое заполнение только выбранных позиций
- Оставшиеся ячейки остаются пустыми
Преимущества новой архитектуры
- Расширяемость: Добавление новых элементов требует минимальных изменений
- Универсальность: Единый интерфейс для всех элементов
- Типобезопасность: TypeScript обеспечивает корректность типов
- Модульность: Каждый элемент самодостаточен
- Переиспользование: Компоненты можно использовать в разных контекстах
Миграция
Старые элементы продолжают работать через fallback-логику в ElementConstructor.tsx. Для полной миграции рекомендуется перевести все элементы на новую архитектуру.