chore: 首次簽入 Thinkyu ASP.NET 專案
- 加入 Visual Studio / ASP.NET .gitignore - 排除建置輸出、IDE 設定、NuGet packages、大型 MSI 安裝檔
This commit is contained in:
@@ -0,0 +1,650 @@
|
||||
// ========================================
|
||||
// 活動管理 - Vue.js 應用程式
|
||||
// 檔案位置:Scripts/ClassesList.js
|
||||
// ========================================
|
||||
|
||||
(() => {
|
||||
'use strict';
|
||||
|
||||
new Vue({
|
||||
el: '#classesApp',
|
||||
data: {
|
||||
// API
|
||||
apiName: 'ClassesService',
|
||||
// 搜尋條件
|
||||
search: {
|
||||
courseCode: '',
|
||||
courseName: '',
|
||||
courseDate1: '',
|
||||
courseDate2: '',
|
||||
courseLocation: '',
|
||||
projectID: '',
|
||||
projectName: '',
|
||||
closed: ''
|
||||
},
|
||||
// 搜尋結果
|
||||
classes: [],
|
||||
loading: false,
|
||||
// 分頁
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
totalCount: 0,
|
||||
// 專案自動完成
|
||||
projectSuggestions: [],
|
||||
showProjectSuggestions: false,
|
||||
projectSearchTimeout: null,
|
||||
// 彈窗
|
||||
showModal: false,
|
||||
isNewMode: true,
|
||||
editItem: {
|
||||
ID: 0,
|
||||
CourseCode: '',
|
||||
CourseName: '',
|
||||
CourseDate: '',
|
||||
CourseLocation: '',
|
||||
StartTime: '',
|
||||
EndTime: '',
|
||||
Teacher: '',
|
||||
projectID: '',
|
||||
projectName: '',
|
||||
NotificationTitle: '',
|
||||
NotificationTemplate: '',
|
||||
AgreedTerms: '',
|
||||
Closed: false,
|
||||
DB_APPNO: 0
|
||||
},
|
||||
// 編輯模式專案自動完成
|
||||
editProjectSuggestions: [],
|
||||
showEditProjectSuggestions: false,
|
||||
editProjectSearchTimeout: null,
|
||||
// 驗證錯誤訊息
|
||||
validationErrors: {
|
||||
CourseName: '',
|
||||
CourseDate: '',
|
||||
StartTime: '',
|
||||
EndTime: '',
|
||||
Teacher: '',
|
||||
CourseLocation: '',
|
||||
NotificationTitle: '',
|
||||
NotificationTemplate: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
totalPages() {
|
||||
return Math.ceil(this.totalCount / this.pageSize);
|
||||
},
|
||||
generatePreview() {
|
||||
if (!this.editItem.NotificationTemplate) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let preview = this.editItem.NotificationTemplate;
|
||||
|
||||
// 替換模板變數
|
||||
preview = preview.replace(/{課程日期}/g, this.editItem.CourseDate ? this.editItem.CourseDate : '(課程日期未設定)');
|
||||
preview = preview.replace(/{課程名稱}/g, this.editItem.CourseName || '(課程名稱未設定)');
|
||||
preview = preview.replace(/{課程地點}/g, this.editItem.CourseLocation || '(課程地點未設定)');
|
||||
preview = preview.replace(/{開始時間}/g, this.editItem.StartTime || '(開始時間未設定)');
|
||||
preview = preview.replace(/{結束時間}/g, this.editItem.EndTime || '(結束時間未設定)');
|
||||
preview = preview.replace(/{講師}/g, this.editItem.Teacher || '(講師未設定)');
|
||||
preview = preview.replace(/{學員姓名}/g, '(學員姓名)');
|
||||
|
||||
return preview;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log('[ClassesList] Vue app mounted');
|
||||
// 初始載入資料
|
||||
this.performSearch();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 執行搜尋
|
||||
*/
|
||||
async performSearch() {
|
||||
this.currentPage = 1;
|
||||
await this.loadClasses();
|
||||
},
|
||||
|
||||
/**
|
||||
* 載入活動資料
|
||||
*/
|
||||
async loadClasses() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await this.$api.call(this.apiName, 'SearchClasses', {
|
||||
pageIndex: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
courseCode: this.search.courseCode,
|
||||
courseName: this.search.courseName,
|
||||
courseDate1: this.search.courseDate1 || null,
|
||||
courseDate2: this.search.courseDate2 || null,
|
||||
courseLocation: this.search.courseLocation,
|
||||
projectID: this.search.projectID,
|
||||
closed: this.search.closed
|
||||
});
|
||||
|
||||
if (response.Success) {
|
||||
this.classes = (response.Data || []);
|
||||
this.totalCount = response.TotalCount || 0;
|
||||
console.log('[ClassesList] 載入成功,共 ' + this.totalCount + ' 筆');
|
||||
} else {
|
||||
this.$message.error(response.Message || '查詢失敗');
|
||||
this.classes = [];
|
||||
this.totalCount = 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[ClassesList] 載入錯誤:', error);
|
||||
this.$message.error('載入資料發生錯誤');
|
||||
this.classes = [];
|
||||
this.totalCount = 0;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除搜尋條件
|
||||
*/
|
||||
clearSearch() {
|
||||
this.search = {
|
||||
courseCode: '',
|
||||
courseName: '',
|
||||
courseDate1: '',
|
||||
courseDate2: '',
|
||||
courseLocation: '',
|
||||
projectID: '',
|
||||
projectName: '',
|
||||
closed: ''
|
||||
};
|
||||
this.projectSuggestions = [];
|
||||
this.showProjectSuggestions = false;
|
||||
this.performSearch();
|
||||
},
|
||||
|
||||
/**
|
||||
* 換頁
|
||||
*/
|
||||
goToPage(page) {
|
||||
if (page < 1 || page > this.totalPages) return;
|
||||
this.currentPage = page;
|
||||
this.loadClasses();
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化專案資訊 (編號 - 名稱)
|
||||
*/
|
||||
formatProjectInfo(item) {
|
||||
if (!item.ProjectID) return '';
|
||||
return item.ProjectName
|
||||
? `${item.ProjectID} - ${item.ProjectName}`
|
||||
: item.ProjectID;
|
||||
},
|
||||
|
||||
/**
|
||||
* 開啟新增彈窗
|
||||
*/
|
||||
openAddModal() {
|
||||
this.isNewMode = true;
|
||||
this.editItem = {
|
||||
ID: 0,
|
||||
CourseCode: '(自動產生)',
|
||||
CourseName: '',
|
||||
CourseDate: '',
|
||||
CourseLocation: '',
|
||||
StartTime: '',
|
||||
EndTime: '',
|
||||
Teacher: '',
|
||||
projectID: '',
|
||||
projectName: '',
|
||||
NotificationTitle: '',
|
||||
NotificationTemplate: '',
|
||||
AgreedTerms: '',
|
||||
Closed: false,
|
||||
DB_APPNO: 0
|
||||
};
|
||||
this.editProjectSuggestions = [];
|
||||
this.showEditProjectSuggestions = false;
|
||||
this.clearValidationErrors();
|
||||
this.showModal = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* 開啟更正彈窗
|
||||
*/
|
||||
openEditModal(item) {
|
||||
this.isNewMode = false;
|
||||
const projectDisplay = item.ProjectID && item.ProjectName
|
||||
? `${item.ProjectID} - ${item.ProjectName}`
|
||||
: (item.ProjectID || '');
|
||||
this.editItem = {
|
||||
ID: item.ID,
|
||||
CourseCode: item.CourseCode,
|
||||
CourseName: item.CourseName,
|
||||
CourseDate: this.$format.date(item.CourseDate),
|
||||
CourseLocation: item.CourseLocation || '',
|
||||
StartTime: item.StartTime || '',
|
||||
EndTime: item.EndTime || '',
|
||||
Teacher: item.Teacher || '',
|
||||
projectID: item.ProjectID || '',
|
||||
projectName: projectDisplay,
|
||||
NotificationTitle: item.NotificationTitle || '',
|
||||
NotificationTemplate: item.NotificationTemplate || '',
|
||||
AgreedTerms: item.AgreedTerms || '',
|
||||
Closed: item.Closed,
|
||||
DB_APPNO: item.DB_APPNO
|
||||
};
|
||||
this.editProjectSuggestions = [];
|
||||
this.showEditProjectSuggestions = false;
|
||||
this.clearValidationErrors();
|
||||
this.showModal = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* 關閉彈窗
|
||||
*/
|
||||
closeModal() {
|
||||
this.showModal = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* 插入模板文字
|
||||
*/
|
||||
insertTemplate(text) {
|
||||
const textarea = this.$refs.templateTextarea;
|
||||
if (!textarea) return;
|
||||
|
||||
const startPos = textarea.selectionStart;
|
||||
const endPos = textarea.selectionEnd;
|
||||
const currentValue = this.editItem.NotificationTemplate || '';
|
||||
|
||||
// 在游標位置插入文字
|
||||
this.editItem.NotificationTemplate =
|
||||
currentValue.substring(0, startPos) +
|
||||
text +
|
||||
currentValue.substring(endPos);
|
||||
|
||||
// 設定游標位置
|
||||
this.$nextTick(() => {
|
||||
textarea.selectionStart = textarea.selectionEnd = startPos + text.length;
|
||||
textarea.focus();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除驗證錯誤訊息
|
||||
*/
|
||||
clearValidationErrors() {
|
||||
this.validationErrors = {
|
||||
CourseName: '',
|
||||
CourseDate: '',
|
||||
StartTime: '',
|
||||
EndTime: '',
|
||||
Teacher: '',
|
||||
CourseLocation: '',
|
||||
NotificationTitle: '',
|
||||
NotificationTemplate: ''
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 搜尋專案 (自動完成)
|
||||
*/
|
||||
searchProjects() {
|
||||
// 清除之前的超時
|
||||
if (this.projectSearchTimeout) {
|
||||
clearTimeout(this.projectSearchTimeout);
|
||||
}
|
||||
|
||||
const keyword = this.search.projectName.trim();
|
||||
|
||||
// 如果輸入為空,清空建議和ProjectID
|
||||
if (!keyword) {
|
||||
this.projectSuggestions = [];
|
||||
this.search.projectID = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// 延遲搜尋以避免過多API呼叫
|
||||
this.projectSearchTimeout = setTimeout(async () => {
|
||||
try {
|
||||
const response = await this.$api.call(this.apiName, 'SearchProjects', {
|
||||
keyword: keyword
|
||||
});
|
||||
|
||||
if (response.Success) {
|
||||
this.projectSuggestions = response.Data || [];
|
||||
this.showProjectSuggestions = this.projectSuggestions.length > 0;
|
||||
} else {
|
||||
this.projectSuggestions = [];
|
||||
this.showProjectSuggestions = false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[ClassesList] 搜尋專案錯誤:', error);
|
||||
this.projectSuggestions = [];
|
||||
this.showProjectSuggestions = false;
|
||||
}
|
||||
}, 300);
|
||||
},
|
||||
|
||||
/**
|
||||
* 選擇專案
|
||||
*/
|
||||
selectProject(suggestion) {
|
||||
this.search.projectID = suggestion.Value;
|
||||
this.search.projectName = `${suggestion.Value} - ${suggestion.Name}`;
|
||||
this.projectSuggestions = [];
|
||||
this.showProjectSuggestions = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* 隱藏專案建議 (延遲隱藏以允許點擊)
|
||||
*/
|
||||
hideProjectSuggestions() {
|
||||
setTimeout(() => {
|
||||
this.showProjectSuggestions = false;
|
||||
}, 200);
|
||||
},
|
||||
|
||||
/**
|
||||
* 搜尋編輯模式的專案 (自動完成)
|
||||
*/
|
||||
searchEditProjectSuggestions() {
|
||||
// 清除之前的超時
|
||||
if (this.editProjectSearchTimeout) {
|
||||
clearTimeout(this.editProjectSearchTimeout);
|
||||
}
|
||||
|
||||
const keyword = this.editItem.projectName.trim();
|
||||
|
||||
// 如果輸入為空,清空建議和ProjectID
|
||||
if (!keyword) {
|
||||
this.editProjectSuggestions = [];
|
||||
this.editItem.projectID = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// 延遲搜尋以避免過多API呼叫
|
||||
this.editProjectSearchTimeout = setTimeout(async () => {
|
||||
try {
|
||||
const response = await this.$api.call(this.apiName, 'SearchProjects', {
|
||||
keyword: keyword
|
||||
});
|
||||
|
||||
if (response.Success) {
|
||||
this.editProjectSuggestions = response.Data || [];
|
||||
this.showEditProjectSuggestions = this.editProjectSuggestions.length > 0;
|
||||
} else {
|
||||
this.editProjectSuggestions = [];
|
||||
this.showEditProjectSuggestions = false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[ClassesList] 搜尋編輯模式專案錯誤:', error);
|
||||
this.editProjectSuggestions = [];
|
||||
this.showEditProjectSuggestions = false;
|
||||
}
|
||||
}, 300);
|
||||
},
|
||||
|
||||
/**
|
||||
* 選擇編輯模式的專案
|
||||
*/
|
||||
selectEditProject(suggestion) {
|
||||
this.editItem.projectID = suggestion.Value;
|
||||
this.editItem.projectName = `${suggestion.Value} - ${suggestion.Name}`;
|
||||
this.editProjectSuggestions = [];
|
||||
this.showEditProjectSuggestions = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* 隱藏編輯模式的專案建議 (延遲隱藏以允許點擊)
|
||||
*/
|
||||
hideEditProjectSuggestions() {
|
||||
setTimeout(() => {
|
||||
this.showEditProjectSuggestions = false;
|
||||
}, 200);
|
||||
},
|
||||
|
||||
/**
|
||||
* 驗證輸入
|
||||
*/
|
||||
validateInput() {
|
||||
this.clearValidationErrors();
|
||||
let isValid = true;
|
||||
|
||||
// 驗證課程名稱
|
||||
if (!this.editItem.CourseName || this.editItem.CourseName.trim() === '') {
|
||||
this.validationErrors.CourseName = '課程名稱為必填欄位';
|
||||
isValid = false;
|
||||
} else if (this.editItem.CourseName.length > 100) {
|
||||
this.validationErrors.CourseName = '課程名稱最長 100 個字元';
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// 驗證課程日期
|
||||
if (!this.editItem.CourseDate) {
|
||||
this.validationErrors.CourseDate = '課程日期為必填欄位';
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// 驗證開始時間
|
||||
if (!this.editItem.StartTime || this.editItem.StartTime.trim() === '') {
|
||||
this.validationErrors.StartTime = '開始時間為必填欄位';
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// 驗證結束時間
|
||||
if (!this.editItem.EndTime || this.editItem.EndTime.trim() === '') {
|
||||
this.validationErrors.EndTime = '結束時間為必填欄位';
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// 驗證講師
|
||||
if (!this.editItem.Teacher || this.editItem.Teacher.trim() === '') {
|
||||
this.validationErrors.Teacher = '講師為必填欄位';
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// 驗證通知標題
|
||||
if (this.editItem.NotificationTitle && this.editItem.NotificationTitle.length > 100) {
|
||||
this.validationErrors.NotificationTitle = '通知標題最長 100 個字元';
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// 驗證通知模板
|
||||
if (this.editItem.NotificationTemplate && this.editItem.NotificationTemplate.length > 500) {
|
||||
this.validationErrors.NotificationTemplate = '通知模板最長 500 個字元';
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
},
|
||||
|
||||
/**
|
||||
* 儲存活動
|
||||
*/
|
||||
async saveClasses() {
|
||||
if (!this.validateInput()) return;
|
||||
|
||||
try {
|
||||
// 準備資料
|
||||
const item = {
|
||||
ID: this.editItem.ID,
|
||||
CourseCode: this.editItem.CourseCode,
|
||||
CourseName: this.editItem.CourseName.trim(),
|
||||
CourseDate: this.editItem.CourseDate,
|
||||
CourseLocation: this.editItem.CourseLocation,
|
||||
StartTime: this.editItem.StartTime,
|
||||
EndTime: this.editItem.EndTime,
|
||||
Teacher: this.editItem.Teacher,
|
||||
ProjectID: this.editItem.projectID,
|
||||
NotificationTitle: this.editItem.NotificationTitle,
|
||||
NotificationTemplate: this.editItem.NotificationTemplate,
|
||||
AgreedTerms: this.editItem.AgreedTerms,
|
||||
Closed: this.editItem.Closed,
|
||||
DB_APPNO: this.editItem.DB_APPNO
|
||||
};
|
||||
|
||||
const response = await this.$api.call(this.apiName, 'SaveClasses', {
|
||||
isNew: this.isNewMode,
|
||||
item: item
|
||||
});
|
||||
|
||||
if (response.Success) {
|
||||
this.$message.success(response.Message || '儲存成功');
|
||||
this.closeModal();
|
||||
this.loadClasses();
|
||||
} else {
|
||||
this.$message.error(response.Message || '儲存失敗');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[ClassesList] 儲存錯誤:', error);
|
||||
this.$message.error('儲存發生錯誤');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 儲存後繼續新增
|
||||
*/
|
||||
async saveAndContinue() {
|
||||
if (!this.validateInput()) return;
|
||||
|
||||
try {
|
||||
// 準備資料
|
||||
const item = {
|
||||
ID: 0,
|
||||
CourseCode: '',
|
||||
CourseName: this.editItem.CourseName.trim(),
|
||||
CourseDate: this.editItem.CourseDate,
|
||||
CourseLocation: this.editItem.CourseLocation,
|
||||
StartTime: this.editItem.StartTime,
|
||||
EndTime: this.editItem.EndTime,
|
||||
Teacher: this.editItem.Teacher,
|
||||
ProjectID: this.editItem.projectID,
|
||||
NotificationTitle: this.editItem.NotificationTitle,
|
||||
NotificationTemplate: this.editItem.NotificationTemplate,
|
||||
AgreedTerms: this.editItem.AgreedTerms,
|
||||
Closed: this.editItem.Closed,
|
||||
DB_APPNO: 0
|
||||
};
|
||||
|
||||
const response = await this.$api.call(this.apiName, 'SaveClasses', {
|
||||
isNew: true,
|
||||
item: item
|
||||
});
|
||||
|
||||
if (response.Success) {
|
||||
this.$message.success(response.Message || '儲存成功');
|
||||
// 清除輸入內容,重新進入新增模式
|
||||
this.editItem = {
|
||||
ID: 0,
|
||||
CourseCode: '(自動產生)',
|
||||
CourseName: '',
|
||||
CourseDate: '',
|
||||
CourseLocation: '',
|
||||
StartTime: '',
|
||||
EndTime: '',
|
||||
Teacher: '',
|
||||
projectID: '',
|
||||
projectName: '',
|
||||
NotificationTitle: '',
|
||||
NotificationTemplate: '',
|
||||
AgreedTerms: '',
|
||||
Closed: false,
|
||||
DB_APPNO: 0
|
||||
};
|
||||
this.loadClasses();
|
||||
} else {
|
||||
this.$message.error(response.Message || '儲存失敗');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[ClassesList] 儲存錯誤:', error);
|
||||
this.$message.error('儲存發生錯誤');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 刪除活動
|
||||
*/
|
||||
async deleteClasses(item) {
|
||||
if (!confirm(`確定要刪除活動「${item.CourseName}」嗎?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.$api.call(this.apiName, 'DeleteClasses', {
|
||||
id: item.ID,
|
||||
dbAppNo: item.DB_APPNO
|
||||
});
|
||||
|
||||
if (response.Success) {
|
||||
this.$message.success(response.Message || '刪除成功');
|
||||
this.loadClasses();
|
||||
} else {
|
||||
this.$message.error(response.Message || '刪除失敗');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[ClassesList] 刪除錯誤:', error);
|
||||
this.$message.error('刪除發生錯誤');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 匯出活動
|
||||
*/
|
||||
async performExport() {
|
||||
try {
|
||||
// 驗證必要條件:課程日期(起)和課程日期(迄)為必填
|
||||
if (!this.search.courseDate1 || !this.search.courseDate2) {
|
||||
this.$message.warning('課程日期(起)和課程日期(迄)為必填欄位');
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await this.$api.call(this.apiName, 'ExportClassesList', {
|
||||
courseDate1: this.search.courseDate1 || null,
|
||||
courseDate2: this.search.courseDate2 || null,
|
||||
projectID: this.search.projectID || null
|
||||
});
|
||||
|
||||
if (response.Success) {
|
||||
// 取得檔案資訊
|
||||
const fileData = response.Data;
|
||||
const fileName = fileData.FileName;
|
||||
const fileBase64 = fileData.FileBase64;
|
||||
const fileSize = fileData.FileSize;
|
||||
|
||||
// 轉換 Base64 為 Blob
|
||||
const binaryString = atob(fileBase64);
|
||||
const bytes = new Uint8Array(binaryString.length);
|
||||
for (let i = 0; i < binaryString.length; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
const blob = new Blob([bytes], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
||||
|
||||
// 建立下載連結
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = fileName;
|
||||
|
||||
// 執行下載
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
// 清理資源
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
this.$message.success(`匯出成功 (${(fileSize / 1024).toFixed(2)} KB)`);
|
||||
console.log('[ClassesList] 匯出成功:', fileName);
|
||||
} else {
|
||||
this.$message.error(response.Message || '匯出失敗');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[ClassesList] 匯出錯誤:', error);
|
||||
this.$message.error('匯出發生錯誤');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user