Files
assist/src/web/static/js/components/modal.js
zhaojie 2026007045 refactor: 移除冗余文件并优化代码结构
- 删除多个不再使用的脚本和配置文件,包括 `auto_push.bat`, `check_and_fix_users.py`, `init.sql` 等。
- 新增 `git_push.bat` 和 `git_push.sh` 脚本以简化 Git 推送流程。
- 更新 `README.md` 以反映最新的功能和结构变化。
- 优化前端代码,添加新的页面和组件,提升用户体验。

此提交旨在清理项目结构并增强代码可维护性。
2025-12-08 00:53:23 +08:00

418 lines
12 KiB
JavaScript

/**
* 模态框组件
*/
import { addClass, removeClass, hasClass } from '../core/utils.js';
export class Modal {
constructor(options = {}) {
this.id = options.id || `modal-${Date.now()}`;
this.title = options.title || '';
this.content = options.content || '';
this.size = options.size || ''; // sm, lg, xl
this.backdrop = options.backdrop !== false;
this.keyboard = options.keyboard !== false;
this.centered = options.centered || false;
this.scrollable = options.scrollable || false;
this.static = options.static || false;
this.className = options.className || '';
this.footer = options.footer || null;
this.show = false;
this.onShow = options.onShow || (() => {});
this.onShown = options.onShown || (() => {});
this.onHide = options.onHide || (() => {});
this.onHidden = options.onHidden || (() => {});
this.init();
}
init() {
this.createModal();
this.bindEvents();
}
createModal() {
// 创建模态框容器
this.modal = document.createElement('div');
this.modal.className = 'modal fade';
this.modal.id = this.id;
this.modal.setAttribute('tabindex', '-1');
this.modal.setAttribute('aria-labelledby', `${this.id}-label`);
this.modal.setAttribute('aria-hidden', 'true');
// 模态框对话框
const dialog = document.createElement('div');
dialog.className = `modal-dialog ${this.size ? `modal-${this.size}` : ''} ${this.centered ? 'modal-dialog-centered' : ''} ${this.scrollable ? 'modal-dialog-scrollable' : ''}`;
// 模态框内容
const content = document.createElement('div');
content.className = 'modal-content';
if (this.className) {
addClass(content, this.className);
}
// 构建模态框HTML
let modalHTML = `
<div class="modal-header">
<h5 class="modal-title" id="${this.id}-label">${this.title}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
${this.content}
</div>
`;
// 添加底部按钮
if (this.footer) {
modalHTML += `
<div class="modal-footer">
${typeof this.footer === 'string' ? this.footer : this.renderFooter()}
</div>
`;
}
content.innerHTML = modalHTML;
dialog.appendChild(content);
this.modal.appendChild(dialog);
// 添加到页面
document.body.appendChild(this.modal);
}
renderFooter() {
if (!this.footer) return '';
if (Array.isArray(this.footer)) {
return this.footer.map(btn => {
const attrs = Object.keys(btn)
.filter(key => key !== 'text')
.map(key => `${key}="${btn[key]}"`)
.join(' ');
return `<button ${attrs}>${btn.text}</button>`;
}).join('');
}
return '';
}
bindEvents() {
// 关闭按钮
const closeBtn = this.modal.querySelector('.btn-close');
if (closeBtn) {
closeBtn.addEventListener('click', () => this.hide());
}
// 背景点击
if (!this.static) {
this.modal.addEventListener('click', (e) => {
if (e.target === this.modal) {
this.hide();
}
});
}
// ESC键关闭
if (this.keyboard) {
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && this.show) {
this.hide();
}
});
}
}
show() {
if (this.show) return;
// 触发显示前事件
this.onShow();
// 添加到页面
if (!this.modal.parentNode) {
document.body.appendChild(this.modal);
}
// 显示模态框
this.modal.style.display = 'block';
addClass(this.modal, 'show');
this.modal.setAttribute('aria-hidden', 'false');
// 防止背景滚动
document.body.style.overflow = 'hidden';
this.show = true;
// 聚焦到第一个可聚焦元素
setTimeout(() => {
const focusable = this.modal.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
if (focusable) {
focusable.focus();
}
}, 100);
// 触发显示后事件
setTimeout(() => {
this.onShown();
}, 150);
}
hide() {
if (!this.show) return;
// 触发隐藏前事件
this.onHide();
// 隐藏模态框
removeClass(this.modal, 'show');
this.modal.setAttribute('aria-hidden', 'true');
// 恢复背景滚动
document.body.style.overflow = '';
this.show = false;
// 延迟移除DOM
setTimeout(() => {
if (this.modal) {
this.modal.style.display = 'none';
if (this.modal.parentNode) {
this.modal.parentNode.removeChild(this.modal);
}
}
// 触发隐藏后事件
this.onHidden();
}, 150);
}
toggle() {
if (this.show) {
this.hide();
} else {
this.show();
}
}
update(options) {
if (options.title) {
this.title = options.title;
const titleEl = this.modal.querySelector('.modal-title');
if (titleEl) {
titleEl.textContent = this.title;
}
}
if (options.content) {
this.content = options.content;
const bodyEl = this.modal.querySelector('.modal-body');
if (bodyEl) {
bodyEl.innerHTML = this.content;
}
}
if (options.footer !== undefined) {
this.footer = options.footer;
const footerEl = this.modal.querySelector('.modal-footer');
if (footerEl) {
if (this.footer) {
footerEl.style.display = 'block';
footerEl.innerHTML = typeof this.footer === 'string' ? this.footer : this.renderFooter();
// 重新绑定底部按钮事件
this.bindFooterEvents();
} else {
footerEl.style.display = 'none';
}
}
}
}
bindFooterEvents() {
const footer = this.modal.querySelector('.modal-footer');
if (!footer) return;
footer.querySelectorAll('button').forEach(btn => {
const dataDismiss = btn.getAttribute('data-bs-dismiss');
if (dataDismiss === 'modal') {
btn.addEventListener('click', () => this.hide());
}
});
}
getModal() {
return this.modal;
}
getElement(selector) {
return this.modal.querySelector(selector);
}
destroy() {
this.hide();
setTimeout(() => {
if (this.modal && this.modal.parentNode) {
this.modal.parentNode.removeChild(this.modal);
}
this.modal = null;
}, 200);
}
}
// 确认对话框
export function confirm(options = {}) {
return new Promise((resolve) => {
const modal = new Modal({
title: options.title || '确认',
content: `
<div class="text-center">
<i class="fas fa-question-circle fa-3x text-warning mb-3"></i>
<p>${options.message || '确定要执行此操作吗?'}</p>
</div>
`,
size: 'sm',
centered: true,
footer: [
{ text: '取消', class: 'btn btn-secondary', 'data-bs-dismiss': 'modal' },
{ text: '确定', class: 'btn btn-primary', id: 'confirm-btn' }
]
});
modal.onHidden = () => {
modal.destroy();
};
modal.getElement('#confirm-btn').addEventListener('click', () => {
modal.hide();
resolve(true);
});
modal.onHidden = () => {
resolve(false);
modal.destroy();
};
modal.show();
});
}
// 警告对话框
export function alert(options = {}) {
return new Promise((resolve) => {
const modal = new Modal({
title: options.title || '提示',
content: `
<div class="text-center">
<i class="fas fa-${options.type === 'error' ? 'exclamation-circle text-danger' : 'info-circle text-info'} fa-3x mb-3"></i>
<p>${options.message || ''}</p>
</div>
`,
size: 'sm',
centered: true,
footer: [
{ text: '确定', class: 'btn btn-primary', id: 'alert-btn' }
]
});
modal.onHidden = () => {
modal.destroy();
resolve();
};
modal.getElement('#alert-btn').addEventListener('click', () => {
modal.hide();
});
modal.show();
});
}
// Toast通知
export class Toast {
constructor(options = {}) {
this.id = `toast-${Date.now()}`;
this.type = options.type || 'info';
this.message = options.message || '';
this.duration = options.duration || 3000;
this.closable = options.closable !== false;
this.autoHide = options.autoHide !== false;
this.init();
}
init() {
this.createToast();
this.show();
}
createToast() {
// 查找或创建toast容器
let container = document.querySelector('.toast-container');
if (!container) {
container = document.createElement('div');
container.className = 'toast-container';
document.body.appendChild(container);
}
// 创建toast元素
this.toast = document.createElement('div');
this.toast.className = `toast ${this.type}`;
this.toast.id = this.id;
const iconMap = {
success: 'fa-check-circle',
error: 'fa-exclamation-circle',
warning: 'fa-exclamation-triangle',
info: 'fa-info-circle'
};
this.toast.innerHTML = `
<div class="toast-content">
<i class="fas ${iconMap[this.type] || iconMap.info} me-2"></i>
<span>${this.message}</span>
${this.closable ? '<button type="button" class="btn-close ms-2" aria-label="Close"></button>' : ''}
</div>
`;
container.appendChild(this.toast);
// 绑定关闭事件
if (this.closable) {
const closeBtn = this.toast.querySelector('.btn-close');
if (closeBtn) {
closeBtn.addEventListener('click', () => this.hide());
}
}
// 自动隐藏
if (this.autoHide) {
setTimeout(() => this.hide(), this.duration);
}
}
show() {
setTimeout(() => {
addClass(this.toast, 'show');
}, 10);
}
hide() {
removeClass(this.toast, 'show');
setTimeout(() => {
if (this.toast && this.toast.parentNode) {
this.toast.parentNode.removeChild(this.toast);
}
}, 300);
}
}
// 创建全局toast函数
export function showToast(options) {
if (typeof options === 'string') {
options = { message: options };
}
return new Toast(options);
}
// 导出
export default Modal;