Добавление и Удаление сайта
Backend (Go): - Добавлен полный функционал создания сайтов - Добавлен функционал удаления сайтов - Новые API методы в admin.go: - Добавлен шаблон стартовой страницы - Добавлена функция DecodeBase64 Исправления критических ошибок: - Исправлена работа wildcard алиасов (*.domain.com) в handler.go - Исправлены ошибки "файл не найден" при создании файлов Frontend (JavaScript + HTML + CSS): - Добавлена страница создания сайта - Добавлена кнопка "Удалить сайт" в редактировании - Мелкие доработки стилей Build: - Обновлён build_admin.ps1 - добавлен шаг генерации биндингов (wails generate module) Fixes: - #fix Wildcard алиасы (*.domain.com) теперь работают корректно - #fix Удалён порт из host при проверке алиасов - #fix Приоритет точных доменов над wildcard - #fix Ошибки "файл не найден" при создании сайтов/vAccess - #fix Секция добавления сайта теперь скрывается при навигации
This commit is contained in:
140
Backend/admin/frontend/assets/js/ui/custom-select.js
Normal file
140
Backend/admin/frontend/assets/js/ui/custom-select.js
Normal file
@@ -0,0 +1,140 @@
|
||||
/* ============================================
|
||||
Custom Select Component
|
||||
Кастомные выпадающие списки
|
||||
============================================ */
|
||||
|
||||
import { $ } from '../utils/dom.js';
|
||||
|
||||
/**
|
||||
* Инициализация всех кастомных select'ов на странице
|
||||
*/
|
||||
export function initCustomSelects() {
|
||||
const selects = document.querySelectorAll('select.form-input');
|
||||
selects.forEach(select => {
|
||||
if (!select.dataset.customized) {
|
||||
createCustomSelect(select);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать кастомный select из нативного
|
||||
*/
|
||||
function createCustomSelect(selectElement) {
|
||||
// Помечаем как обработанный
|
||||
selectElement.dataset.customized = 'true';
|
||||
|
||||
// Создаём контейнер
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'custom-select';
|
||||
|
||||
// Получаем выбранное значение
|
||||
const selectedOption = selectElement.options[selectElement.selectedIndex];
|
||||
const selectedText = selectedOption ? selectedOption.text : '';
|
||||
|
||||
// Создаём кнопку (видимая часть)
|
||||
const button = document.createElement('div');
|
||||
button.className = 'custom-select-trigger';
|
||||
button.innerHTML = `
|
||||
<span class="custom-select-value">${selectedText}</span>
|
||||
<i class="fas fa-chevron-down custom-select-arrow"></i>
|
||||
`;
|
||||
|
||||
// Создаём выпадающий список
|
||||
const dropdown = document.createElement('div');
|
||||
dropdown.className = 'custom-select-dropdown';
|
||||
|
||||
// Заполняем опции
|
||||
Array.from(selectElement.options).forEach((option, index) => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'custom-select-option';
|
||||
item.textContent = option.text;
|
||||
item.dataset.value = option.value;
|
||||
item.dataset.index = index;
|
||||
|
||||
if (option.selected) {
|
||||
item.classList.add('selected');
|
||||
}
|
||||
|
||||
// Клик по опции
|
||||
item.addEventListener('click', () => {
|
||||
selectOption(selectElement, wrapper, item, index);
|
||||
});
|
||||
|
||||
dropdown.appendChild(item);
|
||||
});
|
||||
|
||||
// Клик по кнопке - открыть/закрыть
|
||||
button.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
toggleDropdown(wrapper);
|
||||
});
|
||||
|
||||
// Собираем вместе
|
||||
wrapper.appendChild(button);
|
||||
wrapper.appendChild(dropdown);
|
||||
|
||||
// Скрываем оригинальный select
|
||||
selectElement.style.display = 'none';
|
||||
|
||||
// Вставляем кастомный select после оригинального
|
||||
selectElement.parentNode.insertBefore(wrapper, selectElement.nextSibling);
|
||||
|
||||
// Закрываем при клике вне
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!wrapper.contains(e.target)) {
|
||||
closeDropdown(wrapper);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Открыть/закрыть dropdown
|
||||
*/
|
||||
function toggleDropdown(wrapper) {
|
||||
const isOpen = wrapper.classList.contains('open');
|
||||
|
||||
// Закрываем все открытые
|
||||
document.querySelectorAll('.custom-select.open').forEach(el => {
|
||||
el.classList.remove('open');
|
||||
});
|
||||
|
||||
if (!isOpen) {
|
||||
wrapper.classList.add('open');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Закрыть dropdown
|
||||
*/
|
||||
function closeDropdown(wrapper) {
|
||||
wrapper.classList.remove('open');
|
||||
}
|
||||
|
||||
/**
|
||||
* Выбрать опцию
|
||||
*/
|
||||
function selectOption(selectElement, wrapper, optionElement, index) {
|
||||
// Обновляем оригинальный select
|
||||
selectElement.selectedIndex = index;
|
||||
|
||||
// Триггерим событие change
|
||||
const event = new Event('change', { bubbles: true });
|
||||
selectElement.dispatchEvent(event);
|
||||
|
||||
// Обновляем UI
|
||||
const valueSpan = wrapper.querySelector('.custom-select-value');
|
||||
valueSpan.textContent = optionElement.textContent;
|
||||
|
||||
// Убираем selected у всех опций
|
||||
wrapper.querySelectorAll('.custom-select-option').forEach(opt => {
|
||||
opt.classList.remove('selected');
|
||||
});
|
||||
|
||||
// Добавляем selected к выбранной
|
||||
optionElement.classList.add('selected');
|
||||
|
||||
// Закрываем dropdown
|
||||
closeDropdown(wrapper);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user