Замена простого select на autocomplete с поиском для привязки атрибутов к товарам/комплектам
- Переиспользован модуль select2-product-search.js из orders - Заменен простой select на Select2 с AJAX поиском через API search_products_and_variants - Добавлена поддержка привязки как ProductKit, так и Product к значениям атрибутов - Обновлен метод _save_attributes_from_cards для обработки item_ids и item_types - Удалены дублирующиеся подключения jQuery и Select2 (используются из base.html) - Улучшен UX: живой поиск, отображение типа товара (🌹/💐), цены и наличия
This commit is contained in:
@@ -29,6 +29,30 @@ input[name*="DELETE"] {
|
||||
align-items: center;
|
||||
min-height: 38px;
|
||||
}
|
||||
|
||||
/* Стили для autocomplete товаров/комплектов */
|
||||
.product-kit-select-wrapper {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
/* Отображение типа товара в Select2 */
|
||||
.select2-results__option .item-type-badge {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.1rem 0.4rem;
|
||||
margin-left: 0.3rem;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.item-type-kit {
|
||||
background-color: #0d6efd;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.item-type-product {
|
||||
background-color: #198754;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@@ -108,12 +132,6 @@ input[name*="DELETE"] {
|
||||
|
||||
<!-- Данные для JavaScript -->
|
||||
<script>
|
||||
window.AVAILABLE_KITS = [
|
||||
{% for kit in available_kits %}
|
||||
{ id: {{ kit.id }}, name: "{{ kit.name|escapejs }}" }{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
];
|
||||
|
||||
// Справочник атрибутов с их значениями
|
||||
window.PRODUCT_ATTRIBUTES = [
|
||||
{% for attr in product_attributes %}
|
||||
@@ -133,7 +151,8 @@ input[name*="DELETE"] {
|
||||
// URL для API
|
||||
window.API_URLS = {
|
||||
createAttribute: "{% url 'products:api-attribute-create' %}",
|
||||
addValue: function(attrId) { return `/products/api/attributes/${attrId}/values/add/`; }
|
||||
addValue: function(attrId) { return `/products/api/attributes/${attrId}/values/add/`; },
|
||||
searchProductsAndVariants: "{% url 'products:api-search-products-variants' %}"
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -266,16 +285,17 @@ input[name*="DELETE"] {
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Переиспользуемый модуль для Select2 поиска товаров -->
|
||||
<script src="{% static 'products/js/select2-product-search.js' %}"></script>
|
||||
|
||||
<script>
|
||||
// === Управление параметрами товара (карточный интерфейс) ===
|
||||
|
||||
// Функция для добавления нового поля значения параметра с выбором ProductKit
|
||||
function addValueField(container, valueText = '', kitId = '') {
|
||||
// Функция для добавления нового поля значения параметра с выбором Product/Kit через Select2
|
||||
function addValueField(container, valueText = '', itemId = '', itemType = '') {
|
||||
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 selectId = `product-kit-select-${fieldId}`;
|
||||
|
||||
const html = `
|
||||
<div class="value-field-group d-flex gap-2 mb-2 align-items-start">
|
||||
@@ -283,13 +303,13 @@ function addValueField(container, valueText = '', kitId = '') {
|
||||
placeholder="Введите значение"
|
||||
value="${escapeHtml(valueText)}"
|
||||
data-field-id="${fieldId}"
|
||||
style="min-width: 100px;">
|
||||
<select class="form-select form-select-sm parameter-kit-select"
|
||||
style="flex: 0 0 150px;">
|
||||
<select class="form-select form-select-sm product-kit-select"
|
||||
id="${selectId}"
|
||||
data-field-id="${fieldId}"
|
||||
title="Выберите комплект для этого значения"
|
||||
style="min-width: 150px;">
|
||||
<option value="">-- Выберите комплект --</option>
|
||||
${kitOptionsHtml}
|
||||
data-ajax-url="${window.API_URLS.searchProductsAndVariants}"
|
||||
style="flex: 1; min-width: 200px;">
|
||||
<option value=""></option>
|
||||
</select>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger remove-value-btn" title="Удалить значение">
|
||||
<i class="bi bi-trash"></i>
|
||||
@@ -299,12 +319,22 @@ function addValueField(container, valueText = '', kitId = '') {
|
||||
|
||||
container.insertAdjacentHTML('beforeend', html);
|
||||
|
||||
// Установка выбранного комплекта если был передан
|
||||
if (kitId) {
|
||||
const lastSelect = container.querySelector('.value-field-group:last-child .parameter-kit-select');
|
||||
if (lastSelect) {
|
||||
lastSelect.value = kitId;
|
||||
// Инициализируем Select2 для нового поля
|
||||
const newSelect = document.getElementById(selectId);
|
||||
if (newSelect) {
|
||||
// Если есть предзагруженные данные, добавляем option
|
||||
if (itemId && itemType) {
|
||||
const optionValue = `${itemType}_${itemId}`;
|
||||
const option = document.createElement('option');
|
||||
option.value = optionValue;
|
||||
option.selected = true;
|
||||
option.setAttribute('data-type', itemType);
|
||||
// Текст будет установлен через Select2
|
||||
newSelect.appendChild(option);
|
||||
}
|
||||
|
||||
// Инициализируем Select2 с AJAX поиском
|
||||
window.initProductSelect2(newSelect, 'all', window.API_URLS.searchProductsAndVariants);
|
||||
}
|
||||
|
||||
// Обработчик удаления значения
|
||||
@@ -312,6 +342,11 @@ function addValueField(container, valueText = '', kitId = '') {
|
||||
if (lastRemoveBtn) {
|
||||
lastRemoveBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
// Уничтожаем Select2 перед удалением элемента
|
||||
const select = this.closest('.value-field-group').querySelector('.product-kit-select');
|
||||
if (select && $(select).data('select2')) {
|
||||
$(select).select2('destroy');
|
||||
}
|
||||
this.closest('.value-field-group').remove();
|
||||
});
|
||||
}
|
||||
@@ -324,15 +359,6 @@ function escapeHtml(text) {
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Получить HTML с опциями комплектов
|
||||
function getKitOptionsHtml(selectedKitId = '') {
|
||||
const kitsData = window.AVAILABLE_KITS || [];
|
||||
return kitsData.map(kit => {
|
||||
const selected = kit.id == selectedKitId ? 'selected' : '';
|
||||
return `<option value="${kit.id}" ${selected}>${escapeHtml(kit.name)}</option>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// Найти атрибут в справочнике по имени
|
||||
function findAttributeByName(name) {
|
||||
const attributes = window.PRODUCT_ATTRIBUTES || [];
|
||||
@@ -775,24 +801,32 @@ function initParamDeleteToggle(card) {
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для сериализации значений параметров и их комплектов перед отправкой формы
|
||||
// Функция для сериализации значений параметров и их товаров/комплектов перед отправкой формы
|
||||
function serializeAttributeValues() {
|
||||
document.querySelectorAll('.attribute-card').forEach((card, idx) => {
|
||||
const valueGroups = card.querySelectorAll('.value-field-group');
|
||||
const values = [];
|
||||
const kits = [];
|
||||
const itemIds = [];
|
||||
const itemTypes = [];
|
||||
|
||||
valueGroups.forEach(group => {
|
||||
const valueInput = group.querySelector('.parameter-value-input');
|
||||
const kitSelect = group.querySelector('.parameter-kit-select');
|
||||
const itemSelect = group.querySelector('.product-kit-select');
|
||||
|
||||
if (valueInput) {
|
||||
if (valueInput && itemSelect) {
|
||||
const value = valueInput.value.trim();
|
||||
const kitId = kitSelect ? kitSelect.value : '';
|
||||
const selectedValue = itemSelect.value; // Формат: "product_123" или "kit_456"
|
||||
|
||||
if (value && kitId) {
|
||||
values.push(value);
|
||||
kits.push(parseInt(kitId));
|
||||
if (value && selectedValue) {
|
||||
const parts = selectedValue.split('_');
|
||||
if (parts.length === 2) {
|
||||
const type = parts[0]; // 'product' или 'kit'
|
||||
const id = parts[1];
|
||||
|
||||
values.push(value);
|
||||
itemIds.push(parseInt(id));
|
||||
itemTypes.push(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -808,15 +842,25 @@ function serializeAttributeValues() {
|
||||
}
|
||||
valuesField.value = JSON.stringify(values);
|
||||
|
||||
const kitsFieldName = `attributes-${idx}-kits`;
|
||||
let kitsField = document.querySelector(`input[name="${kitsFieldName}"]`);
|
||||
if (!kitsField) {
|
||||
kitsField = document.createElement('input');
|
||||
kitsField.type = 'hidden';
|
||||
kitsField.name = kitsFieldName;
|
||||
card.appendChild(kitsField);
|
||||
const itemIdsFieldName = `attributes-${idx}-item_ids`;
|
||||
let itemIdsField = document.querySelector(`input[name="${itemIdsFieldName}"]`);
|
||||
if (!itemIdsField) {
|
||||
itemIdsField = document.createElement('input');
|
||||
itemIdsField.type = 'hidden';
|
||||
itemIdsField.name = itemIdsFieldName;
|
||||
card.appendChild(itemIdsField);
|
||||
}
|
||||
kitsField.value = JSON.stringify(kits);
|
||||
itemIdsField.value = JSON.stringify(itemIds);
|
||||
|
||||
const itemTypesFieldName = `attributes-${idx}-item_types`;
|
||||
let itemTypesField = document.querySelector(`input[name="${itemTypesFieldName}"]`);
|
||||
if (!itemTypesField) {
|
||||
itemTypesField = document.createElement('input');
|
||||
itemTypesField.type = 'hidden';
|
||||
itemTypesField.name = itemTypesFieldName;
|
||||
card.appendChild(itemTypesField);
|
||||
}
|
||||
itemTypesField.value = JSON.stringify(itemTypes);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user