| | |
| | | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification |
| | | // for details on configuring this project to bundle and minify static web assets. |
| | | |
| | | // Write your JavaScript code. |
| | | // API Base URL |
| | | const API_BASE_URL = '/api'; |
| | | |
| | | // Show toast notification |
| | | function showToast(message, type = 'info') { |
| | | const toastContainer = document.getElementById('toastContainer') || createToastContainer(); |
| | | const toastId = 'toast-' + Date.now(); |
| | | |
| | | const bgClass = { |
| | | 'success': 'bg-success', |
| | | 'danger': 'bg-danger', |
| | | 'warning': 'bg-warning', |
| | | 'info': 'bg-info', |
| | | 'error': 'bg-danger' |
| | | }[type] || 'bg-info'; |
| | | |
| | | const toastHtml = ` |
| | | <div id="${toastId}" class="toast align-items-center text-white ${bgClass} border-0" role="alert" aria-live="assertive" aria-atomic="true"> |
| | | <div class="d-flex"> |
| | | <div class="toast-body"> |
| | | ${message} |
| | | </div> |
| | | <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button> |
| | | </div> |
| | | </div> |
| | | `; |
| | | |
| | | toastContainer.insertAdjacentHTML('beforeend', toastHtml); |
| | | const toastElement = document.getElementById(toastId); |
| | | const toast = new bootstrap.Toast(toastElement, { delay: 3000 }); |
| | | toast.show(); |
| | | |
| | | toastElement.addEventListener('hidden.bs.toast', () => { |
| | | toastElement.remove(); |
| | | }); |
| | | } |
| | | |
| | | function createToastContainer() { |
| | | const container = document.createElement('div'); |
| | | container.id = 'toastContainer'; |
| | | container.className = 'toast-container'; |
| | | document.body.appendChild(container); |
| | | return container; |
| | | } |
| | | |
| | | // Show loading spinner |
| | | function showLoading() { |
| | | const existingOverlay = document.getElementById('loadingOverlay'); |
| | | if (existingOverlay) return; |
| | | |
| | | const overlay = document.createElement('div'); |
| | | overlay.id = 'loadingOverlay'; |
| | | overlay.className = 'spinner-overlay'; |
| | | overlay.innerHTML = ` |
| | | <div class="spinner-border text-light" style="width: 3rem; height: 3rem;" role="status"> |
| | | <span class="visually-hidden">Loading...</span> |
| | | </div> |
| | | `; |
| | | document.body.appendChild(overlay); |
| | | } |
| | | |
| | | // Hide loading spinner |
| | | function hideLoading() { |
| | | const overlay = document.getElementById('loadingOverlay'); |
| | | if (overlay) { |
| | | overlay.remove(); |
| | | } |
| | | } |
| | | |
| | | // API call helper |
| | | async function apiCall(url, options = {}) { |
| | | try { |
| | | showLoading(); |
| | | const response = await fetch(url, { |
| | | ...options, |
| | | headers: { |
| | | 'Content-Type': 'application/json', |
| | | ...options.headers |
| | | } |
| | | }); |
| | | |
| | | if (!response.ok) { |
| | | const errorData = await response.json().catch(() => ({ error: response.statusText })); |
| | | throw new Error(errorData.error || `HTTP ${response.status}: ${response.statusText}`); |
| | | } |
| | | |
| | | return await response.json(); |
| | | } catch (error) { |
| | | showToast(error.message, 'error'); |
| | | throw error; |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | // Format date |
| | | function formatDate(dateString) { |
| | | if (!dateString) return '-'; |
| | | const date = new Date(dateString); |
| | | return date.toLocaleString('zh-CN', { |
| | | year: 'numeric', |
| | | month: '2-digit', |
| | | day: '2-digit', |
| | | hour: '2-digit', |
| | | minute: '2-digit', |
| | | second: '2-digit' |
| | | }); |
| | | } |
| | | |
| | | // Get status badge class |
| | | function getStatusBadgeClass(status) { |
| | | const statusMap = { |
| | | 'Stopped': 'status-stopped', |
| | | 'Starting': 'status-starting', |
| | | 'Running': 'status-running', |
| | | 'Stopping': 'status-stopping', |
| | | 'Error': 'status-error' |
| | | }; |
| | | return statusMap[status] || 'bg-secondary'; |
| | | } |
| | | |
| | | // Get status text |
| | | function getStatusText(status) { |
| | | const statusMap = { |
| | | 'Stopped': '已停止', |
| | | 'Starting': '启动中', |
| | | 'Running': '运行中', |
| | | 'Stopping': '停止中', |
| | | 'Error': '错误' |
| | | }; |
| | | return statusMap[status] || status; |
| | | } |
| | | |
| | | // Get PLC type text |
| | | function getPlcTypeText(plcType) { |
| | | const plcTypeMap = { |
| | | 'S7200Smart': 'S7-200 Smart', |
| | | 'S71200': 'S7-1200', |
| | | 'S71500': 'S7-1500', |
| | | 'S7300': 'S7-300', |
| | | 'S7400': 'S7-400' |
| | | }; |
| | | return plcTypeMap[plcType] || plcType; |
| | | } |
| | | |
| | | // Confirm action |
| | | function confirmAction(message, callback) { |
| | | if (confirm(message)) { |
| | | callback(); |
| | | } |
| | | } |
| | | |
| | | // Redirect with delay |
| | | function redirectWithDelay(url, delay = 1500) { |
| | | setTimeout(() => { |
| | | window.location.href = url; |
| | | }, delay); |
| | | } |
| | | |
| | | // Format bytes |
| | | function formatBytes(bytes, decimals = 2) { |
| | | if (bytes === 0) return '0 Bytes'; |
| | | const k = 1024; |
| | | const dm = decimals < 0 ? 0 : decimals; |
| | | const sizes = ['Bytes', 'KB', 'MB', 'GB']; |
| | | const i = Math.floor(Math.log(bytes) / Math.log(k)); |
| | | return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; |
| | | } |
| | | |
| | | // Auto-refresh interval handler |
| | | let refreshInterval = null; |
| | | |
| | | function startAutoRefresh(callback, intervalMs = 5000) { |
| | | stopAutoRefresh(); |
| | | callback(); // Initial call |
| | | refreshInterval = setInterval(callback, intervalMs); |
| | | } |
| | | |
| | | function stopAutoRefresh() { |
| | | if (refreshInterval) { |
| | | clearInterval(refreshInterval); |
| | | refreshInterval = null; |
| | | } |
| | | } |
| | | |
| | | // Page unload handler |
| | | window.addEventListener('beforeunload', () => { |
| | | stopAutoRefresh(); |
| | | }); |