// ======================================== // 匯入參加人員 - Vue.js 應用程式 // 檔案位置:Scripts/SignUpImport.js // ======================================== (() => { 'use strict'; new Vue({ el: '#signUpImportApp', data: { // API apiNameClasses: 'ClassesService', apiNameSignUp: 'SignUpService', // 活動列表 courses: [], selectedCourseID: '', // 包含已結案 includeClosed: false, // 檔案上傳 selectedFile: null, isDragging: false, // 預覽資料 previewData: [], validationErrors: [], // 匯入選項 clearBefore: true, // 匯入狀態 importing: false, importResult: null }, computed: { validRowCount() { return this.previewData.filter(item => item.errors.length === 0).length; } }, mounted() { console.log('[SignUpImport] Vue app mounted'); // 初始載入活動清單 this.loadCourses(); }, methods: { /** * 載入活動清單 */ async loadCourses() { try { const response = await this.$api.call(this.apiNameClasses, 'SearchClasses', { pageIndex: 1, pageSize: 1000, courseCode: '', courseName: '', courseDate1: null, courseDate2: null, courseLocation: '', projectID: '', closed: this.includeClosed ? null : false }); if (response.Success) { const convertedData = this.$dataConvert.convertDatesInObject(response.Data || []); this.courses = convertedData; console.log('[SignUpImport] 載入活動清單成功'); } else { this.$message.error(response.Message || '載入活動清單失敗'); this.courses = []; } } catch (error) { console.error('[SignUpImport] 載入活動清單錯誤:', error); this.$message.error('載入活動清單發生錯誤'); this.courses = []; } }, /** * 檔案被選擇 */ onFileSelected(event) { const file = event.target.files[0]; if (file) { this.processFile(file); } }, /** * 檔案被拖放 */ onFileDrop(event) { this.isDragging = false; const files = event.dataTransfer.files; if (files.length > 0) { this.processFile(files[0]); } }, /** * 處理檔案 - 上傳至後端進行解析和驗證 */ async processFile(file) { // 驗證檔案類型 if (!file.name.toLowerCase().endsWith('.xlsx')) { this.$message.error('只支援 .xlsx 格式的 Excel 檔案'); return; } if (!this.selectedCourseID) { this.$message.warning('請先選擇活動'); return; } this.selectedFile = file; this.previewData = []; this.validationErrors = []; try { // 建立 FormData 物件用於上傳檔案 const formData = new FormData(); formData.append('file', file); // 呼叫後端 ProcessImportFile 方法 const response = await this.callProcessImportFile(formData); if (response.Success) { const data = response.Data; this.previewData = this.convertBackendDataToPreviewFormat(data.PreviewData); this.validationErrors = data.ValidationErrors || []; this.$message.success(`成功讀取 ${data.ValidRowCount} 列有效資料`); } else { this.$message.error('處理檔案失敗:' + response.Message); this.previewData = []; this.validationErrors = []; } } catch (error) { console.error('[SignUpImport] 處理檔案錯誤:', error); this.$message.error('處理檔案發生錯誤:' + error.message); this.previewData = []; this.validationErrors = []; } }, /** * 呼叫後端 ProcessImportFile 方法 */ async callProcessImportFile(formData) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); // 使用 FileUploadHandler.ashx 處理檔案上傳 const url = `${this.$api.baseUrl}FileUploadHandler.ashx?courseID=${this.selectedCourseID}&action=ProcessImportFile`; xhr.onload = () => { try { if (xhr.status !== 200) { reject(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`)); return; } let response = JSON.parse(xhr.responseText); resolve(response); } catch (error) { reject(new Error('解析響應失敗:' + error.message + '\n回應:' + xhr.responseText)); } }; xhr.onerror = () => reject(new Error('檔案上傳失敗')); xhr.onprogress = (event) => { if (event.lengthComputable) { // 可以在此處更新上傳進度 const percentComplete = (event.loaded / event.total) * 100; console.log(`上傳進度: ${percentComplete.toFixed(2)}%`); } }; xhr.open('POST', url); // 不要設定 Content-Type,讓瀏覽器自動設定 multipart/form-data xhr.send(formData); }); }, /** * 將後端資料轉換為前端預覽格式 */ convertBackendDataToPreviewFormat(backendData) { if (!Array.isArray(backendData)) { return []; } return backendData.map(item => ({ Name: item.Name || '', Mobile: item.Mobile || '', IDType: item.IDType || '', IDNumber: item.IDNumber || '', MealType: item.MealType || '', Email: item.Email || '', LossJobTimes: item.LossJobTimes || '', Gender: item.Gender || '', SignUpType: item.SignUpType || '', errors: Array.isArray(item.Errors) ? item.Errors : [] })); }, /** * 執行匯入 - 確認驗證無誤的資料並送交後端入庫 */ async executeImport() { if (!this.selectedCourseID) { this.$message.warning('請先選擇活動'); return; } if (this.validRowCount === 0) { this.$message.error('沒有有效的資料可匯入'); return; } // 準備要匯入的資料(只包括有效的列) const itemsToImport = this.previewData .filter(item => item.errors.length === 0) .map(item => ({ ID: 0, CourseID: this.selectedCourseID, Name: item.Name, Mobile: item.Mobile, IDType: item.IDType || '', IDNumber: item.IDNumber ? item.IDNumber.trim() : null, MealType: item.MealType || '', Email: item.Email ? item.Email.trim() : null, SignUpType: item.SignUpType || '', Gender: item.Gender || '', LossJobTimes: item.LossJobTimes || '', QRCode: null, CheckInTime: null, CheckOutTime: null, DB_APPNO: 0 })); this.importing = true; this.importResult = null; try { const response = await this.$api.call(this.apiNameSignUp, 'ConfirmImport', { courseID: this.selectedCourseID, items: itemsToImport, clearBefore: this.clearBefore }); this.importResult = { success: response.Success, message: response.Message, details: response.Data }; if (response.Success) { this.$message.success('匯入成功'); } else { this.$message.error('匯入部分失敗:' + response.Message); } } catch (error) { console.error('[SignUpImport] 匯入錯誤:', error); this.$message.error('匯入發生錯誤'); this.importResult = { success: false, message: '匯入發生錯誤:' + error.message }; } finally { this.importing = false; } }, /** * 重新選擇檔案 */ resetUpload() { this.selectedFile = null; this.previewData = []; this.validationErrors = []; this.importResult = null; this.clearBefore = true; if (this.$refs.fileInput) { this.$refs.fileInput.value = ''; } }, /** * 格式化檔案大小 */ formatFileSize(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i]; } }, watch: { includeClosed() { this.loadCourses(); } } }); })();