chore: 首次簽入 Thinkyu ASP.NET 專案

- 加入 Visual Studio / ASP.NET .gitignore
- 排除建置輸出、IDE 設定、NuGet packages、大型 MSI 安裝檔
This commit is contained in:
2026-09-10 09:42:37 +08:00
commit 577060bc78
2496 changed files with 501389 additions and 0 deletions
+619
View File
@@ -0,0 +1,619 @@
// ========================================
// 活動參加人員 - 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();
}
}
});
})();