Update Js for logs.html
This commit is contained in:
129
frontend/js/components/customSelectV2.js
Normal file
129
frontend/js/components/customSelectV2.js
Normal file
@@ -0,0 +1,129 @@
|
||||
// Filename: frontend/js/components/customSelectV2.js
|
||||
import { createPopper } from '../vendor/popper.esm.min.js';
|
||||
|
||||
export default class CustomSelectV2 {
|
||||
constructor(container) {
|
||||
this.container = container;
|
||||
this.trigger = this.container.querySelector('.custom-select-trigger');
|
||||
this.nativeSelect = this.container.querySelector('select');
|
||||
this.template = this.container.querySelector('.custom-select-panel-template');
|
||||
|
||||
if (!this.trigger || !this.nativeSelect || !this.template) {
|
||||
console.warn('CustomSelectV2 cannot initialize: missing required elements.', this.container);
|
||||
return;
|
||||
}
|
||||
|
||||
this.panel = null;
|
||||
this.popperInstance = null;
|
||||
this.isOpen = false;
|
||||
this.triggerText = this.trigger.querySelector('span');
|
||||
|
||||
if (typeof CustomSelectV2.openInstance === 'undefined') {
|
||||
CustomSelectV2.openInstance = null;
|
||||
CustomSelectV2.initGlobalListener();
|
||||
}
|
||||
|
||||
this.updateTriggerText();
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
static initGlobalListener() {
|
||||
document.addEventListener('click', (event) => {
|
||||
const instance = CustomSelectV2.openInstance;
|
||||
if (instance && !instance.container.contains(event.target) && (!instance.panel || !instance.panel.contains(event.target))) {
|
||||
instance.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createPanel() {
|
||||
const panelFragment = this.template.content.cloneNode(true);
|
||||
this.panel = panelFragment.querySelector('.custom-select-panel');
|
||||
document.body.appendChild(this.panel);
|
||||
|
||||
this.panel.innerHTML = '';
|
||||
Array.from(this.nativeSelect.options).forEach(option => {
|
||||
const item = document.createElement('a');
|
||||
item.href = '#';
|
||||
item.className = 'custom-select-option block w-full text-left px-3 py-1.5 text-sm text-zinc-700 hover:bg-zinc-100 dark:text-zinc-200 dark:hover:bg-zinc-700';
|
||||
item.textContent = option.textContent;
|
||||
item.dataset.value = option.value;
|
||||
if (option.selected) { item.classList.add('is-selected'); }
|
||||
this.panel.appendChild(item);
|
||||
});
|
||||
|
||||
this.panel.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
const optionEl = event.target.closest('.custom-select-option');
|
||||
if (optionEl) { this.selectOption(optionEl); }
|
||||
});
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.trigger.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
if (CustomSelectV2.openInstance && CustomSelectV2.openInstance !== this) {
|
||||
CustomSelectV2.openInstance.close();
|
||||
}
|
||||
this.toggle();
|
||||
});
|
||||
}
|
||||
|
||||
selectOption(optionEl) {
|
||||
const selectedValue = optionEl.dataset.value;
|
||||
if (this.nativeSelect.value !== selectedValue) {
|
||||
this.nativeSelect.value = selectedValue;
|
||||
this.nativeSelect.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
this.updateTriggerText();
|
||||
this.close();
|
||||
}
|
||||
|
||||
updateTriggerText() {
|
||||
const selectedOption = this.nativeSelect.options[this.nativeSelect.selectedIndex];
|
||||
if (selectedOption) {
|
||||
this.triggerText.textContent = selectedOption.textContent;
|
||||
}
|
||||
}
|
||||
|
||||
toggle() { this.isOpen ? this.close() : this.open(); }
|
||||
|
||||
open() {
|
||||
if (this.isOpen) return;
|
||||
this.isOpen = true;
|
||||
|
||||
if (!this.panel) { this.createPanel(); }
|
||||
|
||||
this.panel.style.display = 'block';
|
||||
this.panel.offsetHeight;
|
||||
|
||||
this.popperInstance = createPopper(this.trigger, this.panel, {
|
||||
placement: 'top-start',
|
||||
modifiers: [
|
||||
{ name: 'offset', options: { offset: [0, 8] } },
|
||||
{ name: 'flip', options: { fallbackPlacements: ['bottom-start'] } }
|
||||
],
|
||||
});
|
||||
|
||||
CustomSelectV2.openInstance = this;
|
||||
}
|
||||
|
||||
close() {
|
||||
if (!this.isOpen) return;
|
||||
this.isOpen = false;
|
||||
|
||||
if (this.popperInstance) {
|
||||
this.popperInstance.destroy();
|
||||
this.popperInstance = null;
|
||||
}
|
||||
|
||||
if (this.panel) {
|
||||
this.panel.remove();
|
||||
this.panel = null;
|
||||
}
|
||||
|
||||
if (CustomSelectV2.openInstance === this) {
|
||||
CustomSelectV2.openInstance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
95
frontend/js/components/filterPopover.js
Normal file
95
frontend/js/components/filterPopover.js
Normal file
@@ -0,0 +1,95 @@
|
||||
// Filename: frontend/js/components/filterPopover.js
|
||||
|
||||
import { createPopper } from '../vendor/popper.esm.min.js';
|
||||
|
||||
export default class FilterPopover {
|
||||
constructor(triggerElement, options, title) {
|
||||
if (!triggerElement || typeof createPopper !== 'function') {
|
||||
console.error('FilterPopover: Trigger element or Popper.js not found.');
|
||||
return;
|
||||
}
|
||||
this.triggerElement = triggerElement;
|
||||
this.options = options;
|
||||
this.title = title;
|
||||
this.selectedValues = new Set();
|
||||
|
||||
this._createPopoverHTML();
|
||||
this.popperInstance = createPopper(this.triggerElement, this.popoverElement, {
|
||||
placement: 'bottom-start',
|
||||
modifiers: [{ name: 'offset', options: { offset: [0, 8] } }],
|
||||
});
|
||||
|
||||
this._bindEvents();
|
||||
}
|
||||
|
||||
_createPopoverHTML() {
|
||||
this.popoverElement = document.createElement('div');
|
||||
this.popoverElement.className = 'hidden z-50 min-w-[12rem] rounded-md border bg-popover bg-white dark:bg-zinc-800 p-2 text-popover-foreground shadow-md';
|
||||
this.popoverElement.innerHTML = `
|
||||
<div class="px-2 py-1.5 text-sm font-semibold">${this.title}</div>
|
||||
<div class="space-y-1 p-1">
|
||||
${this.options.map(option => `
|
||||
<label class="flex items-center space-x-2 px-2 py-1.5 rounded-md hover:bg-accent cursor-pointer">
|
||||
<input type="checkbox" value="${option.value}" class="h-4 w-4 rounded border-zinc-300 text-blue-600 focus:ring-blue-500">
|
||||
<span class="text-sm">${option.label}</span>
|
||||
</label>
|
||||
`).join('')}
|
||||
</div>
|
||||
<div class="border-t border-border mt-2 pt-2 px-2 flex justify-end space-x-2">
|
||||
<button data-action="clear" class="btn btn-ghost h-7 px-2 text-xs">清空</button>
|
||||
<button data-action="apply" class="btn btn-primary h-7 px-2 text-xs">应用</button>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(this.popoverElement);
|
||||
}
|
||||
|
||||
_bindEvents() {
|
||||
this.triggerElement.addEventListener('click', () => this.toggle());
|
||||
|
||||
document.addEventListener('click', (event) => {
|
||||
if (!this.popoverElement.contains(event.target) && !this.triggerElement.contains(event.target)) {
|
||||
this.hide();
|
||||
}
|
||||
});
|
||||
|
||||
this.popoverElement.addEventListener('click', (event) => {
|
||||
const target = event.target.closest('button');
|
||||
if (!target) return;
|
||||
const action = target.dataset.action;
|
||||
if (action === 'clear') this._handleClear();
|
||||
if (action === 'apply') this._handleApply();
|
||||
});
|
||||
}
|
||||
|
||||
_handleClear() {
|
||||
this.popoverElement.querySelectorAll('input[type="checkbox"]').forEach(cb => cb.checked = false);
|
||||
this.selectedValues.clear();
|
||||
this._handleApply();
|
||||
}
|
||||
|
||||
_handleApply() {
|
||||
this.selectedValues.clear();
|
||||
this.popoverElement.querySelectorAll('input:checked').forEach(cb => {
|
||||
this.selectedValues.add(cb.value);
|
||||
});
|
||||
|
||||
const filterChangeEvent = new CustomEvent('filter-change', {
|
||||
detail: {
|
||||
filterKey: this.triggerElement.id,
|
||||
selected: this.selectedValues
|
||||
}
|
||||
});
|
||||
this.triggerElement.dispatchEvent(filterChangeEvent);
|
||||
|
||||
this.hide();
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.popoverElement.classList.toggle('hidden');
|
||||
this.popperInstance.update();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.popoverElement.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user