Files
thinkyu/報到系統/Scripts/SignUpList.js
T
sryang 577060bc78 chore: 首次簽入 Thinkyu ASP.NET 專案
- 加入 Visual Studio / ASP.NET .gitignore
- 排除建置輸出、IDE 設定、NuGet packages、大型 MSI 安裝檔
2026-09-10 09:42:37 +08:00

620 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ========================================
// 活動參加人員 - Vue.js 應用程式
// 檔案位置:Scripts/SignUpList.js
// ========================================
(() => {
'use strict';
new Vue({
el: '#signUpApp',
data: {
// API
apiNameClasses: 'ClassesService',
apiNameSignUp: 'SignUpService',
// 活動列表
courses: [],
selectedCourseID: '',
// 搜尋條件
search: {
seqNo: null,
name: '',
mobile: '',
idType: '',
idNumber: '',
idNumberEmpty: false,
mealType: '',
email: '',
emailEmpty: false
},
// 包含已結案
includeClosed: false,
// 搜尋結果
signups: [],
loading: false,
// 分頁
currentPage: 1,
pageSize: 20,
totalCount: 0,
// 勾選功能
selectedIds: [], // 已選人員 ID 清單(跨分頁保留)
selectAllCurrentPage: false, // 當前頁全選狀態
// 簡訊發送
sendingSmsBatch: false, // 發送中狀態
// 彈窗
showModal: false,
isNewMode: true,
editItem: {
ID: 0,
CourseID: 0,
SeqNo: 0,
Name: '',
Mobile: '',
SignUpType: '',
CheckInTime: '',
CheckOutTime: '',
IDType: '',
IDNumber: '',
MealType: '',
Email: '',
Gender: '',
LossJobTimes: 0,
DB_APPNO: 0
},
// 驗證錯誤訊息
validationErrors: {
Name: '',
Mobile: '',
IDNumber: '',
Email: '',
LossJobTimes: ''
}
},
computed: {
totalPages() {
return Math.ceil(this.totalCount / this.pageSize);
},
hasIndeterminate() {
// 當前頁部分已選且不是全選時顯示 indeterminate 狀態
const currentPageSelectedCount = this.signups.filter(item =>
this.selectedIds.includes(item.ID)
).length;
return currentPageSelectedCount > 0 && currentPageSelectedCount < this.signups.length;
}
},
mounted() {
console.log('[SignUpList] 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('[SignUpList] 載入活動清單成功,共 ' + this.courses.length + ' 筆');
} else {
this.$message.error(response.Message || '載入活動清單失敗');
this.courses = [];
}
} catch (error) {
console.error('[SignUpList] 載入活動清單錯誤:', error);
this.$message.error('載入活動清單發生錯誤');
this.courses = [];
}
},
/**
* 活動選擇改變
*/
onCourseSelected() {
this.clearSearch();
this.clearSmsSelection(); // 活動改變時清除已選
if (this.selectedCourseID) {
this.currentPage = 1;
this.loadSignUps();
}
},
/**
* 執行搜尋
*/
async performSearch() {
if (!this.selectedCourseID) {
this.$message.warning('請先選擇活動');
return;
}
this.currentPage = 1;
await this.loadSignUps();
},
/**
* 載入參加人員資料
*/
async loadSignUps() {
if (!this.selectedCourseID) return;
this.loading = true;
try {
const response = await this.$api.call(this.apiNameSignUp, 'SearchSignUpsAdvanced', {
pageIndex: this.currentPage,
pageSize: this.pageSize,
courseID: this.selectedCourseID,
seqNo: this.search.seqNo,
name: this.search.name,
mobile: this.search.mobile,
idType: this.search.idType,
idNumber: this.search.idNumber,
idNumberEmpty: this.search.idNumberEmpty,
mealType: this.search.mealType,
email: this.search.email,
emailEmpty: this.search.emailEmpty
});
if (response.Success) {
const convertedData = this.$dataConvert.convertDatesInObject(response.Data || []);
this.signups = convertedData;
this.totalCount = response.TotalCount || 0;
console.log('[SignUpList] 載入成功,共 ' + this.totalCount + ' 筆');
console.log('[SignUpList] ', convertedData);
// 更新當前頁全選狀態
this.updateSelectAllState();
} else {
this.$message.error(response.Message || '查詢失敗');
this.signups = [];
this.totalCount = 0;
}
} catch (error) {
console.error('[SignUpList] 載入錯誤:', error);
this.$message.error('載入資料發生錯誤');
this.signups = [];
this.totalCount = 0;
} finally {
this.loading = false;
}
},
/**
* 清除搜尋條件
*/
clearSearch() {
this.search = {
seqNo: null,
name: '',
mobile: '',
idType: '',
idNumber: '',
idNumberEmpty: false,
mealType: '',
email: '',
emailEmpty: false
};
this.includeClosed = false;
this.clearSmsSelection();
},
/**
* 換頁
*/
goToPage(page) {
if (page < 1 || page > this.totalPages) return;
this.currentPage = page;
this.loadSignUps();
},
/**
* 前往第一頁
*/
goToFirstPage() {
this.currentPage = 1;
this.loadSignUps();
},
/**
* 前往最後一頁
*/
goToLastPage() {
this.currentPage = this.totalPages;
this.loadSignUps();
},
/**
* 分頁大小改變
*/
onPageSizeChanged() {
this.currentPage = 1;
this.loadSignUps();
},
/**
* 開啟新增彈窗
*/
openAddModal() {
this.isNewMode = true;
this.editItem = {
ID: 0,
CourseID: this.selectedCourseID,
SeqNo: 0,
Name: '',
Mobile: '',
SignUpType: '現場',
CheckInTime: '',
CheckOutTime: '',
IDType: '',
IDNumber: '',
MealType: '',
Email: '',
Gender: '',
LossJobTimes: 0,
DB_APPNO: 0
};
this.clearValidationErrors();
this.showModal = true;
},
/**
* 開啟更正彈窗
*/
openEditModal(item) {
this.isNewMode = false;
this.editItem = {
ID: item.ID,
CourseID: item.CourseID,
SeqNo: item.SeqNo,
Name: item.Name,
Mobile: item.Mobile,
SignUpType: item.SignUpType || '',
CheckInTime: item.CheckInTime,
CheckOutTime: item.CheckOutTime,
IDType: item.IDType || '',
IDNumber: item.IDNumber || '',
MealType: item.MealType || '',
Email: item.Email || '',
Gender: item.Gender || '',
LossJobTimes: item.LossJobTimes || 0,
DB_APPNO: item.DB_APPNO
};
this.clearValidationErrors();
this.showModal = true;
},
/**
* 關閉彈窗
*/
closeModal() {
this.showModal = false;
},
/**
* 清除驗證錯誤訊息
*/
clearValidationErrors() {
this.validationErrors = {
Name: '',
Mobile: '',
IDNumber: '',
Email: '',
LossJobTimes: ''
};
},
/**
* 驗證電子郵件格式
*/
validateEmail(email) {
if (!email || email.trim() === '') {
return true; // 電子郵件非必填,空值視為有效
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email.trim());
},
/**
* 驗證身分證字號格式和檢查碼
* 台灣身分證字號格式:首位為大寫英文字母,後跟9位數字
* 檢查碼算法:
* 1. 首字母轉換為數字(A=10, B=11, ...Z=35
* 2. 首字母對應的十位數字乘以1,個位數字乘以9
* 3. 後續8位數字分別乘以8,7,6,5,4,3,2
* 4. 最後一位為檢查碼,乘以1
* 5. 所有乘積相加,模10後的結果應該為0
*/
validateIDNumber(idNumber) {
if (!idNumber || idNumber.trim() === '') {
return true; // 身分證字號非必填,空值視為有效
}
idNumber = idNumber.trim().toUpperCase();
// 基本格式驗證:首位為大寫英文字母,後跟9位數字
const idRegex = /^[A-Z]\d{9}$/;
if (!idRegex.test(idNumber)) {
return false;
}
// 檢查碼驗證
const letterToNumber = {
'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14,
'F': 15, 'G': 16, 'H': 17, 'I': 34, 'J': 18,
'K': 19, 'L': 20, 'M': 21, 'N': 22, 'O': 35,
'P': 23, 'Q': 24, 'R': 25, 'S': 26, 'T': 27,
'U': 28, 'V': 29, 'W': 32, 'X': 30, 'Y': 31,
'Z': 33
};
const firstLetter = idNumber[0];
const firstLetterNum = letterToNumber[firstLetter];
// 將首字母對應的數字分成十位和個位
const letterTens = Math.floor(firstLetterNum / 10);
const letterOnes = firstLetterNum % 10;
// 計算檢查碼
let sum = letterTens * 1 + letterOnes * 9;
// 第2-9位數字分別乘以8,7,6,5,4,3,2
const weights = [8, 7, 6, 5, 4, 3, 2, 1];
for (let i = 0; i < 8; i++) {
sum += parseInt(idNumber[i + 1]) * weights[i];
}
// 第10位為檢查碼,乘以1
sum += parseInt(idNumber[9]) * 1;
// 檢查碼驗證:總和模10應該為0
return (sum % 10) === 0;
},
/**
* 驗證輸入
*/
validateInput() {
this.clearValidationErrors();
let isValid = true;
// 驗證姓名
if (!this.editItem.Name || this.editItem.Name.trim() === '') {
this.validationErrors.Name = '姓名為必填欄位';
isValid = false;
} else if (this.editItem.Name.length > 100) {
this.validationErrors.Name = '姓名最長 100 個字元';
isValid = false;
}
// 驗證手機號
if (!this.editItem.Mobile || this.editItem.Mobile.trim() === '') {
this.validationErrors.Mobile = '手機號為必填欄位';
isValid = false;
} else if (this.editItem.Mobile.length > 20) {
this.validationErrors.Mobile = '手機號最長 20 個字元';
isValid = false;
}
// 驗證身分證字號(如果有填寫)
if (this.editItem.IDNumber && this.editItem.IDNumber.trim() !== '') {
if (!this.validateIDNumber(this.editItem.IDNumber)) {
this.validationErrors.IDNumber = '身分證字號無效(格式應為:大寫英文字母 + 9位數字,且檢查碼需正確,例如:A123456789';
isValid = false;
}
}
// 驗證電子郵件(如果有填寫)
if (this.editItem.Email && this.editItem.Email.trim() !== '') {
if (!this.validateEmail(this.editItem.Email)) {
this.validationErrors.Email = '電子郵件格式不正確(例如:user@example.com';
isValid = false;
}
}
// 驗證失業給付認定次數(如果有填寫)
if (this.editItem.LossJobTimes !== null && this.editItem.LossJobTimes !== '' && this.editItem.LossJobTimes !== undefined) {
if (isNaN(this.editItem.LossJobTimes) || this.editItem.LossJobTimes < 0) {
this.validationErrors.LossJobTimes = '失業給付認定次數必須為非負整數';
isValid = false;
}
}
return isValid;
},
/**
* 儲存參加人員
*/
async saveSignUp() {
if (!this.validateInput()) return;
try {
// 準備資料
const item = {
ID: this.editItem.ID,
CourseID: this.editItem.CourseID,
SeqNo: this.editItem.SeqNo,
Name: this.editItem.Name.trim(),
Mobile: this.editItem.Mobile.trim(),
SignUpType: this.editItem.SignUpType,
CheckInTime: this.editItem.CheckInTime,
CheckOutTime: this.editItem.CheckOutTime,
IDType: this.editItem.IDType,
IDNumber: this.editItem.IDNumber ? this.editItem.IDNumber.trim() : null,
MealType: this.editItem.MealType,
Email: this.editItem.Email ? this.editItem.Email.trim() : null,
Gender: this.editItem.Gender,
LossJobTimes: this.editItem.LossJobTimes !== null && this.editItem.LossJobTimes !== '' ? this.editItem.LossJobTimes : null,
DB_APPNO: this.editItem.DB_APPNO
};
console.debug('[saveSignUp]', item);
const response = await this.$api.call(this.apiNameSignUp, 'SaveSignUp', {
isNew: this.isNewMode,
item: item
});
if (response.Success) {
this.$message.success(response.Message || '儲存成功');
this.closeModal();
this.loadSignUps();
} else {
this.$message.error(response.Message || '儲存失敗');
}
} catch (error) {
console.error('[SignUpList] 儲存錯誤:', error);
this.$message.error('儲存發生錯誤');
}
},
/**
* 刪除參加人員
*/
async deleteSignUp(item) {
if (!confirm(`確定要刪除「${item.Name}」的報名資料嗎?`)) {
return;
}
try {
const response = await this.$api.call(this.apiNameSignUp, 'DeleteSignUp', {
id: item.ID,
dbAppNo: item.DB_APPNO
});
if (response.Success) {
this.$message.success(response.Message || '刪除成功');
// 從已選清單中移除
const index = this.selectedIds.indexOf(item.ID);
if (index > -1) {
this.selectedIds.splice(index, 1);
}
this.loadSignUps();
} else {
this.$message.error(response.Message || '刪除失敗');
}
} catch (error) {
console.error('[SignUpList] 刪除錯誤:', error);
this.$message.error('刪除發生錯誤');
}
},
/**
* 全選/反選當前頁
*/
toggleSelectAll() {
if (this.selectAllCurrentPage) {
// 全選當前頁
this.signups.forEach(item => {
if (!this.selectedIds.includes(item.ID)) {
this.selectedIds.push(item.ID);
}
});
} else {
// 反選當前頁
this.signups.forEach(item => {
const index = this.selectedIds.indexOf(item.ID);
if (index > -1) {
this.selectedIds.splice(index, 1);
}
});
}
},
/**
* 更新全選狀態 - 根據當前頁已選情況
*/
updateSelectAllState() {
const currentPageSelectedCount = this.signups.filter(item =>
this.selectedIds.includes(item.ID)
).length;
this.selectAllCurrentPage = currentPageSelectedCount === this.signups.length && this.signups.length > 0;
},
/**
* 清除已選人員
*/
clearSmsSelection() {
this.selectedIds = [];
this.selectAllCurrentPage = false;
},
/**
* 批發簡訊
*/
async sendSmsBatch() {
if (this.selectedIds.length === 0) {
this.$message.warning('請先勾選要發送的人員');
return;
}
if (!confirm(`確定要向 ${this.selectedIds.length} 位人員發送簡訊嗎?`)) {
return;
}
this.sendingSmsBatch = true;
try {
const response = await this.$api.call(this.apiNameSignUp, 'SendSmsBatch', {
courseID: this.selectedCourseID,
signupIds: this.selectedIds
});
if (response.Success) {
this.$message.success(response.Message || '簡訊發送成功');
this.clearSmsSelection();
} else {
this.$message.error(response.Message || '簡訊發送失敗');
}
} catch (error) {
console.error('[SignUpList] 發送簡訊錯誤:', error);
this.$message.error('發送簡訊發生錯誤');
} finally {
this.sendingSmsBatch = false;
}
},
/**
* 查詢餘額
*/
async checkBalance() {
try {
const response = await this.$api.call(this.apiNameSignUp, 'CheckBalance');
if (response.Success) {
this.$message.success(response.Message || `餘額尚有 ${response.Data.Balance} 點`);
} else {
this.$message.error(response.Message || '餘額查詢失敗');
}
} catch (error) {
console.error('[SignUpList] 查詢餘額錯誤:', error);
this.$message.error('查詢餘額發生錯誤');
} finally {
this.sendingSmsBatch = false;
}
}
},
watch: {
includeClosed() {
this.loadCourses();
}
}
});
})();