chore: 首次簽入 Thinkyu ASP.NET 專案
- 加入 Visual Studio / ASP.NET .gitignore - 排除建置輸出、IDE 設定、NuGet packages、大型 MSI 安裝檔
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
// ========================================
|
||||
// 匯入參加人員 - 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();
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user