Добавлено API для получения списка атрибутов и их значений; обновлены формы для работы с атрибутами через JavaScript
This commit is contained in:
@@ -106,13 +106,35 @@ input[name*="DELETE"] {
|
||||
|
||||
{{ attribute_formset.management_form }}
|
||||
|
||||
<!-- Список доступных комплектов для JavaScript -->
|
||||
<!-- Данные для JavaScript -->
|
||||
<script>
|
||||
window.AVAILABLE_KITS = [
|
||||
{% for kit in available_kits %}
|
||||
{ id: {{ kit.id }}, name: "{{ kit.name }}" }{% if not forloop.last %},{% endif %}
|
||||
{ id: {{ kit.id }}, name: "{{ kit.name|escapejs }}" }{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
];
|
||||
|
||||
// Справочник атрибутов с их значениями
|
||||
window.PRODUCT_ATTRIBUTES = [
|
||||
{% for attr in product_attributes %}
|
||||
{
|
||||
id: {{ attr.id }},
|
||||
name: "{{ attr.name|escapejs }}",
|
||||
slug: "{{ attr.slug|escapejs }}",
|
||||
values: [
|
||||
{% for val in attr.values.all %}
|
||||
{ id: {{ val.id }}, value: "{{ val.value|escapejs }}", slug: "{{ val.slug|escapejs }}" }{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
]
|
||||
}{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
];
|
||||
|
||||
// URL для API
|
||||
window.API_URLS = {
|
||||
createAttribute: "{% url 'products:api-attribute-create' %}",
|
||||
addValue: function(attrId) { return `/products/api/attributes/${attrId}/values/add/`; }
|
||||
};
|
||||
</script>
|
||||
|
||||
{% if attribute_formset.non_form_errors %}
|
||||
@@ -127,10 +149,26 @@ input[name*="DELETE"] {
|
||||
{{ form.id }}
|
||||
|
||||
<div class="row align-items-end g-3 mb-3">
|
||||
<!-- Название параметра -->
|
||||
<div class="col-md-3">
|
||||
<!-- Название параметра с выбором из справочника -->
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">{{ form.name.label }}</label>
|
||||
{{ form.name }}
|
||||
<div class="input-group">
|
||||
<select class="form-select param-name-select"
|
||||
data-name-input="id_attributes-{{ forloop.counter0 }}-name">
|
||||
<option value="">-- Выберите атрибут --</option>
|
||||
{% for attr in product_attributes %}
|
||||
<option value="{{ attr.name }}" {% if form.name.value == attr.name %}selected{% endif %}>
|
||||
{{ attr.name }} ({{ attr.values.count }} знач.)
|
||||
</option>
|
||||
{% endfor %}
|
||||
<option value="__new__">+ Создать новый...</option>
|
||||
</select>
|
||||
<input type="hidden"
|
||||
name="attributes-{{ forloop.counter0 }}-name"
|
||||
id="id_attributes-{{ forloop.counter0 }}-name"
|
||||
class="param-name-input"
|
||||
value="{{ form.name.value|default:'' }}">
|
||||
</div>
|
||||
{% if form.name.errors %}
|
||||
<div class="text-danger small">{{ form.name.errors.0 }}</div>
|
||||
{% endif %}
|
||||
@@ -169,9 +207,14 @@ input[name*="DELETE"] {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Значения параметра (добавляются инлайн через JavaScript) -->
|
||||
<!-- Значения параметра -->
|
||||
<div class="parameter-values-container mt-3 p-3 bg-white rounded border">
|
||||
<label class="form-label small fw-semibold d-block mb-2">Значения параметра:</label>
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<label class="form-label small fw-semibold mb-0">Значения параметра:</label>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary load-values-btn" title="Загрузить значения из справочника">
|
||||
<i class="bi bi-arrow-down-circle me-1"></i> Загрузить из справочника
|
||||
</button>
|
||||
</div>
|
||||
<div class="value-fields-wrapper" data-param-index="{{ forloop.counter0 }}">
|
||||
<!-- Значения будут добавлены через JavaScript -->
|
||||
</div>
|
||||
@@ -231,14 +274,14 @@ function addValueField(container, valueText = '', kitId = '') {
|
||||
const index = container.querySelectorAll('.value-field-group').length;
|
||||
const fieldId = `value-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
// Получаем список доступных комплектов из скрытого элемента
|
||||
// Получаем список доступных комплектов
|
||||
const kitOptionsHtml = getKitOptionsHtml(kitId);
|
||||
|
||||
const html = `
|
||||
<div class="value-field-group d-flex gap-2 mb-2 align-items-start">
|
||||
<input type="text" class="form-control form-control-sm parameter-value-input"
|
||||
placeholder="Введите значение"
|
||||
value="${valueText}"
|
||||
value="${escapeHtml(valueText)}"
|
||||
data-field-id="${fieldId}"
|
||||
style="min-width: 100px;">
|
||||
<select class="form-select form-select-sm parameter-kit-select"
|
||||
@@ -258,17 +301,27 @@ function addValueField(container, valueText = '', kitId = '') {
|
||||
|
||||
// Установка выбранного комплекта если был передан
|
||||
if (kitId) {
|
||||
const kitSelect = container.querySelector('.parameter-kit-select:last-child');
|
||||
if (kitSelect) {
|
||||
kitSelect.value = kitId;
|
||||
const lastSelect = container.querySelector('.value-field-group:last-child .parameter-kit-select');
|
||||
if (lastSelect) {
|
||||
lastSelect.value = kitId;
|
||||
}
|
||||
}
|
||||
|
||||
// Обработчик удаления значения
|
||||
container.querySelector('.remove-value-btn:last-child').addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
this.closest('.value-field-group').remove();
|
||||
});
|
||||
const lastRemoveBtn = container.querySelector('.value-field-group:last-child .remove-value-btn');
|
||||
if (lastRemoveBtn) {
|
||||
lastRemoveBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
this.closest('.value-field-group').remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Экранирование HTML
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Получить HTML с опциями комплектов
|
||||
@@ -276,23 +329,89 @@ function getKitOptionsHtml(selectedKitId = '') {
|
||||
const kitsData = window.AVAILABLE_KITS || [];
|
||||
return kitsData.map(kit => {
|
||||
const selected = kit.id == selectedKitId ? 'selected' : '';
|
||||
return `<option value="${kit.id}" ${selected}>${kit.name}</option>`;
|
||||
return `<option value="${kit.id}" ${selected}>${escapeHtml(kit.name)}</option>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// Найти атрибут в справочнике по имени
|
||||
function findAttributeByName(name) {
|
||||
const attributes = window.PRODUCT_ATTRIBUTES || [];
|
||||
return attributes.find(attr => attr.name.toLowerCase() === name.toLowerCase());
|
||||
}
|
||||
|
||||
// Инициализация существующих параметров с их значениями из БД
|
||||
function initializeParameterCards() {
|
||||
document.querySelectorAll('.attribute-card').forEach(card => {
|
||||
// Если это существующий параметр с ID, загрузим его значения
|
||||
// Это будет обработано при первой загрузке в view
|
||||
initAddValueBtn(card);
|
||||
initCardHandlers(card);
|
||||
});
|
||||
}
|
||||
|
||||
// Инициализация кнопки добавления значения для карточки
|
||||
// Инициализация всех обработчиков для карточки
|
||||
function initCardHandlers(card) {
|
||||
initAddValueBtn(card);
|
||||
initLoadValuesBtn(card);
|
||||
initAttributeSelect(card);
|
||||
initParamDeleteToggle(card);
|
||||
}
|
||||
|
||||
// Получить HTML опций для select атрибутов
|
||||
function getAttributeOptionsHtml(selectedName = '') {
|
||||
const attributes = window.PRODUCT_ATTRIBUTES || [];
|
||||
let html = '<option value="">-- Выберите атрибут --</option>';
|
||||
attributes.forEach(attr => {
|
||||
const selected = attr.name === selectedName ? 'selected' : '';
|
||||
html += `<option value="${escapeHtml(attr.name)}" ${selected}>${escapeHtml(attr.name)} (${attr.values.length} знач.)</option>`;
|
||||
});
|
||||
html += '<option value="__new__">+ Создать новый...</option>';
|
||||
return html;
|
||||
}
|
||||
|
||||
// Инициализация select выбора атрибута
|
||||
function initAttributeSelect(card) {
|
||||
const select = card.querySelector('.param-name-select');
|
||||
if (select && !select.dataset.initialized) {
|
||||
select.dataset.initialized = 'true';
|
||||
select.addEventListener('change', function() {
|
||||
const hiddenInput = card.querySelector('.param-name-input');
|
||||
const value = this.value;
|
||||
|
||||
if (value === '__new__') {
|
||||
// Открываем модальное окно для создания нового атрибута
|
||||
openCreateAttributeModal('', card);
|
||||
// Сбрасываем select
|
||||
this.value = '';
|
||||
} else {
|
||||
// Устанавливаем значение в скрытое поле
|
||||
if (hiddenInput) {
|
||||
hiddenInput.value = value;
|
||||
}
|
||||
|
||||
// Если выбран атрибут из справочника - предлагаем загрузить значения
|
||||
if (value) {
|
||||
const attribute = findAttributeByName(value);
|
||||
if (attribute && attribute.values.length > 0) {
|
||||
const container = card.querySelector('.value-fields-wrapper');
|
||||
const existingValues = container.querySelectorAll('.value-field-group');
|
||||
|
||||
if (existingValues.length === 0) {
|
||||
// Автоматически загружаем значения если их ещё нет
|
||||
attribute.values.forEach(val => {
|
||||
addValueField(container, val.value, '');
|
||||
});
|
||||
showToast(`Загружено ${attribute.values.length} значений для "${value}"`, 'success');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация кнопки добавления значения
|
||||
function initAddValueBtn(card) {
|
||||
const addBtn = card.querySelector('.add-value-btn');
|
||||
if (addBtn) {
|
||||
if (addBtn && !addBtn.dataset.initialized) {
|
||||
addBtn.dataset.initialized = 'true';
|
||||
addBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const container = this.closest('.parameter-values-container').querySelector('.value-fields-wrapper');
|
||||
@@ -301,6 +420,263 @@ function initAddValueBtn(card) {
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация кнопки загрузки значений из справочника
|
||||
function initLoadValuesBtn(card) {
|
||||
const loadBtn = card.querySelector('.load-values-btn');
|
||||
if (loadBtn && !loadBtn.dataset.initialized) {
|
||||
loadBtn.dataset.initialized = 'true';
|
||||
loadBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const nameInput = card.querySelector('.param-name-input');
|
||||
const attrName = nameInput ? nameInput.value.trim() : '';
|
||||
|
||||
if (!attrName) {
|
||||
showToast('Сначала введите или выберите название параметра', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const attribute = findAttributeByName(attrName);
|
||||
if (!attribute) {
|
||||
showToast(`Атрибут "${attrName}" не найден в справочнике. Создайте его или добавьте значения вручную.`, 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
if (attribute.values.length === 0) {
|
||||
showToast(`У атрибута "${attrName}" нет значений в справочнике`, 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
// Загружаем значения из справочника
|
||||
const container = card.querySelector('.value-fields-wrapper');
|
||||
|
||||
// Спрашиваем пользователя, если уже есть значения
|
||||
const existingValues = container.querySelectorAll('.value-field-group');
|
||||
if (existingValues.length > 0) {
|
||||
if (!confirm('Уже есть добавленные значения. Добавить значения из справочника дополнительно?')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Добавляем значения
|
||||
attribute.values.forEach(val => {
|
||||
addValueField(container, val.value, '');
|
||||
});
|
||||
|
||||
showToast(`Загружено ${attribute.values.length} значений из справочника`, 'success');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Открыть модальное окно создания атрибута
|
||||
function openCreateAttributeModal(attrName, card) {
|
||||
// Получаем или создаем модальное окно
|
||||
let modal = document.getElementById('createAttributeModal');
|
||||
if (!modal) {
|
||||
createAttributeModal();
|
||||
modal = document.getElementById('createAttributeModal');
|
||||
}
|
||||
|
||||
// Заполняем название
|
||||
const nameInput = modal.querySelector('#newAttributeName');
|
||||
nameInput.value = attrName;
|
||||
|
||||
// Очищаем значения
|
||||
const valuesContainer = modal.querySelector('#newAttributeValues');
|
||||
valuesContainer.innerHTML = '';
|
||||
addNewAttributeValueField(valuesContainer);
|
||||
|
||||
// Сохраняем ссылку на карточку
|
||||
modal.dataset.targetCardIndex = card.dataset.formsetIndex;
|
||||
|
||||
// Показываем модальное окно
|
||||
const bsModal = new bootstrap.Modal(modal);
|
||||
bsModal.show();
|
||||
}
|
||||
|
||||
// Создать модальное окно для создания атрибута
|
||||
function createAttributeModal() {
|
||||
const modalHtml = `
|
||||
<div class="modal fade" id="createAttributeModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Создать новый атрибут</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Название атрибута</label>
|
||||
<input type="text" class="form-control" id="newAttributeName" placeholder="Например: Длина стебля">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Значения атрибута <small class="text-muted">(опционально)</small></label>
|
||||
<div id="newAttributeValues"></div>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary mt-2" id="addNewAttributeValueBtn">
|
||||
<i class="bi bi-plus-circle me-1"></i> Добавить значение
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
|
||||
<button type="button" class="btn btn-primary" id="saveNewAttributeBtn">
|
||||
<i class="bi bi-check-circle me-1"></i> Создать
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
||||
|
||||
// Инициализируем обработчики
|
||||
const modal = document.getElementById('createAttributeModal');
|
||||
|
||||
document.getElementById('addNewAttributeValueBtn').addEventListener('click', function() {
|
||||
const container = document.getElementById('newAttributeValues');
|
||||
addNewAttributeValueField(container);
|
||||
});
|
||||
|
||||
document.getElementById('saveNewAttributeBtn').addEventListener('click', async function() {
|
||||
await saveNewAttribute();
|
||||
});
|
||||
}
|
||||
|
||||
// Добавить поле значения в модальном окне
|
||||
function addNewAttributeValueField(container) {
|
||||
const html = `
|
||||
<div class="input-group mb-2 new-value-field">
|
||||
<input type="text" class="form-control" placeholder="Значение">
|
||||
<button type="button" class="btn btn-outline-danger remove-new-value-btn">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
container.insertAdjacentHTML('beforeend', html);
|
||||
|
||||
const lastRemoveBtn = container.querySelector('.new-value-field:last-child .remove-new-value-btn');
|
||||
lastRemoveBtn.addEventListener('click', function() {
|
||||
this.closest('.new-value-field').remove();
|
||||
});
|
||||
}
|
||||
|
||||
// Сохранить новый атрибут через API
|
||||
async function saveNewAttribute() {
|
||||
const modal = document.getElementById('createAttributeModal');
|
||||
const name = document.getElementById('newAttributeName').value.trim();
|
||||
|
||||
if (!name) {
|
||||
showToast('Введите название атрибута', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const saveBtn = document.getElementById('saveNewAttributeBtn');
|
||||
saveBtn.disabled = true;
|
||||
saveBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span> Сохранение...';
|
||||
|
||||
try {
|
||||
// Создаем атрибут
|
||||
const csrfToken = document.querySelector('[name="csrfmiddlewaretoken"]').value;
|
||||
|
||||
const response = await fetch(window.API_URLS.createAttribute, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
body: JSON.stringify({ name: name })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || 'Ошибка создания атрибута');
|
||||
}
|
||||
|
||||
const newAttr = {
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
slug: data.slug,
|
||||
values: []
|
||||
};
|
||||
|
||||
// Добавляем значения если они есть
|
||||
const valueInputs = document.querySelectorAll('#newAttributeValues .new-value-field input');
|
||||
for (const input of valueInputs) {
|
||||
const value = input.value.trim();
|
||||
if (value) {
|
||||
const valResponse = await fetch(window.API_URLS.addValue(data.id), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
body: JSON.stringify({ value: value })
|
||||
});
|
||||
const valData = await valResponse.json();
|
||||
if (valData.success) {
|
||||
newAttr.values.push({
|
||||
id: valData.id,
|
||||
value: valData.value,
|
||||
slug: valData.slug
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Добавляем в локальный справочник
|
||||
window.PRODUCT_ATTRIBUTES.push(newAttr);
|
||||
|
||||
// Обновляем все select'ы атрибутов
|
||||
updateAttributeSelects();
|
||||
|
||||
// Закрываем модальное окно
|
||||
const bsModal = bootstrap.Modal.getInstance(modal);
|
||||
bsModal.hide();
|
||||
|
||||
showToast(`Атрибут "${name}" успешно создан!`, 'success');
|
||||
|
||||
// Если есть целевая карточка, обновляем её
|
||||
const cardIndex = modal.dataset.targetCardIndex;
|
||||
if (cardIndex !== undefined) {
|
||||
const card = document.querySelector(`[data-formset-index="${cardIndex}"]`);
|
||||
if (card) {
|
||||
// Устанавливаем новый атрибут в select
|
||||
const select = card.querySelector('.param-name-select');
|
||||
const hiddenInput = card.querySelector('.param-name-input');
|
||||
if (select) {
|
||||
select.value = newAttr.name;
|
||||
}
|
||||
if (hiddenInput) {
|
||||
hiddenInput.value = newAttr.name;
|
||||
}
|
||||
|
||||
// Загружаем значения если есть
|
||||
if (newAttr.values.length > 0) {
|
||||
const container = card.querySelector('.value-fields-wrapper');
|
||||
newAttr.values.forEach(val => {
|
||||
addValueField(container, val.value, '');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
showToast(error.message, 'danger');
|
||||
} finally {
|
||||
saveBtn.disabled = false;
|
||||
saveBtn.innerHTML = '<i class="bi bi-check-circle me-1"></i> Создать';
|
||||
}
|
||||
}
|
||||
|
||||
// Обновить все select'ы атрибутов после добавления нового
|
||||
function updateAttributeSelects() {
|
||||
document.querySelectorAll('.param-name-select').forEach(select => {
|
||||
const currentValue = select.value;
|
||||
select.innerHTML = getAttributeOptionsHtml(currentValue);
|
||||
});
|
||||
}
|
||||
|
||||
// Добавление нового параметра
|
||||
document.getElementById('addParameterBtn')?.addEventListener('click', function() {
|
||||
const container = document.getElementById('attributeFormsetContainer');
|
||||
@@ -312,12 +688,19 @@ document.getElementById('addParameterBtn')?.addEventListener('click', function()
|
||||
<input type="hidden" name="attributes-${formIdx}-id">
|
||||
|
||||
<div class="row align-items-end g-3 mb-3">
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">Название параметра</label>
|
||||
<input type="text" name="attributes-${formIdx}-name"
|
||||
id="id_attributes-${formIdx}-name"
|
||||
class="form-control param-name-input"
|
||||
placeholder="Например: Длина, Цвет, Размер">
|
||||
<div class="input-group">
|
||||
<select class="form-select param-name-select"
|
||||
data-name-input="id_attributes-${formIdx}-name">
|
||||
${getAttributeOptionsHtml()}
|
||||
</select>
|
||||
<input type="hidden"
|
||||
name="attributes-${formIdx}-name"
|
||||
id="id_attributes-${formIdx}-name"
|
||||
class="param-name-input"
|
||||
value="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label small">Порядок</label>
|
||||
@@ -348,7 +731,12 @@ document.getElementById('addParameterBtn')?.addEventListener('click', function()
|
||||
</div>
|
||||
|
||||
<div class="parameter-values-container mt-3 p-3 bg-white rounded border">
|
||||
<label class="form-label small fw-semibold d-block mb-2">Значения параметра:</label>
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<label class="form-label small fw-semibold mb-0">Значения параметра:</label>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary load-values-btn" title="Загрузить значения из справочника">
|
||||
<i class="bi bi-arrow-down-circle me-1"></i> Загрузить из справочника
|
||||
</button>
|
||||
</div>
|
||||
<div class="value-fields-wrapper" data-param-index="${formIdx}">
|
||||
<!-- Значения добавляются сюда -->
|
||||
</div>
|
||||
@@ -364,16 +752,17 @@ document.getElementById('addParameterBtn')?.addEventListener('click', function()
|
||||
|
||||
// Инициализируем новую карточку
|
||||
const newCard = container.querySelector(`[data-formset-index="${formIdx}"]`);
|
||||
initAddValueBtn(newCard);
|
||||
initCardHandlers(newCard);
|
||||
|
||||
// Инициализируем удаление параметра
|
||||
initParamDeleteToggle(newCard);
|
||||
// Фокус на select
|
||||
newCard.querySelector('.param-name-select')?.focus();
|
||||
});
|
||||
|
||||
// Функция для скрытия удаленного параметра
|
||||
function initParamDeleteToggle(card) {
|
||||
const deleteCheckbox = card.querySelector('input[type="checkbox"][name$="-DELETE"]');
|
||||
if (deleteCheckbox) {
|
||||
if (deleteCheckbox && !deleteCheckbox.dataset.initialized) {
|
||||
deleteCheckbox.dataset.initialized = 'true';
|
||||
deleteCheckbox.addEventListener('change', function() {
|
||||
if (this.checked) {
|
||||
card.style.opacity = '0.5';
|
||||
@@ -388,12 +777,7 @@ function initParamDeleteToggle(card) {
|
||||
|
||||
// Функция для сериализации значений параметров и их комплектов перед отправкой формы
|
||||
function serializeAttributeValues() {
|
||||
/**
|
||||
* Перед отправкой формы нужно сериализовать все значения параметров
|
||||
* и их связанные комплекты из инлайн input'ов в скрытые JSON поля
|
||||
*/
|
||||
document.querySelectorAll('.attribute-card').forEach((card, idx) => {
|
||||
// Получаем все инпуты с значениями и их комплектами внутри этой карточки
|
||||
const valueGroups = card.querySelectorAll('.value-field-group');
|
||||
const values = [];
|
||||
const kits = [];
|
||||
@@ -406,7 +790,7 @@ function serializeAttributeValues() {
|
||||
const value = valueInput.value.trim();
|
||||
const kitId = kitSelect ? kitSelect.value : '';
|
||||
|
||||
if (value && kitId) { // Требуем чтобы оба поля были заполнены
|
||||
if (value && kitId) {
|
||||
values.push(value);
|
||||
kits.push(parseInt(kitId));
|
||||
}
|
||||
@@ -414,7 +798,6 @@ function serializeAttributeValues() {
|
||||
});
|
||||
|
||||
// Создаем или обновляем скрытые поля JSON
|
||||
// поле values: ["50", "60", "70"]
|
||||
const valuesFieldName = `attributes-${idx}-values`;
|
||||
let valuesField = document.querySelector(`input[name="${valuesFieldName}"]`);
|
||||
if (!valuesField) {
|
||||
@@ -425,7 +808,6 @@ function serializeAttributeValues() {
|
||||
}
|
||||
valuesField.value = JSON.stringify(values);
|
||||
|
||||
// поле kits: [1, 2, 3] (id ProductKit)
|
||||
const kitsFieldName = `attributes-${idx}-kits`;
|
||||
let kitsField = document.querySelector(`input[name="${kitsFieldName}"]`);
|
||||
if (!kitsField) {
|
||||
@@ -438,18 +820,52 @@ function serializeAttributeValues() {
|
||||
});
|
||||
}
|
||||
|
||||
// Toast уведомления
|
||||
function showToast(message, type = 'info') {
|
||||
// Создаем контейнер если его нет
|
||||
let container = document.getElementById('toastContainer');
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
container.id = 'toastContainer';
|
||||
container.className = 'toast-container position-fixed bottom-0 end-0 p-3';
|
||||
container.style.zIndex = '1100';
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
const bgClass = {
|
||||
'success': 'bg-success',
|
||||
'danger': 'bg-danger',
|
||||
'warning': 'bg-warning',
|
||||
'info': 'bg-info'
|
||||
}[type] || 'bg-info';
|
||||
|
||||
const textClass = type === 'warning' ? 'text-dark' : 'text-white';
|
||||
|
||||
const toastHtml = `
|
||||
<div class="toast ${bgClass} ${textClass}" role="alert">
|
||||
<div class="d-flex">
|
||||
<div class="toast-body">${escapeHtml(message)}</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.insertAdjacentHTML('beforeend', toastHtml);
|
||||
const toastEl = container.lastElementChild;
|
||||
const toast = new bootstrap.Toast(toastEl, { delay: 4000 });
|
||||
toast.show();
|
||||
|
||||
toastEl.addEventListener('hidden.bs.toast', () => toastEl.remove());
|
||||
}
|
||||
|
||||
// Инициализация при загрузке страницы
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initializeParameterCards();
|
||||
document.querySelectorAll('.attribute-card').forEach(card => {
|
||||
initParamDeleteToggle(card);
|
||||
});
|
||||
|
||||
// Добавляем сериализацию значений перед отправкой формы
|
||||
const form = document.querySelector('form');
|
||||
if (form) {
|
||||
form.addEventListener('submit', function(e) {
|
||||
// Перед отправкой формы сериализуем все значения параметров
|
||||
serializeAttributeValues();
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user