Update Js for logs.html

This commit is contained in:
XOF
2025-11-24 20:47:12 +08:00
parent f2706d6fc8
commit e026d8f324
23 changed files with 1884 additions and 396 deletions

View 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;
}
}
}

View 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');
}
}