/** * 通用代码 * 包含工具函数和UI组件 */ // ==================== 工具函数 ==================== /** * 从URL查询参数中获取指定参数的值 * @param {string} name - 参数名称 * @returns {string|null} 参数值或null */ function getUrlParameter(name) { const urlParams = new URLSearchParams(window.location.search); return urlParams.get(name); } /** * 防抖函数 * @param {Function} func - 要防抖的函数 * @param {number} wait - 等待时间(毫秒) * @returns {Function} 防抖后的函数 */ function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } /** * 显示Toast消息 * @param {string} message - 消息内容 * @param {string} type - 消息类型 ('success', 'error', 'warning', 'info') * @param {number} duration - 显示时长(毫秒),默认3000 */ function showToast(message, type = 'info', duration = 3000) { // 创建toast容器(如果不存在) let toastContainer = document.getElementById('toast-container'); if (!toastContainer) { toastContainer = document.createElement('div'); toastContainer.id = 'toast-container'; toastContainer.className = 'fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-50 space-y-2'; document.body.appendChild(toastContainer); } // 创建toast元素 const toast = document.createElement('div'); const bgColor = { success: 'bg-green-500', error: 'bg-red-500', warning: 'bg-yellow-500', info: 'bg-blue-500' }[type] || 'bg-blue-500'; toast.className = `${bgColor} text-white px-4 py-2 rounded-lg shadow-lg transform transition-all duration-300 translate-x-full opacity-0`; toast.textContent = message; // 添加到容器 toastContainer.appendChild(toast); // 显示动画 setTimeout(() => { toast.classList.remove('translate-x-full', 'opacity-0'); }, 100); // 自动隐藏 setTimeout(() => { toast.classList.add('translate-x-full', 'opacity-0'); setTimeout(() => { if (toast.parentNode) { toast.parentNode.removeChild(toast); } }, 300); }, duration); } /** * 验证表单字段 * @param {Object} data - 表单数据 * @param {Object} rules - 验证规则 * @returns {Object} 验证结果 {isValid: boolean, errors: Object} */ function validateForm(data, rules) { const errors = {}; let isValid = true; for (const field in rules) { const value = data[field]; const rule = rules[field]; // 必填验证 if (rule.required && (!value || value.toString().trim() === '')) { errors[field] = rule.message || `${field}是必填项`; isValid = false; continue; } // 数字验证 if (rule.type === 'number' && value !== undefined && value !== '') { const num = Number(value); if (isNaN(num)) { errors[field] = `${field}必须是数字`; isValid = false; continue; } if (rule.min !== undefined && num < rule.min) { errors[field] = `${field}不能小于${rule.min}`; isValid = false; continue; } if (rule.max !== undefined && num > rule.max) { errors[field] = `${field}不能大于${rule.max}`; isValid = false; continue; } } // 字符串长度验证 if (rule.type === 'string' && value) { if (rule.minLength && value.length < rule.minLength) { errors[field] = `${field}长度不能少于${rule.minLength}个字符`; isValid = false; continue; } if (rule.maxLength && value.length > rule.maxLength) { errors[field] = `${field}长度不能超过${rule.maxLength}个字符`; isValid = false; continue; } } } return { isValid, errors }; } /** * 深拷贝对象 * @param {any} obj - 要拷贝的对象 * @returns {any} 拷贝后的对象 */ function deepClone(obj) { if (obj === null || typeof obj !== 'object') { return obj; } if (obj instanceof Date) { return new Date(obj.getTime()); } if (obj instanceof Array) { return obj.map(item => deepClone(item)); } if (typeof obj === 'object') { const cloned = {}; for (const key in obj) { if (obj.hasOwnProperty(key)) { cloned[key] = deepClone(obj[key]); } } return cloned; } return obj; } // ==================== UI组件 ==================== /** * 顶部导航栏组件 */ class HeaderNav { constructor() { this.element = document.getElementById('header-nav'); this.backBtn = document.getElementById('back-btn'); this.init(); } /** * 初始化导航栏事件 */ init() { this.backBtn.addEventListener('click', () => { // 返回上一页或关闭页面 if (window.history.length > 1) { window.history.back(); } else { window.close(); } }); } } /** * 工单选择器组件 */ class WorkOrderSelector { constructor(onSelect) { this.element = document.getElementById('work-order-selector'); this.selectedText = document.getElementById('selected-work-order-text'); this.dropdownArrow = document.getElementById('dropdown-arrow'); this.scanBtn = document.getElementById('scan-btn'); this.onSelect = onSelect; this.selectedWorkOrder = null; this.init(); } /** * 初始化选择器事件 */ init() { // 点击选择器展开下拉菜单 this.element.addEventListener('click', (e) => { // 如果点击的是扫码按钮,不展开下拉菜单 if (e.target.closest('#scan-btn')) { return; } this.onSelect(); }); // 扫码按钮事件 this.scanBtn.addEventListener('click', (e) => { e.stopPropagation(); this.handleScan(); }); } /** * 处理扫码功能 */ handleScan() { showToast('扫码功能开发中...', 'info'); // TODO: 实现扫码功能 } /** * 更新选中的工单显示 * @param {Object} workOrder - 工单对象 */ updateSelected(workOrder) { this.selectedWorkOrder = workOrder; if (workOrder) { this.selectedText.textContent = workOrder.id; this.selectedText.className = 'text-gray-800 ml-2 font-medium'; } else { this.selectedText.textContent = '请选择'; this.selectedText.className = 'text-gray-500 ml-2'; } } /** * 切换下拉箭头状态 * @param {boolean} expanded - 是否展开 */ toggleArrow(expanded) { if (expanded) { this.dropdownArrow.classList.add('rotate'); } else { this.dropdownArrow.classList.remove('rotate'); } } } /** * 工单下拉菜单组件 */ class WorkOrderDropdown { constructor(onConfirm, onClose) { this.element = document.getElementById('work-order-dropdown'); this.closeBtn = document.getElementById('close-dropdown'); this.searchInput = document.getElementById('search-input'); this.workOrderList = document.getElementById('work-order-list'); this.selectAllCheckbox = document.getElementById('select-all'); this.selectedCount = document.getElementById('selected-count'); this.confirmBtn = document.getElementById('confirm-selection'); this.onConfirm = onConfirm; this.onClose = onClose; this.workOrders = []; this.filteredWorkOrders = []; this.selectedWorkOrders = new Set(); this.init(); } /** * 初始化下拉菜单事件 */ init() { // 关闭按钮事件 this.closeBtn.addEventListener('click', () => { this.hide(); }); // 点击背景关闭 this.element.addEventListener('click', (e) => { if (e.target === this.element) { this.hide(); } }); // 搜索功能 this.searchInput.addEventListener('input', debounce((e) => { this.filterWorkOrders(e.target.value); }, 300)); // 全选功能 this.selectAllCheckbox.addEventListener('change', (e) => { this.toggleSelectAll(e.target.checked); }); // 确定按钮事件 this.confirmBtn.addEventListener('click', () => { this.confirmSelection(); }); // ESC键关闭 document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && this.isVisible()) { this.hide(); } }); } /** * 显示下拉菜单 */ show() { this.element.classList.remove('hidden'); this.element.classList.add('show'); document.body.style.overflow = 'hidden'; // 聚焦搜索框 setTimeout(() => { this.searchInput.focus(); }, 300); } /** * 隐藏下拉菜单 */ hide() { this.element.classList.remove('show'); document.body.style.overflow = ''; setTimeout(() => { this.element.classList.add('hidden'); }, 300); this.onClose(); } /** * 检查是否可见 * @returns {boolean} */ isVisible() { return !this.element.classList.contains('hidden'); } /** * 设置工单数据 * @param {Array} workOrders - 工单数组 */ setWorkOrders(workOrders) { this.workOrders = workOrders; this.filteredWorkOrders = [...workOrders]; this.renderWorkOrderList(); } /** * 过滤工单 * @param {string} searchTerm - 搜索关键词 */ filterWorkOrders(searchTerm) { const term = searchTerm.toLowerCase().trim(); if (!term) { this.filteredWorkOrders = [...this.workOrders]; } else { this.filteredWorkOrders = this.workOrders.filter(wo => wo.id.toLowerCase().includes(term) || (wo.name && wo.name.toLowerCase().includes(term)) ); } this.renderWorkOrderList(); } /** * 渲染工单列表 */ renderWorkOrderList() { this.workOrderList.innerHTML = ''; if (this.filteredWorkOrders.length === 0) { const emptyDiv = document.createElement('div'); emptyDiv.className = 'text-center py-8 text-gray-500'; emptyDiv.textContent = '没有找到匹配的工单'; this.workOrderList.appendChild(emptyDiv); return; } this.filteredWorkOrders.forEach(workOrder => { const item = this.createWorkOrderItem(workOrder); this.workOrderList.appendChild(item); }); this.updateSelectAllState(); } /** * 创建工单列表项 * @param {Object} workOrder - 工单对象 * @returns {HTMLElement} */ createWorkOrderItem(workOrder) { const item = document.createElement('div'); item.className = 'work-order-item flex items-center space-x-3'; item.dataset.workOrderId = workOrder.id; const isSelected = this.selectedWorkOrders.has(workOrder.id); if (isSelected) { item.classList.add('selected'); } item.innerHTML = ` ${workOrder.id} `; // 点击事件 item.addEventListener('click', () => { this.selectWorkOrder(workOrder.id); }); return item; } /** * 选择工单 * @param {string} workOrderId - 工单ID */ selectWorkOrder(workOrderId) { // 单选模式:清除之前的选择 this.selectedWorkOrders.clear(); this.selectedWorkOrders.add(workOrderId); // 更新UI this.workOrderList.querySelectorAll('.work-order-item').forEach(item => { const radio = item.querySelector('input[type="radio"]'); const isSelected = item.dataset.workOrderId === workOrderId; item.classList.toggle('selected', isSelected); radio.checked = isSelected; }); this.updateSelectedCount(); this.updateConfirmButton(); } /** * 切换全选状态 * @param {boolean} selectAll - 是否全选 */ toggleSelectAll(selectAll) { if (selectAll) { this.filteredWorkOrders.forEach(wo => { this.selectedWorkOrders.add(wo.id); }); } else { this.selectedWorkOrders.clear(); } this.renderWorkOrderList(); this.updateSelectedCount(); this.updateConfirmButton(); } /** * 更新全选状态 */ updateSelectAllState() { const visibleCount = this.filteredWorkOrders.length; const selectedVisibleCount = this.filteredWorkOrders.filter(wo => this.selectedWorkOrders.has(wo.id) ).length; this.selectAllCheckbox.checked = visibleCount > 0 && selectedVisibleCount === visibleCount; this.selectAllCheckbox.indeterminate = selectedVisibleCount > 0 && selectedVisibleCount < visibleCount; } /** * 更新选中数量显示 */ updateSelectedCount() { this.selectedCount.textContent = `已选择${this.selectedWorkOrders.size}个`; } /** * 更新确定按钮状态 */ updateConfirmButton() { this.confirmBtn.disabled = this.selectedWorkOrders.size === 0; } /** * 确认选择 */ confirmSelection() { if (this.selectedWorkOrders.size === 0) { showToast('请选择工单', 'warning'); return; } const selectedIds = Array.from(this.selectedWorkOrders); const selectedWorkOrder = this.workOrders.find(wo => wo.id === selectedIds[0]); this.onConfirm(selectedWorkOrder); this.hide(); } /** * 重置选择状态 */ reset() { this.selectedWorkOrders.clear(); this.searchInput.value = ''; this.filteredWorkOrders = [...this.workOrders]; this.renderWorkOrderList(); this.updateSelectedCount(); this.updateConfirmButton(); } } /** * 物料信息组件 */ class MaterialInfo { constructor() { this.element = document.getElementById('material-info'); this.workOrderId = document.getElementById('selected-work-order-id'); this.materialName = document.getElementById('material-name'); this.materialCode = document.getElementById('material-code'); this.qualifiedCount = document.getElementById('qualified-count'); this.defectiveCount = document.getElementById('defective-count'); this.pendingCount = document.getElementById('pending-count'); } /** * 显示物料信息 * @param {Object} data - 物料数据 */ show(data) { this.workOrderId.textContent = data.workOrderId; this.materialName.textContent = data.materialName; this.materialCode.textContent = data.materialCode; this.qualifiedCount.textContent = data.qualifiedCount || 0; this.defectiveCount.textContent = data.defectiveCount || 0; this.pendingCount.textContent = data.pendingCount || 0; this.element.classList.remove('hidden'); } /** * 隐藏物料信息 */ hide() { this.element.classList.add('hidden'); } } /** * 报工表单组件 */ class ReportForm { constructor() { this.element = document.getElementById('report-form'); this.quantityInput = document.getElementById('quantity'); this.auxiliaryQuantityInput = document.getElementById('auxiliary-quantity'); this.remarkInput = document.getElementById('remark'); this.workingHoursInput = document.getElementById('working-hours'); this.reportStartTimeInput = document.getElementById('report-start-time'); this.reportEndTimeInput = document.getElementById('report-end-time'); this.auxName = document.getElementById('auxName'); this.init(); } /** * 初始化表单事件 */ init() { // 数字输入验证 [this.quantityInput, this.auxiliaryQuantityInput].forEach(input => { if (input) { input.addEventListener('input', (e) => { this.validateNumberInput(e.target); }); } }); // 表单字段变化时的验证 this.getAllInputs().forEach(input => { if (input) { input.addEventListener('blur', () => { this.validateField(input); }); } }); } /** * 验证数字输入 * @param {HTMLInputElement} input - 输入元素 */ validateNumberInput(input) { if (!input) return; const value = parseFloat(input.value); if (isNaN(value) || value < 0) { input.classList.add('border-red-500'); this.showFieldError(input, '请输入有效的数字'); } else { input.classList.remove('border-red-500'); this.hideFieldError(input); } } /** * 验证单个字段 * @param {HTMLInputElement} input - 输入元素 */ validateField(input) { if (!input) return false; const value = input.value.trim(); const isRequired = input.previousElementSibling?.querySelector('.text-red-500'); if (isRequired && !value) { input.classList.add('border-red-500'); this.showFieldError(input, '此字段为必填项'); return false; } else { input.classList.remove('border-red-500'); this.hideFieldError(input); return true; } } /** * 显示字段错误 * @param {HTMLInputElement} input - 输入元素 * @param {string} message - 错误消息 */ showFieldError(input, message) { let errorElement = input.parentNode.querySelector('.field-error'); if (!errorElement) { errorElement = document.createElement('div'); // errorElement.className = 'field-error text-red-500 text-sm mt-1'; errorElement.className = 'field-error text-red-500 text-sm mt-1 w-full'; input.parentNode.appendChild(errorElement); } errorElement.textContent = message; } /** * 隐藏字段错误 * @param {HTMLInputElement} input - 输入元素 */ hideFieldError(input) { const errorElement = input.parentNode.querySelector('.field-error'); if (errorElement) { errorElement.remove(); } } /** * 获取所有输入元素 * @returns {HTMLInputElement[]} */ getAllInputs() { return [ this.quantityInput, this.auxiliaryQuantityInput, this.remarkInput, this.workingHoursInput, this.reportStartTimeInput, this.reportEndTimeInput, ].filter(input => input !== null && input !== undefined); } /** * 获取表单数据 * @returns {Object} */ getData() { return { quantity: parseFloat(this.quantityInput?.value) || 0, auxiliaryQuantity: parseFloat(this.auxiliaryQuantityInput?.value) || 0, remark: this.remarkInput?.value?.trim() || '', workingHours: parseFloat(this.workingHoursInput?.value) || 0, reportStartTime: this.reportStartTimeInput?.value?.trim() || '', reportEndTimeInput: this.reportEndTimeInput?.value?.trim() || '', }; } /** * 设置表单数据 * @param {Object} data - 表单数据 */ setData(data) { if (this.quantityInput) { this.quantityInput.value = data.quantity || ''; } if (this.auxiliaryQuantityInput) { this.auxiliaryQuantityInput.value = data.auxiliaryQuantity || ''; } } setAuxName(data) { if (this.auxName) { this.auxName.innerText = data.auxName || '公斤'; } } /** * 验证整个表单 * @returns {Object} 验证结果 */ validate() { const data = this.getData(); const rules = { quantity: { required: true, type: 'number', min: 0 }, auxiliaryQuantity: { required: true, type: 'number', min: 0 } }; return validateForm(data, rules); } /** * 显示表单 */ show() { this.element.classList.remove('hidden'); } /** * 隐藏表单 */ hide() { this.element.classList.add('hidden'); } /** * 重置表单 */ reset() { this.getAllInputs().forEach(input => { input.value = ''; input.classList.remove('border-red-500'); this.hideFieldError(input); }); } } const GlobalLoading = { element: null, init() { if (this.element) return; this.element = document.createElement('div'); this.element.id = 'global-loading'; this.element.innerHTML = `
加载中...
`; // 添加样式 const style = document.createElement('style'); style.textContent = ` #global-loading { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 9999; display: none; } .loading-backdrop { position: absolute; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); } .loading-spinner { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; } .spinner { width: 40px; height: 40px; border: 4px solid #f3f3f3; border-top: 4px solid #007bff; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto 10px; } .loading-text { color: white; font-size: 14px; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } body.loading-active { overflow: hidden; } `; document.head.appendChild(style); document.body.appendChild(this.element); }, show(text = '加载中...') { this.init(); const textElement = this.element.querySelector('.loading-text'); if (textElement) { textElement.textContent = text; } this.element.style.display = 'block'; document.body.classList.add('loading-active'); }, hide() { if (this.element) { this.element.style.display = 'none'; document.body.classList.remove('loading-active'); } } };