651 lines
25 KiB
JavaScript
651 lines
25 KiB
JavaScript
// ========================================
|
|
// 活動管理 - 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('匯出發生錯誤');
|
|
}
|
|
}
|
|
}
|
|
});
|
|
})();
|