diff --git a/static/css/style.css b/static/css/style.css index 91ddaa16..b1798c33 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -608,4 +608,70 @@ body.modal-open { .close:hover { opacity: 1; +} + +/* Toast Notifications */ +.toast { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%) translateY(100px); + background: var(--lora-surface); + color: var(--text-color); + padding: 12px 24px; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + z-index: calc(var(--z-overlay) + 10); /* 确保 toast 显示在模态窗口之上 */ + opacity: 0; + transition: transform 0.3s ease-out, opacity 0.3s ease-out; + text-align: center; + max-width: 90%; + backdrop-filter: blur(8px); + border: 1px solid var(--lora-border); +} + +/* 当模态窗口打开时的 toast 样式 */ +body.modal-open .toast { + bottom: 50% !important; /* 强制覆盖默认位置 */ + transform: translate(-50%, 50%) !important; /* 强制覆盖默认变换 */ + background: var(--lora-accent); + color: white; + z-index: 9999; /* 确保显示在最上层 */ + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.25); +} + +.toast.show { + transform: translateX(-50%) translateY(0); + opacity: 1; +} + +/* 确保在模态窗口打开时,不同类型的 toast 依然可辨识 */ +body.modal-open .toast-success { + background: oklch(65% 0.2 142); /* 绿色 */ +} + +body.modal-open .toast-error { + background: oklch(65% 0.2 29); /* 红色 */ +} + +body.modal-open .toast-info { + background: oklch(65% 0.2 256); /* 蓝色 */ +} + +.toast-success { + border-left: 4px solid #4caf50; +} + +.toast-error { + border-left: 4px solid #f44336; +} + +.toast-info { + border-left: 4px solid #2196f3; +} + +/* Ensure toasts are visible in both themes */ +[data-theme="dark"] .toast { + background: var(--lora-surface); + color: var(--lora-text); } \ No newline at end of file diff --git a/static/js/script.js b/static/js/script.js index 63c2f970..05921f77 100644 --- a/static/js/script.js +++ b/static/js/script.js @@ -130,6 +130,32 @@ class ModalManager { if (e.target === this.modal) this.close(); } } + +// 添加 toast 通知功能 +function showToast(message, type = 'info') { + const toast = document.createElement('div'); + toast.className = `toast toast-${type}`; + toast.textContent = message; + + // 移除任何现有的 toast + document.querySelectorAll('.toast').forEach(t => t.remove()); + + document.body.appendChild(toast); + + // 如果模态窗口打开,调整 toast 位置 + if (document.body.classList.contains('modal-open')) { + toast.style.transform = 'translate(-50%, 50%)'; // 在屏幕中间显示 + } + + // 触发动画 + requestAnimationFrame(() => { + toast.classList.add('show'); + setTimeout(() => { + toast.classList.remove('show'); + setTimeout(() => toast.remove(), 300); + }, 2000); + }); +} // 初始化 document.addEventListener('DOMContentLoaded', () => { @@ -351,8 +377,32 @@ document.querySelectorAll('.lora-card').forEach(card => { }); }); +// 更新卡片复制操作 +document.querySelectorAll('.lora-card').forEach(card => { + const copyBtn = card.querySelector('.fa-copy'); + if (copyBtn) { + copyBtn.onclick = (event) => { + event.stopPropagation(); + navigator.clipboard.writeText(card.dataset.file_name) + .then(() => showToast('Model name copied to clipboard', 'success')) + .catch(() => showToast('Failed to copy model name', 'error')); + }; + } + + // 为没有元数据的卡片添加点击反馈 + card.addEventListener('click', () => { + const meta = JSON.parse(card.dataset.meta || '{}'); + if (Object.keys(meta).length === 0) { + showToast('This model is not available on Civitai. No additional information to display.', 'info'); + } + }); +}); + function showModal(lora) { const modal = document.getElementById('loraModal'); + const escapedWords = lora.trainedWords?.length ? + lora.trainedWords.join(', ').toUpperCase().replace(/'/g, '\\\'') : ''; + modal.innerHTML = `