Files
JieXi/static/js/ui-components.js
2025-11-30 19:49:25 +08:00

240 lines
8.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 现代化UI组件库
const UI = {
// 初始化所有自定义下拉框
initSelects() {
document.querySelectorAll('select.ui-input, select.ui-select').forEach(select => {
if (select.dataset.uiInit) return; // 已初始化
this.createCustomSelect(select);
});
},
// 创建自定义下拉框
createCustomSelect(select) {
select.dataset.uiInit = 'true';
// 创建包装器
const wrapper = document.createElement('div');
wrapper.className = 'ui-select-wrapper';
select.parentNode.insertBefore(wrapper, select);
// 隐藏原生 select
select.classList.add('ui-select-native');
wrapper.appendChild(select);
// 创建触发器
const trigger = document.createElement('div');
trigger.className = 'ui-select-trigger';
trigger.tabIndex = 0;
wrapper.appendChild(trigger);
// 创建下拉菜单
const dropdown = document.createElement('div');
dropdown.className = 'ui-select-dropdown';
wrapper.appendChild(dropdown);
// 更新选项
const updateOptions = () => {
dropdown.innerHTML = '';
const selectedValue = select.value;
let selectedText = '';
Array.from(select.options).forEach(option => {
const optionEl = document.createElement('div');
optionEl.className = 'ui-select-option';
optionEl.dataset.value = option.value;
optionEl.textContent = option.textContent;
if (option.value === selectedValue) {
optionEl.classList.add('selected');
selectedText = option.textContent;
}
optionEl.addEventListener('click', () => {
select.value = option.value;
select.dispatchEvent(new Event('change', { bubbles: true }));
wrapper.classList.remove('open');
updateTrigger();
});
dropdown.appendChild(optionEl);
});
return selectedText;
};
// 更新触发器显示
const updateTrigger = () => {
const selectedText = updateOptions();
if (selectedText) {
trigger.innerHTML = `<span>${selectedText}</span>`;
} else {
trigger.innerHTML = `<span class="placeholder">${select.options[0]?.textContent || '请选择'}</span>`;
}
};
// 初始化
updateTrigger();
// 点击触发器
trigger.addEventListener('click', (e) => {
e.stopPropagation();
// 关闭其他下拉框
document.querySelectorAll('.ui-select-wrapper.open').forEach(w => {
if (w !== wrapper) w.classList.remove('open');
});
wrapper.classList.toggle('open');
});
// 键盘支持
trigger.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
wrapper.classList.toggle('open');
} else if (e.key === 'Escape') {
wrapper.classList.remove('open');
}
});
// 监听原生 select 变化
select.addEventListener('change', updateTrigger);
// 监听选项变化(用于动态更新)
const observer = new MutationObserver(updateTrigger);
observer.observe(select, { childList: true, subtree: true });
},
// 显示通知消息
notify(message, type = 'info', duration = 3000) {
const container = document.getElementById('notification-container') || this.createNotificationContainer();
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.innerHTML = `
<div class="notification-content">
<span class="notification-icon">${this.getIcon(type)}</span>
<span class="notification-message">${message}</span>
</div>
`;
container.appendChild(notification);
setTimeout(() => notification.classList.add('show'), 10);
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => notification.remove(), 300);
}, duration);
},
// 显示确认对话框
confirm(title, message, onConfirm, onCancel) {
const modal = document.createElement('div');
modal.className = 'ui-modal';
modal.innerHTML = `
<div class="ui-modal-overlay"></div>
<div class="ui-modal-content ui-modal-confirm">
<h3>${title}</h3>
<p>${message}</p>
<div class="ui-modal-actions">
<button class="ui-btn ui-btn-secondary" data-action="cancel">取消</button>
<button class="ui-btn ui-btn-primary" data-action="confirm">确认</button>
</div>
</div>
`;
document.body.appendChild(modal);
setTimeout(() => modal.classList.add('show'), 10);
modal.querySelector('[data-action="confirm"]').onclick = () => {
this.closeModal(modal);
if (onConfirm) onConfirm();
};
modal.querySelector('[data-action="cancel"]').onclick = () => {
this.closeModal(modal);
if (onCancel) onCancel();
};
modal.querySelector('.ui-modal-overlay').onclick = () => {
this.closeModal(modal);
if (onCancel) onCancel();
};
},
// 显示输入对话框
prompt(title, placeholder, defaultValue, onConfirm, onCancel) {
const modal = document.createElement('div');
modal.className = 'ui-modal';
modal.innerHTML = `
<div class="ui-modal-overlay"></div>
<div class="ui-modal-content ui-modal-prompt">
<h3>${title}</h3>
<input type="text" class="ui-input" placeholder="${placeholder}" value="${defaultValue || ''}">
<div class="ui-modal-actions">
<button class="ui-btn ui-btn-secondary" data-action="cancel">取消</button>
<button class="ui-btn ui-btn-primary" data-action="confirm">确认</button>
</div>
</div>
`;
document.body.appendChild(modal);
setTimeout(() => modal.classList.add('show'), 10);
const input = modal.querySelector('.ui-input');
input.focus();
input.select();
const confirm = () => {
const value = input.value.trim();
if (value) {
this.closeModal(modal);
if (onConfirm) onConfirm(value);
}
};
modal.querySelector('[data-action="confirm"]').onclick = confirm;
input.onkeypress = (e) => { if (e.key === 'Enter') confirm(); };
modal.querySelector('[data-action="cancel"]').onclick = () => {
this.closeModal(modal);
if (onCancel) onCancel();
};
modal.querySelector('.ui-modal-overlay').onclick = () => {
this.closeModal(modal);
if (onCancel) onCancel();
};
},
// 关闭模态框
closeModal(modal) {
modal.classList.remove('show');
setTimeout(() => modal.remove(), 300);
},
// 创建通知容器
createNotificationContainer() {
const container = document.createElement('div');
container.id = 'notification-container';
document.body.appendChild(container);
return container;
},
// 获取图标
getIcon(type) {
const icons = {
success: '✓',
error: '✕',
warning: '⚠',
info: ''
};
return icons[type] || icons.info;
}
};
// 点击外部关闭下拉框
document.addEventListener('click', (e) => {
if (!e.target.closest('.ui-select-wrapper')) {
document.querySelectorAll('.ui-select-wrapper.open').forEach(w => {
w.classList.remove('open');
});
}
});
// 页面加载完成后自动初始化下拉框
document.addEventListener('DOMContentLoaded', () => {
UI.initSelects();
});
// 提供手动刷新方法(用于动态加载的内容)
UI.refreshSelects = UI.initSelects;