290 lines
9.1 KiB
JavaScript
290 lines
9.1 KiB
JavaScript
/**
|
||
* UI交互增强脚本
|
||
* 提供加载状态、视觉反馈、操作提示等功能
|
||
*/
|
||
|
||
// 创建Toast容器
|
||
function ensureToastContainer() {
|
||
let container = document.getElementById('toastContainer');
|
||
if (!container) {
|
||
container = document.createElement('div');
|
||
container.id = 'toastContainer';
|
||
container.className = 'toast-container position-fixed top-0 end-0 p-3';
|
||
container.style.zIndex = '1060';
|
||
document.body.appendChild(container);
|
||
}
|
||
return container;
|
||
}
|
||
|
||
// 显示成功提示
|
||
function showSuccess(message, duration = 3000) {
|
||
const container = ensureToastContainer();
|
||
const toast = document.createElement('div');
|
||
toast.className = 'toast align-items-center text-white bg-success border-0';
|
||
toast.setAttribute('role', 'alert');
|
||
toast.setAttribute('aria-live', 'assertive');
|
||
toast.setAttribute('aria-atomic', 'true');
|
||
toast.innerHTML = `
|
||
<div class="d-flex">
|
||
<div class="toast-body">
|
||
<i class="bi bi-check-circle me-2"></i>${message}
|
||
</div>
|
||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||
</div>
|
||
`;
|
||
container.appendChild(toast);
|
||
const bsToast = new bootstrap.Toast(toast, { delay: duration });
|
||
bsToast.show();
|
||
toast.addEventListener('hidden.bs.toast', () => toast.remove());
|
||
}
|
||
|
||
// 显示错误提示
|
||
function showError(message, duration = 4000) {
|
||
const container = ensureToastContainer();
|
||
const toast = document.createElement('div');
|
||
toast.className = 'toast align-items-center text-white bg-danger border-0';
|
||
toast.setAttribute('role', 'alert');
|
||
toast.setAttribute('aria-live', 'assertive');
|
||
toast.setAttribute('aria-atomic', 'true');
|
||
toast.innerHTML = `
|
||
<div class="d-flex">
|
||
<div class="toast-body">
|
||
<i class="bi bi-exclamation-circle me-2"></i>${message}
|
||
</div>
|
||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||
</div>
|
||
`;
|
||
container.appendChild(toast);
|
||
const bsToast = new bootstrap.Toast(toast, { delay: duration });
|
||
bsToast.show();
|
||
toast.addEventListener('hidden.bs.toast', () => toast.remove());
|
||
}
|
||
|
||
// 显示警告提示
|
||
function showWarning(message, duration = 3000) {
|
||
const container = ensureToastContainer();
|
||
const toast = document.createElement('div');
|
||
toast.className = 'toast align-items-center text-white bg-warning border-0';
|
||
toast.setAttribute('role', 'alert');
|
||
toast.setAttribute('aria-live', 'assertive');
|
||
toast.setAttribute('aria-atomic', 'true');
|
||
toast.innerHTML = `
|
||
<div class="d-flex">
|
||
<div class="toast-body">
|
||
<i class="bi bi-exclamation-triangle me-2"></i>${message}
|
||
</div>
|
||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||
</div>
|
||
`;
|
||
container.appendChild(toast);
|
||
const bsToast = new bootstrap.Toast(toast, { delay: duration });
|
||
bsToast.show();
|
||
toast.addEventListener('hidden.bs.toast', () => toast.remove());
|
||
}
|
||
|
||
// 显示信息提示
|
||
function showInfo(message, duration = 3000) {
|
||
const container = ensureToastContainer();
|
||
const toast = document.createElement('div');
|
||
toast.className = 'toast align-items-center text-white bg-info border-0';
|
||
toast.setAttribute('role', 'alert');
|
||
toast.setAttribute('aria-live', 'assertive');
|
||
toast.setAttribute('aria-atomic', 'true');
|
||
toast.innerHTML = `
|
||
<div class="d-flex">
|
||
<div class="toast-body">
|
||
<i class="bi bi-info-circle me-2"></i>${message}
|
||
</div>
|
||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||
</div>
|
||
`;
|
||
container.appendChild(toast);
|
||
const bsToast = new bootstrap.Toast(toast, { delay: duration });
|
||
bsToast.show();
|
||
toast.addEventListener('hidden.bs.toast', () => toast.remove());
|
||
}
|
||
|
||
// 显示加载状态
|
||
function showLoading(elementId, message = '加载中...') {
|
||
const element = document.getElementById(elementId);
|
||
if (element) {
|
||
element.innerHTML = `
|
||
<div class="text-center py-5">
|
||
<div class="spinner-border text-primary" role="status">
|
||
<span class="visually-hidden">${message}</span>
|
||
</div>
|
||
<p class="text-muted mt-3">${message}</p>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
// 按钮加载状态
|
||
function setButtonLoading(button, loading = true, originalText = null) {
|
||
if (loading) {
|
||
if (!button.dataset.originalText) {
|
||
button.dataset.originalText = button.innerHTML;
|
||
}
|
||
button.disabled = true;
|
||
button.innerHTML = `
|
||
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
|
||
处理中...
|
||
`;
|
||
} else {
|
||
button.disabled = false;
|
||
button.innerHTML = button.dataset.originalText || originalText || '提交';
|
||
delete button.dataset.originalText;
|
||
}
|
||
}
|
||
|
||
// 增强的fetch函数(带加载状态和错误处理)
|
||
async function enhancedFetch(url, options = {}) {
|
||
const { showLoading: showLoadingId, button: loadingButton, ...fetchOptions } = options;
|
||
|
||
// 显示加载状态
|
||
if (showLoadingId) {
|
||
showLoading(showLoadingId);
|
||
}
|
||
|
||
// 按钮加载状态
|
||
if (loadingButton) {
|
||
setButtonLoading(loadingButton, true);
|
||
}
|
||
|
||
try {
|
||
const response = await fetch(url, fetchOptions);
|
||
const data = await response.json();
|
||
|
||
// 恢复按钮状态
|
||
if (loadingButton) {
|
||
setButtonLoading(loadingButton, false);
|
||
}
|
||
|
||
return { response, data };
|
||
} catch (error) {
|
||
// 恢复按钮状态
|
||
if (loadingButton) {
|
||
setButtonLoading(loadingButton, false);
|
||
}
|
||
|
||
showError('网络请求失败,请检查网络连接');
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// 确认对话框(使用自定义模态框)
|
||
function confirmAction(message, title = '确认操作', confirmText = '确定', cancelText = '取消') {
|
||
return new Promise((resolve) => {
|
||
// 确保自定义模态框已加载
|
||
if (typeof CustomModal === 'undefined') {
|
||
console.error('CustomModal未加载,请先引入custom-modal.js');
|
||
// 降级到原生confirm
|
||
resolve(confirm(message));
|
||
return;
|
||
}
|
||
|
||
const modal = new CustomModal({
|
||
title: title,
|
||
content: `<p style="margin: 0;">${message}</p>`,
|
||
confirmText: confirmText,
|
||
cancelText: cancelText,
|
||
onConfirm: () => {
|
||
resolve(true);
|
||
return true;
|
||
},
|
||
onClose: () => {
|
||
resolve(false);
|
||
}
|
||
});
|
||
modal.show();
|
||
});
|
||
}
|
||
|
||
// 表单验证增强
|
||
function validateForm(formId) {
|
||
const form = document.getElementById(formId);
|
||
if (!form) return false;
|
||
|
||
const requiredFields = form.querySelectorAll('[required]');
|
||
let isValid = true;
|
||
|
||
requiredFields.forEach(field => {
|
||
if (!field.value.trim()) {
|
||
field.classList.add('is-invalid');
|
||
isValid = false;
|
||
} else {
|
||
field.classList.remove('is-invalid');
|
||
field.classList.add('is-valid');
|
||
}
|
||
});
|
||
|
||
return isValid;
|
||
}
|
||
|
||
// 数字格式化
|
||
function formatNumber(num, decimals = 2) {
|
||
return parseFloat(num || 0).toFixed(decimals);
|
||
}
|
||
|
||
// 日期格式化
|
||
function formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') {
|
||
if (!date) return '-';
|
||
const d = new Date(date);
|
||
const year = d.getFullYear();
|
||
const month = String(d.getMonth() + 1).padStart(2, '0');
|
||
const day = String(d.getDate()).padStart(2, '0');
|
||
const hours = String(d.getHours()).padStart(2, '0');
|
||
const minutes = String(d.getMinutes()).padStart(2, '0');
|
||
const seconds = String(d.getSeconds()).padStart(2, '0');
|
||
|
||
return format
|
||
.replace('YYYY', year)
|
||
.replace('MM', month)
|
||
.replace('DD', day)
|
||
.replace('HH', hours)
|
||
.replace('mm', minutes)
|
||
.replace('ss', seconds);
|
||
}
|
||
|
||
// 防抖函数
|
||
function debounce(func, wait) {
|
||
let timeout;
|
||
return function executedFunction(...args) {
|
||
const later = () => {
|
||
clearTimeout(timeout);
|
||
func(...args);
|
||
};
|
||
clearTimeout(timeout);
|
||
timeout = setTimeout(later, wait);
|
||
};
|
||
}
|
||
|
||
// 节流函数
|
||
function throttle(func, limit) {
|
||
let inThrottle;
|
||
return function(...args) {
|
||
if (!inThrottle) {
|
||
func.apply(this, args);
|
||
inThrottle = true;
|
||
setTimeout(() => inThrottle = false, limit);
|
||
}
|
||
};
|
||
}
|
||
|
||
// 导出到全局
|
||
window.UIEnhancements = {
|
||
showSuccess,
|
||
showError,
|
||
showWarning,
|
||
showInfo,
|
||
showLoading,
|
||
setButtonLoading,
|
||
enhancedFetch,
|
||
confirmAction,
|
||
validateForm,
|
||
formatNumber,
|
||
formatDate,
|
||
debounce,
|
||
throttle
|
||
};
|