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
+275
View File
@@ -0,0 +1,275 @@
<%@ Page Title="課程管理" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="ClassesList.aspx.cs" Inherits="報到系統.Forms.ClassesList" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
<link href="<%= ResolveUrlWithSysVer("~/Content/ClassesList.css") %>" rel="stylesheet" type="text/css" />
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<div id="classesApp" class="page-container">
<div class="page-header">
<h1 class="page-title">課程管理</h1>
</div>
<!-- 搜尋面板 -->
<div class="search-panel">
<div class="search-row">
<div class="search-field-inline w-3">
<label class="search-label-inline">課程編號</label>
<input type="text" v-model="search.courseCode" class="search-input" placeholder="請輸入課程編號">
</div>
<div class="search-field-inline w-3">
<label class="search-label-inline">課程名稱</label>
<input type="text" v-model="search.courseName" class="search-input" placeholder="請輸入課程名稱">
</div>
<div class="search-field-inline w-3">
<label class="search-label-inline">課程日期(起)</label>
<input type="date" v-model="search.courseDate1" class="search-input">
</div>
<div class="search-field-inline w-3">
<label class="search-label-inline">課程日期(迄)</label>
<input type="date" v-model="search.courseDate2" class="search-input">
</div>
<div class="search-field-inline w-3">
<label class="search-label-inline">課程地點</label>
<input type="text" v-model="search.courseLocation" class="search-input" placeholder="請輸入課程地點">
</div>
<div class="search-field-inline w-3">
<label class="search-label-inline">結案</label>
<select v-model="search.closed" class="search-input">
<option value="">全部</option>
<option value="true">是</option>
<option value="false">否</option>
</select>
</div>
<div class="search-field-inline autocomplete-field w-6">
<label class="search-label-inline">計畫</label>
<div class="autocomplete-container" style="flex: 1;">
<input
type="text"
v-model="search.projectName"
@input="searchProjects"
@focus="showProjectSuggestions = true"
@blur="hideProjectSuggestions"
class="search-input autocomplete-input"
placeholder="請輸入計畫編號或名稱">
<div class="autocomplete-dropdown" v-if="showProjectSuggestions && projectSuggestions.length > 0">
<div
v-for="suggestion in projectSuggestions"
:key="suggestion.Value"
class="autocomplete-item"
@click="selectProject(suggestion)">
<span class="autocomplete-value">{{ suggestion.Value }}</span>
<span class="autocomplete-name">{{ suggestion.Name }}</span>
</div>
</div>
</div>
</div>
</div>
<div class="search-actions">
<span class="search-hint">💡 匯出時僅使用課程日期與計畫篩選條件</span>
<button class="btn btn-secondary" @click="clearSearch">清除</button>
<button class="btn btn-primary" @click="performSearch">搜尋</button>
<button class="btn btn-primary" @click="performExport">匯出</button>
</div>
</div>
<!-- 搜尋結果 -->
<div class="result-panel">
<table class="result-table">
<thead>
<tr>
<th class="text-center">課程編號</th>
<th class="text-center">課程名稱</th>
<th class="text-center">課程日期</th>
<th class="text-center">開始時間</th>
<th class="text-center">結束時間</th>
<th class="text-center">講師</th>
<th class="text-center">課程地點</th>
<th class="text-center">計畫</th>
<%-- <th class="text-center">實到</th>
<th class="text-center">應到</th>
<th class="text-center">缺席</th>
<th class="text-center">異常</th>
<th class="text-center">現場</th>--%>
<th class="text-center">結案</th>
<th class="text-center">
操作
<button class="btn btn-success btn-sm" style="margin-left: 10px;" @click="openAddModal">新增</button>
</th>
</tr>
</thead>
<tbody>
<tr v-if="loading">
<td colspan="10" class="empty-message">載入中...</td>
</tr>
<tr v-else-if="classes.length === 0">
<td colspan="10" class="empty-message">查無資料</td>
</tr>
<tr v-else v-for="item in classes" :key="item.ID">
<td class="text-center">{{ item.CourseCode }}</td>
<td>{{ item.CourseName }}</td>
<td class="text-center">{{ $format.date(item.CourseDate) }}</td>
<td class="text-center">{{ item.StartTime || '-' }}</td>
<td class="text-center">{{ item.EndTime || '-' }}</td>
<td>{{ item.Teacher || '-' }}</td>
<td>{{ item.CourseLocation }}</td>
<td class="project-cell">{{ formatProjectInfo(item) }}</td>
<%-- <td class="text-center">{{ item.Actual }}</td>
<td class="text-center">{{ item.Expected }}</td>
<td class="text-center">{{ item.Absent }}</td>
<td class="text-center">{{ item.Exception }}</td>
<td class="text-center">{{ item.OnSite }}</td>--%>
<td class="text-center">{{ item.Closed ? "是" : "否" }}</td>
<td class="text-center">
<button class="btn btn-primary btn-sm" @click="openEditModal(item)">更正</button>
<button class="btn btn-danger btn-sm" @click="deleteClasses(item)">刪除</button>
</td>
</tr>
</tbody>
</table>
<!-- 分頁 -->
<div class="pagination" v-if="totalCount > 0">
<button class="btn btn-secondary btn-sm" :disabled="currentPage === 1" @click="goToPage(currentPage - 1)">上一頁</button>
<span class="pagination-info">第 {{ currentPage }} / {{ totalPages }} 頁,共 {{ totalCount }} 筆</span>
<button class="btn btn-secondary btn-sm" :disabled="currentPage === totalPages" @click="goToPage(currentPage + 1)">下一頁</button>
</div>
</div>
<!-- 新增/更正彈窗 -->
<div class="modal" :class="{ show: showModal }">
<div class="modal-dialog">
<div class="modal-header">
<h3 class="modal-title">{{ isNewMode ? '新增課程' : '更正課程' }}</h3>
</div>
<div class="modal-body">
<!-- 第一行:課程編號、結案 -->
<div class="form-row">
<div class="form-group-inline form-col-50">
<label class="form-label-inline">課程編號</label>
<input type="text" v-model="editItem.CourseCode" class="form-control" disabled>
</div>
<div class="form-group-inline form-col-50">
<div class="checkbox-group">
<input type="checkbox" v-model="editItem.Closed" id="cbClosed">
<label class="checkbox-label" for="cbClosed">結案 (在客戶端隱藏)</label>
</div>
</div>
</div>
<!-- 第二行:課程名稱 -->
<div class="form-group-inline">
<label class="form-label-inline required">課程名稱</label>
<input type="text" v-model="editItem.CourseName" class="form-control" maxlength="100" placeholder="請輸入課程名稱">
<div class="error-message" v-if="validationErrors.CourseName">{{ validationErrors.CourseName }}</div>
</div>
<!-- 第三行:課程日期、開始時間、結束時間 -->
<div class="form-row">
<div class="form-group-inline form-col-33">
<label class="form-label-inline required">課程日期</label>
<input type="date" v-model="editItem.CourseDate" class="form-control">
<div class="error-message" v-if="validationErrors.CourseDate">{{ validationErrors.CourseDate }}</div>
</div>
<div class="form-group-inline form-col-33">
<label class="form-label-inline required">開始時間</label>
<input type="time" v-model="editItem.StartTime" class="form-control">
<div class="error-message" v-if="validationErrors.StartTime">{{ validationErrors.StartTime }}</div>
</div>
<div class="form-group-inline form-col-33">
<label class="form-label-inline required">結束時間</label>
<input type="time" v-model="editItem.EndTime" class="form-control">
<div class="error-message" v-if="validationErrors.EndTime">{{ validationErrors.EndTime }}</div>
</div>
</div>
<!-- 第四行:課程地點、講師 -->
<div class="form-row">
<div class="form-group-inline form-col-50">
<label class="form-label-inline">課程地點</label>
<input type="text" v-model="editItem.CourseLocation" class="form-control" placeholder="請輸入課程地點">
<div class="error-message" v-if="validationErrors.CourseLocation">{{ validationErrors.CourseLocation }}</div>
</div>
<div class="form-group-inline form-col-50">
<label class="form-label-inline required">講師</label>
<input type="text" v-model="editItem.Teacher" class="form-control" placeholder="請輸入講師名稱">
<div class="error-message" v-if="validationErrors.Teacher">{{ validationErrors.Teacher }}</div>
</div>
</div>
<!-- 第五行:計畫 -->
<div class="form-group-inline autocomplete-field">
<label class="form-label-inline">計畫</label>
<div class="autocomplete-container" style="flex: 1;">
<input
type="text"
v-model="editItem.projectName"
@input="searchEditProjectSuggestions"
@focus="showEditProjectSuggestions = true"
@blur="hideEditProjectSuggestions"
class="form-control autocomplete-input"
placeholder="請輸入計畫編號或名稱">
<div class="autocomplete-dropdown" v-if="showEditProjectSuggestions && editProjectSuggestions.length > 0">
<div
v-for="suggestion in editProjectSuggestions"
:key="suggestion.Value"
class="autocomplete-item"
@click="selectEditProject(suggestion)">
<span class="autocomplete-value">{{ suggestion.Value }}</span>
<span class="autocomplete-name">{{ suggestion.Name }}</span>
</div>
</div>
</div>
</div>
<!-- 第五點五行:同意確認事項 -->
<div class="form-group-inline">
<label class="form-label-inline">同意確認事項</label>
<input type="text" v-model="editItem.AgreedTerms" class="form-control" maxlength="100" placeholder="請輸入同意確認事項">
</div>
<!-- 第六行:通知標題 -->
<div class="form-group-inline">
<label class="form-label-inline">通知標題</label>
<input type="text" v-model="editItem.NotificationTitle" class="form-control" maxlength="100" placeholder="請輸入通知標題">
<div class="error-message" v-if="validationErrors.NotificationTitle">{{ validationErrors.NotificationTitle }}</div>
</div>
<!-- 第七行:通知模板、預覽 -->
<div class="form-row">
<div class="form-group form-col-50">
<label class="form-label">通知模板</label>
<textarea ref="templateTextarea" v-model="editItem.NotificationTemplate" class="form-control" maxlength="1000" placeholder="請輸入通知模板"></textarea>
<div class="error-message" v-if="validationErrors.NotificationTemplate">{{ validationErrors.NotificationTemplate }}</div>
<div class="template-buttons">
<button class="btn-template" @click="insertTemplate('{課程日期}')">插入:課程日期</button>
<button class="btn-template" @click="insertTemplate('{課程名稱}')">插入:課程名稱</button>
<button class="btn-template" @click="insertTemplate('{課程地點}')">插入:課程地點</button>
<button class="btn-template" @click="insertTemplate('{開始時間}')">插入:開始時間</button>
<button class="btn-template" @click="insertTemplate('{結束時間}')">插入:結束時間</button>
<button class="btn-template" @click="insertTemplate('{講師}')">插入:講師</button>
<button class="btn-template" @click="insertTemplate('{學員姓名}')">插入:學員姓名</button>
</div>
</div>
<div class="form-group form-col-50">
<label class="form-label">預覽</label>
<div class="template-preview">
<p v-if="!editItem.NotificationTemplate" class="preview-placeholder">(空白)</p>
<p v-else class="preview-content">{{ generatePreview }}</p>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" @click="closeModal">取消</button>
<button class="btn btn-success" v-if="isNewMode" @click="saveAndContinue">存檔後繼續新增</button>
<button class="btn btn-primary" @click="saveClasses">存檔</button>
</div>
</div>
</div>
</div>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="script" runat="server">
<script src="<%= ResolveUrlWithSysVer("~/Scripts/ClassesList.js") %>"></script>
</asp:Content>
+20
View File
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace .Forms
{
public partial class ClassesList : PageBase
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// 可在此處進行權限檢查或初始化
}
}
}
}
+17
View File
@@ -0,0 +1,17 @@
//------------------------------------------------------------------------------
// <自動產生>
// 這段程式碼是由工具產生的。
//
// 變更這個檔案可能會導致不正確的行為,而且如果已重新產生
// 程式碼,則會遺失變更。
// </自動產生>
//------------------------------------------------------------------------------
namespace .Forms
{
public partial class ClassesList
{
}
}
+145
View File
@@ -0,0 +1,145 @@
<%@ Page Title="課程報表" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="ClassesReport.aspx.cs" Inherits="報到系統.Forms.ClassesReport" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
<link href="<%= ResolveUrlWithSysVer("~/Content/ClassesList.css") %>" rel="stylesheet" type="text/css" />
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<div id="classesReportApp" class="page-container">
<div class="page-header">
<h1 class="page-title">課程報表</h1>
</div>
<!-- 搜尋面板 -->
<div class="search-panel">
<div class="search-row">
<div class="search-field-inline w-3">
<label class="search-label-inline">課程編號</label>
<input type="text" v-model="search.courseCode" class="search-input" placeholder="請輸入課程編號">
</div>
<div class="search-field-inline w-3">
<label class="search-label-inline">課程名稱</label>
<input type="text" v-model="search.courseName" class="search-input" placeholder="請輸入課程名稱">
</div>
<div class="search-field-inline w-3">
<label class="search-label-inline">課程日期(起)</label>
<input type="date" v-model="search.courseDate1" class="search-input">
</div>
<div class="search-field-inline w-3">
<label class="search-label-inline">課程日期(迄)</label>
<input type="date" v-model="search.courseDate2" class="search-input">
</div>
<div class="search-field-inline w-3">
<label class="search-label-inline">課程地點</label>
<input type="text" v-model="search.courseLocation" class="search-input" placeholder="請輸入課程地點">
</div>
<div class="search-field-inline w-3">
<label class="search-label-inline">結案</label>
<select v-model="search.closed" class="search-input">
<option value="">全部</option>
<option value="true">是</option>
<option value="false">否</option>
</select>
</div>
<div class="search-field-inline w-3">
<label class="search-label-inline">簽到退時間格式</label>
<select v-model="search.timeFormat" class="search-input">
<option value="datetime">年月日時分秒</option>
<option value="time">時分</option>
</select>
</div>
<div class="search-field-inline autocomplete-field w-6">
<label class="search-label-inline">計畫</label>
<div class="autocomplete-container" style="flex: 1;">
<input
type="text"
v-model="search.projectName"
@input="searchProjects"
@focus="showProjectSuggestions = true"
@blur="hideProjectSuggestions"
class="search-input autocomplete-input"
placeholder="請輸入計畫編號或名稱">
<div class="autocomplete-dropdown" v-if="showProjectSuggestions && projectSuggestions.length > 0">
<div
v-for="suggestion in projectSuggestions"
:key="suggestion.Value"
class="autocomplete-item"
@click="selectProject(suggestion)">
<span class="autocomplete-value">{{ suggestion.Value }}</span>
<span class="autocomplete-name">{{ suggestion.Name }}</span>
</div>
</div>
</div>
</div>
</div>
<div class="search-actions">
<button class="btn btn-secondary" @click="clearSearch">清除</button>
<button class="btn btn-primary" @click="performSearch">搜尋</button>
</div>
</div>
<!-- 搜尋結果 -->
<div class="result-panel">
<table class="result-table">
<thead>
<tr>
<th class="text-center">課程編號</th>
<th class="text-center">課程名稱</th>
<th class="text-center">課程日期</th>
<th class="text-center">開始時間</th>
<th class="text-center">結束時間</th>
<th class="text-center">講師</th>
<th class="text-center">課程地點</th>
<th class="text-center">計畫</th>
<%-- <th class="text-center">實到</th>
<th class="text-center">應到</th>
<th class="text-center">缺席</th>
<th class="text-center">異常</th>
<th class="text-center">現場</th>--%>
<th class="text-center">結案</th>
<th class="text-center">操作</th>
</tr>
</thead>
<tbody>
<tr v-if="loading">
<td colspan="10" class="empty-message">載入中...</td>
</tr>
<tr v-else-if="classes.length === 0">
<td colspan="10" class="empty-message">查無資料</td>
</tr>
<tr v-else v-for="item in classes" :key="item.ID">
<td class="text-center">{{ item.CourseCode }}</td>
<td class="text-center">{{ item.CourseName }}</td>
<td class="text-center">{{ $format.date(item.CourseDate) }}</td>
<td class="text-center">{{ item.StartTime }}</td>
<td class="text-center">{{ item.EndTime }}</td>
<td class="text-center">{{ item.Teacher }}</td>
<td class="text-center">{{ item.CourseLocation }}</td>
<td class="text-center">{{ item.ProjectValue }} {{ item.ProjectName }}</td>
<%-- <td class="text-center">{{ item.Actual }}</td>
<td class="text-center">{{ item.Expected }}</td>
<td class="text-center">{{ item.Absent }}</td>
<td class="text-center">{{ item.Exception }}</td>
<td class="text-center">{{ item.OnSite }}</td>--%>
<td class="text-center">{{ item.Closed ? '是' : '否' }}</td>
<td class="text-center">
<button class="btn btn-primary btn-sm" @click="exportReport(item.ID)" :disabled="exporting">
<span v-if="exporting && exportingId === item.ID">匯出中...</span>
<span v-else>學員簽到表</span>
</button>
</td>
</tr>
</tbody>
</table>
<!-- 分頁 -->
<div class="pagination" v-if="totalPages > 1">
<button class="btn btn-secondary btn-sm" @click="previousPage" :disabled="currentPage === 1">上一頁</button>
<span class="pagination-info">第 {{ currentPage }} / {{ totalPages }} 頁,共 {{ totalCount }} 筆</span>
<button class="btn btn-secondary btn-sm" @click="nextPage" :disabled="currentPage === totalPages">下一頁</button>
</div>
</div>
</div>
<script src="<%= ResolveUrlWithSysVer("~/Scripts/ClassesReport.js") %>"></script>
</asp:Content>
+16
View File
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace .Forms
{
public partial class ClassesReport : PageBase
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
}
+17
View File
@@ -0,0 +1,17 @@
//------------------------------------------------------------------------------
// <自動產生>
// 這段程式碼是由工具產生的。
//
// 變更這個檔案可能會導致不正確的行為,而且如果已重新產生
// 程式碼,則會遺失變更。
// </自動產生>
//------------------------------------------------------------------------------
namespace .Forms
{
public partial class ClassesReport
{
}
}
+13
View File
@@ -0,0 +1,13 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Constructing.aspx.cs" Inherits="報到系統.Forms.Constructing" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<div id="homeApp" class="page-container">
<h1 class="page-title">功能建置中...</h1>
</div>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="script" runat="server">
</asp:Content>
+17
View File
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace .Forms
{
public partial class Constructing : PageBase
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
}
+17
View File
@@ -0,0 +1,17 @@
//------------------------------------------------------------------------------
// <自動產生>
// 這段程式碼是由工具產生的。
//
// 變更這個檔案可能會導致不正確的行為,而且如果已重新產生
// 程式碼,則會遺失變更。
// </自動產生>
//------------------------------------------------------------------------------
namespace .Forms
{
public partial class Constructing
{
}
}
+37
View File
@@ -0,0 +1,37 @@
<%@ Page Title="首頁" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Home.aspx.cs" Inherits="報到系統.Forms.Home" %>
<%@ Import Namespace="報到系統" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
<link href="<%= ResolveUrlWithSysVer("~/Content/Home.css") %>" rel="stylesheet" type="text/css" />
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<div id="homeApp" class="home-container">
<!-- 客戶端安裝指南 -->
<div class="content-section">
<h3>📥 下載與安裝報到系統客戶端</h3>
<ol class="steps-list">
<li>
<a href="<%= LatestClientPackage() %>" class="home-btn">點此下載安裝包壓縮檔</a>
</li>
<li>
解壓縮下載的安裝包壓縮檔到任何資料夾
</li>
<li>
找到並執行 <strong>setup.exe</strong> 文件
</li>
<li>
依照安裝精靈進行安裝步驟
</li>
<li>
安裝完畢後,您將在桌面看到 <strong>"報到系統客戶端"</strong> 捷徑
</li>
</ol>
<div class="info-box">
<div class="info-box-title">💡 提示</div>
<p>建議使用最新版本的報到系統客戶端以獲得最佳體驗和安全性。</p>
</div>
</div>
</div>
</asp:Content>
+63
View File
@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace .Forms
{
public partial class Home : PageBase
{
protected void Page_Load(object sender, EventArgs e)
{
}
public string LatestClientPackage()
{
var latestClientPackage = GetLatestClientPackageFileName();
if (!string.IsNullOrEmpty(latestClientPackage))
{
return ResolveUrlWithSysVer(string.Format("~/Assets/Downloads/{0}", latestClientPackage));
}
return string.Empty;
}
/// <summary>
/// 取得最新的客戶端安裝包檔案名稱
/// 從 Assets/Downloads 目錄下查找 "報到系統客戶端_*.zip" 檔案
/// 按檔名排序取最大的那一個
/// </summary>
private string GetLatestClientPackageFileName()
{
try
{
string downloadDir = Server.MapPath("~/Assets/Downloads/");
if (!Directory.Exists(downloadDir))
{
return string.Empty;
}
DirectoryInfo dirInfo = new DirectoryInfo(downloadDir);
FileInfo[] files = dirInfo.GetFiles("報到系統客戶端_*.zip");
if (files.Length == 0)
{
return string.Empty;
}
// 按檔名排序,取最後一個(最大的那個)
string latestFileName = files.OrderBy(f => f.Name).LastOrDefault()?.Name;
return latestFileName ?? string.Empty;
}
catch
{
return string.Empty;
}
}
}
}
+17
View File
@@ -0,0 +1,17 @@
//------------------------------------------------------------------------------
// <自動產生>
// 這段程式碼是由工具產生的。
//
// 變更這個檔案可能會導致不正確的行為,而且如果已重新產生
// 程式碼,則會遺失變更。
// </自動產生>
//------------------------------------------------------------------------------
namespace .Forms
{
public partial class Home
{
}
}
@@ -0,0 +1,221 @@
<%@ Page Title="簡訊帳號管理" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="MessageAccountManage.aspx.cs" Inherits="報到系統.Forms.MessageAccountManage" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
<link href="<%= ResolveUrlWithSysVer("~/Content/MessageAccountManage.css") %>" rel="stylesheet" type="text/css" />
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<div id="messageAccountManageApp" class="page-container">
<div class="page-header">
<h1 class="page-title">簡訊帳號管理</h1>
<p class="page-description">管理計畫的簡訊發送帳號密碼</p>
</div>
<!-- 目前預設平台區塊(含 inline 切換) -->
<div style="margin-bottom:12px; border:1px solid #c8d8f8; border-radius:4px; background:#f0f4ff; overflow:hidden;">
<div style="padding:10px 16px; display:flex; align-items:center; gap:16px;">
<span>目前預設平台:<strong>{{ currentPlatform ? currentPlatform.PlatformName + '' + currentPlatform.PlatformCode + '' : '載入中...' }}</strong></span>
<button type="button" class="btn btn-secondary btn-sm" @click="showSwitchPanel = !showSwitchPanel">
{{ showSwitchPanel ? '收合' : '切換平台' }}
</button>
</div>
<div v-if="showSwitchPanel" style="padding:10px 16px 14px; border-top:1px solid #c8d8f8; background:#fff; display:flex; align-items:center; gap:12px; flex-wrap:wrap;">
<label style="margin:0;">切換為:</label>
<select v-model="switchPlatformCode" class="form-control" style="width:220px;">
<option v-for="p in platforms" :key="p.PlatformCode" :value="p.PlatformCode">{{ p.PlatformName }}{{ p.PlatformCode }}</option>
</select>
<button type="button" class="btn btn-primary btn-sm" @click="setCurrentPlatform">確認切換</button>
<button type="button" class="btn btn-secondary btn-sm" @click="showSwitchPanel = false">取消</button>
</div>
</div>
<!-- 平台設定區塊 -->
<div class="search-panel" style="margin-bottom:12px;">
<div style="display:flex; align-items:center; cursor:pointer; user-select:none;" @click="showPlatformSettings = !showPlatformSettings">
<strong>平台帳號設定</strong>
<span style="margin-left:8px;">{{ showPlatformSettings ? '▲' : '▼' }}</span>
</div>
<div v-if="showPlatformSettings" style="margin-top:12px;">
<div v-for="p in platforms" :key="p.PlatformCode" style="display:flex; align-items:center; gap:12px; margin-bottom:10px; flex-wrap:wrap;">
<span style="min-width:80px; font-weight:bold;">{{ p.PlatformName }}</span>
<input type="text" v-model="p.Account" class="form-control" style="width:200px;" placeholder="主帳號">
<input :type="p.showPwd ? 'text' : 'password'" v-model="p.Password" class="form-control" style="width:200px;" placeholder="主密碼">
<button type="button" class="btn-template" @click="p.showPwd = !p.showPwd">{{ p.showPwd ? '隱藏' : '顯示' }}</button>
<button type="button" class="btn btn-primary btn-sm" @click="savePlatformSetting(p)">儲存</button>
</div>
</div>
</div>
<!-- 搜尋面板 -->
<div class="search-panel">
<div class="search-row">
<div class="search-field-inline autocomplete-field w-6">
<label class="search-label-inline">計畫</label>
<div class="autocomplete-container" style="flex: 1;">
<input
type="text"
v-model="search.projectName"
@input="searchProjects"
@focus="showProjectSuggestions = true"
@blur="hideProjectSuggestions"
class="search-input autocomplete-input"
placeholder="請輸入計畫編號或名稱">
<div class="autocomplete-dropdown" v-if="showProjectSuggestions && projectSuggestions.length > 0">
<div
v-for="suggestion in projectSuggestions"
:key="suggestion.Value"
class="autocomplete-item"
@click="selectProject(suggestion)">
<span class="autocomplete-value">{{ suggestion.Value }}</span>
<span class="autocomplete-name">{{ suggestion.Name }}</span>
</div>
</div>
</div>
</div>
<div class="search-field-inline w-4">
<label class="search-label-inline">平台</label>
<select v-model="search.platformCode" class="search-input">
<option value="">全部</option>
<option v-for="p in platforms" :key="p.PlatformCode" :value="p.PlatformCode">{{ p.PlatformName }}{{ p.PlatformCode }}</option>
</select>
</div>
</div>
<div class="search-actions">
<button type="button" class="btn btn-secondary" @click="clearSearch">清除</button>
<button type="button" class="btn btn-primary" @click="performSearch">搜尋</button>
<button type="button" class="btn btn-success" style="margin-left: 10px;" @click="openAddModal">新增帳號</button>
</div>
</div>
<!-- 列表區域 -->
<div class="result-panel">
<table class="result-table">
<thead>
<tr>
<th class="text-center">計畫代號</th>
<th class="text-center">計畫名稱</th>
<th class="text-center">平台</th>
<th class="text-center">簡訊帳號</th>
<th class="text-center">簡訊密碼</th>
<th class="text-center">操作</th>
</tr>
</thead>
<tbody>
<tr v-if="loading">
<td colspan="6" class="empty-message">載入中...</td>
</tr>
<tr v-else-if="accountList.length === 0">
<td colspan="6" class="empty-message">查無資料</td>
</tr>
<tr v-else v-for="item in accountList" :key="item.ID">
<td class="text-center">{{ item.ProjectID }}</td>
<td>{{ item.ProjectName || '' }}</td>
<td class="text-center">{{ getPlatformName(item.PlatformCode) }}</td>
<td class="password-cell">
<span v-if="!item.showPassword" class="password-mask">●●●●●●●●</span>
<span v-else class="password-text">{{ item.MMSAccount }}</span>
</td>
<td class="password-cell">
<span v-if="!item.showPassword" class="password-mask">●●●●●●●●</span>
<span v-else class="password-text">{{ item.MMSPassword }}</span>
<button type="button" class="btn-icon" @click.prevent="togglePasswordDisplay(item)">
{{ item.showPassword ? '隱藏' : '顯示' }}
</button>
</td>
<td class="text-center">
<button type="button" class="btn btn-primary btn-sm" @click="openEditModal(item)">編輯</button>
<button type="button" class="btn btn-danger btn-sm" @click="deleteAccount(item)">刪除</button>
</td>
</tr>
</tbody>
</table>
<!-- 分頁 -->
<div class="pagination" v-if="totalCount > 0">
<button type="button" class="btn btn-secondary btn-sm" :disabled="currentPage === 1" @click="previousPage">上一頁</button>
<span class="pagination-info">第 {{ currentPage }} / {{ totalPages }} 頁,共 {{ totalCount }} 筆</span>
<button type="button" class="btn btn-secondary btn-sm" :disabled="currentPage === totalPages" @click="nextPage">下一頁</button>
</div>
</div>
<!-- 新增/編輯彈窗 -->
<div class="modal" :class="{ show: showModal }">
<div class="modal-dialog">
<div class="modal-header">
<h3 class="modal-title">{{ isNewMode ? '新增簡訊帳號' : '編輯簡訊帳號' }}</h3>
</div>
<div class="modal-body">
<!-- 計畫代號 - 自動完成 -->
<div class="form-group-inline autocomplete-field">
<label class="form-label-inline required">計畫代號</label>
<div class="autocomplete-container" style="flex: 1;">
<input
type="text"
v-model="editItem.projectName"
@input="searchEditProjectSuggestions"
@focus="showEditProjectSuggestions = true"
@blur="hideEditProjectSuggestions"
class="form-control autocomplete-input"
:disabled="!isNewMode"
placeholder="請輸入計畫編號或名稱">
<div class="autocomplete-dropdown" v-if="showEditProjectSuggestions && editProjectSuggestions.length > 0">
<div
v-for="suggestion in editProjectSuggestions"
:key="suggestion.Value"
class="autocomplete-item"
@click="selectEditProject(suggestion)">
<span class="autocomplete-value">{{ suggestion.Value }}</span>
<span class="autocomplete-name">{{ suggestion.Name }}</span>
</div>
</div>
</div>
</div>
<div class="error-message" v-if="validationErrors.ProjectID">{{ validationErrors.ProjectID }}</div>
<!-- 使用平台 -->
<div class="form-group-inline">
<label class="form-label-inline">使用平台</label>
<select v-model="editItem.PlatformCode" class="form-control">
<option v-for="p in platforms" :key="p.PlatformCode" :value="p.PlatformCode">{{ p.PlatformName }}{{ p.PlatformCode }}</option>
</select>
</div>
<!-- 簡訊帳號 -->
<div class="form-group-inline">
<label class="form-label-inline required">簡訊帳號</label>
<input type="text"
v-model="editItem.MMSAccount"
class="form-control"
placeholder="請輸入簡訊帳號"
maxlength="50">
<div class="error-message" v-if="validationErrors.MMSAccount">{{ validationErrors.MMSAccount }}</div>
</div>
<!-- 簡訊密碼 -->
<div class="form-group-inline">
<label class="form-label-inline required">簡訊密碼</label>
<input :type="showPassword ? 'text' : 'password'"
v-model="editItem.MMSPassword"
class="form-control"
placeholder="請輸入簡訊密碼"
maxlength="50">
<button type="button" class="btn-template" @click.prevent="toggleEditPasswordDisplay">
{{ showPassword ? '隱藏' : '顯示' }}
</button>
<div class="error-message" v-if="validationErrors.MMSPassword">{{ validationErrors.MMSPassword }}</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" @click="closeModal">取消</button>
<button type="button" class="btn btn-primary" @click="saveAccount">存檔</button>
</div>
</div>
</div>
</div>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="script" runat="server">
<script src="<%= ResolveUrlWithSysVer("~/Scripts/MessageAccountManage.js") %>"></script>
</asp:Content>
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace .Forms
{
public partial class MessageAccountManage : PageBase
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// 初始化頁面
}
}
}
}
+17
View File
@@ -0,0 +1,17 @@
//------------------------------------------------------------------------------
// <自動產生>
// 這段程式碼是由工具產生的。
//
// 變更這個檔案可能會導致不正確的行為,而且如果已重新產生
// 程式碼,則會遺失變更。
// </自動產生>
//------------------------------------------------------------------------------
namespace .Forms
{
public partial class MessageAccountManage
{
}
}
+136
View File
@@ -0,0 +1,136 @@
<%@ Page Title="群組人員管理" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="RoleManage.aspx.cs" Inherits="報到系統.Forms.RoleManage" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
<link href="<%= ResolveUrlWithSysVer("~/Content/RoleManage.css") %>" rel="stylesheet" type="text/css" />
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<div id="roleManageApp" class="page-container">
<div class="page-header">
<h1 class="page-title">權限群組管理</h1>
<p class="page-description">管理系統群組及其成員,支援批次新增或移除</p>
</div>
<div class="role-manage-layout">
<!-- 左側:群組清單 -->
<div class="group-panel">
<h2 class="section-title">群組清單</h2>
<div class="group-item"
v-for="group in groupList"
:key="group.id"
:class="{ active: selectedGroup && selectedGroup.id === group.id }"
@click="selectGroup(group)">
<div class="group-name">{{ group.name }}</div>
<div class="group-description">{{ group.description }}</div>
</div>
</div>
<!-- 右側:人員清單 -->
<div class="member-panel">
<!-- 群組成員列表 -->
<div class="member-list-section">
<h2 class="section-title">
群組成員
<span v-if="selectedGroup"> ({{ selectedGroup.name }})</span>
</h2>
<div v-if="!selectedGroup" class="empty-message">
請先選擇一個群組
</div>
<div v-else-if="loadingMembers" class="loading-message">
載入中...
</div>
<template v-else>
<div class="member-list">
<div v-if="groupMembers.length === 0" class="empty-message">
此群組目前沒有成員
</div>
<div v-else
v-for="member in groupMembers"
:key="member.Value"
class="member-item"
:class="{ selected: isSelectedMember(member.Value) }"
@click="toggleMemberSelection(member.Value)">
<input type="checkbox"
class="member-checkbox"
:checked="isSelectedMember(member.Value)"
@click.stop="toggleMemberSelection(member.Value)">
<div class="member-info">
<span class="member-name">{{ member.Name }}</span>
<span class="member-id">({{ member.Value }})</span>
</div>
</div>
</div>
<div class="action-buttons">
<button class="btn btn-secondary"
:disabled="selectedMembers.length === 0"
@click="removeMembersFromGroup">
移除群組 ({{ selectedMembers.length }})
</button>
</div>
</template>
</div>
<!-- 人員查詢 -->
<div class="member-search-section">
<h2 class="section-title">人員查詢</h2>
<div class="search-box">
<input type="text"
v-model="searchKeyword"
class="search-input"
placeholder="輸入員工編號或姓名搜尋..."
@keyup.enter="searchEmployees">
<button class="btn btn-primary"
style="margin-top: 10px; width: 100%;"
@click="searchEmployees">
搜尋
</button>
</div>
<div v-if="loadingSearch" class="loading-message">
搜尋中...
</div>
<template v-else>
<div class="member-list">
<div v-if="searchResults.length === 0" class="empty-message">
{{ searchKeyword ? '查無結果' : '請輸入關鍵字進行搜尋' }}
</div>
<div v-else
v-for="employee in searchResults"
:key="employee.Value"
class="member-item"
:class="{ selected: isSelectedEmployee(employee.Value) }"
@click="toggleEmployeeSelection(employee.Value)">
<input type="checkbox"
class="member-checkbox"
:checked="isSelectedEmployee(employee.Value)"
@click.stop="toggleEmployeeSelection(employee.Value)">
<div class="member-info">
<span class="member-name">{{ employee.Name }}</span>
<span class="member-id">({{ employee.Value }})</span>
</div>
</div>
</div>
<div class="action-buttons">
<button class="btn btn-primary"
:disabled="!selectedGroup || selectedEmployees.length === 0"
@click="addEmployeesToGroup">
加入群組 ({{ selectedEmployees.length }})
</button>
</div>
</template>
</div>
</div>
</div>
</div>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="script" runat="server">
<script src="<%= ResolveUrlWithSysVer("~/Scripts/RoleManage.js") %>"></script>
</asp:Content>
+22
View File
@@ -0,0 +1,22 @@
using System;
using System.Web.UI;
namespace .Forms
{
public partial class RoleManage : PageBase
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// 檢查是否為系統管理員
if (Session[PublicVariable.IsSystemManager] == null ||
!(bool)Session[PublicVariable.IsSystemManager])
{
Response.Redirect("~/Forms/Home.aspx");
return;
}
}
}
}
}
+17
View File
@@ -0,0 +1,17 @@
//------------------------------------------------------------------------------
// <自動產生>
// 這段程式碼是由工具產生的。
//
// 變更這個檔案可能會導致不正確的行為,而且如果已重新產生
// 程式碼,則會遺失變更。
// </自動產生>
//------------------------------------------------------------------------------
namespace .Forms
{
public partial class RoleManage
{
}
}
+87
View File
@@ -0,0 +1,87 @@
<%@ Page Title="匯出參加人員" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="SignUpExport.aspx.cs" Inherits="報到系統.Forms.SignUpExport" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
<link href="<%= ResolveUrlWithSysVer("~/Content/SignUpExport.css") %>" rel="stylesheet" type="text/css" />
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<div id="signUpExportApp" class="page-container">
<div class="page-header">
<h1 class="page-title">匯出參加人員</h1>
</div>
<!-- 課程選擇面板 -->
<div class="search-panel">
<div class="search-row">
<div class="search-field-inline w-6">
<div class="search-input-group">
<label class="search-label-inline required">選擇課程</label>
<select v-model="selectedCourseID" class="search-input">
<option value="">-- 請選擇要匯出的課程 --</option>
<option v-for="course in courses" :key="course.ID" :value="course.ID">
{{ course.CourseName }} ({{ $format.date(course.CourseDate) }})
</option>
</select>
<label class="search-checkbox-label">
<input type="checkbox" v-model="includeClosed"> 包含已結案
</label>
</div>
</div>
<div class="search-field-inline w-3">
<label class="search-label-inline">簽到退時間格式</label>
<select v-model="timeFormat" class="search-input">
<option value="datetime">年月日時分秒</option>
<option value="time">時分</option>
</select>
</div>
</div>
</div>
<!-- 預覽與匯出面板 -->
<div class="export-panel" v-if="selectedCourseID">
<div class="preview-section" v-if="signupData">
<h3 class="section-title">準備匯出</h3>
<div class="course-info">
<div class="info-row">
<span class="info-label">匯出檔案:</span>
<span class="info-value">{{ signupData.FileName }}</span>
</div>
<div class="info-row">
<span class="info-label">參加人員數:</span>
<span class="info-value">{{ signupData.DataCount }} 人</span>
</div>
<div class="info-row">
<span class="info-label">檔案大小:</span>
<span class="info-value">{{ formatFileSize(signupData.FileSize) }}</span>
</div>
</div>
</div>
<!-- 匯出按鈕 -->
<div class="export-actions">
<button class="btn btn-success" @click="executeExport" :disabled="!signupData || exporting">
<span v-if="!exporting">📥 匯出為 Excel 檔案</span>
<span v-else>匯出中...</span>
</button>
<button class="btn btn-secondary" @click="clearSelection">清除選擇</button>
</div>
<!-- 匯出結果訊息 -->
<div class="result-section" v-if="exportMessage">
<div class="message-box" :class="{ success: exportSuccess, error: !exportSuccess }">
<span v-if="exportSuccess">✓ {{ exportMessage }}</span>
<span v-else>✗ {{ exportMessage }}</span>
</div>
</div>
</div>
<div class="empty-message" v-else style="padding: 40px;">
請先選擇要匯出的課程
</div>
</div>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="script" runat="server">
<script src="<%= ResolveUrlWithSysVer("~/Scripts/SignUpExport.js") %>"></script>
</asp:Content>
+18
View File
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace .Forms
{
public partial class SignUpExport : PageBase
{
protected void Page_Load(object sender, EventArgs e)
{
// SignUpExport.aspx 現在使用 Vue.js
// 所有邏輯都在前端和 SignUpService.asmx 處理
}
}
}
+157
View File
@@ -0,0 +1,157 @@
<%@ Page Title="匯入參加人員" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="SignUpImport.aspx.cs" Inherits="報到系統.Forms.SignUpImport" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
<link href="<%= ResolveUrlWithSysVer("~/Content/SignUpImport.css") %>" rel="stylesheet" type="text/css" />
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<div id="signUpImportApp" class="page-container">
<div class="page-header">
<h1 class="page-title">匯入參加人員</h1>
</div>
<!-- 課程選擇面板 -->
<div class="search-panel">
<div class="search-row">
<div class="search-field-inline w-6">
<div class="search-input-group">
<label class="search-label-inline required">選擇課程</label>
<select v-model="selectedCourseID" class="search-input">
<option value="">-- 請選擇課程 --</option>
<option v-for="course in courses" :key="course.ID" :value="course.ID">
{{ course.CourseName }} ({{ $format.date(course.CourseDate) }})
</option>
</select>
<label class="search-checkbox-label">
<input type="checkbox" v-model="includeClosed"> 包含已結案
</label>
</div>
</div>
</div>
</div>
<!-- 檔案上傳面板 -->
<div class="upload-panel" v-if="selectedCourseID">
<div class="upload-section">
<h3 class="section-title">步驟 1:選擇檔案</h3>
<div class="upload-area" :class="{ dragging: isDragging }" @dragover.prevent="isDragging = true" @dragleave="isDragging = false" @drop.prevent="onFileDrop">
<input type="file" ref="fileInput" @change="onFileSelected" accept=".xlsx" style="display: none;" :disabled="selectedFile !== null">
<button class="btn btn-primary" @click="$refs.fileInput.click()" :disabled="selectedFile !== null">選擇 .xlsx 檔案</button>
<span class="upload-text">或將檔案拖放到此處</span>
<div class="file-info" v-if="selectedFile">
<span class="file-name">📄 {{ selectedFile.name }}</span>
<span class="file-size">({{ formatFileSize(selectedFile.size) }})</span>
</div>
</div>
</div>
<!-- 預覽與驗證面板 -->
<div class="preview-section" v-if="previewData.length > 0">
<h3 class="section-title">步驟 2:預覽及驗證</h3>
<div class="validation-summary" v-if="validationErrors.length > 0">
<div class="error-box">
<div class="error-title">⚠️ 發現 {{ validationErrors.length }} 個驗證錯誤</div>
<div class="error-list">
<div class="error-item" v-for="(error, index) in validationErrors" :key="index">
{{ error }}
</div>
</div>
</div>
</div>
<div class="preview-info">
預計匯入 {{ validRowCount }} 筆資料(共讀取 {{ previewData.length }} 列)
</div>
<div class="preview-table-wrapper">
<table class="preview-table">
<thead>
<tr>
<th class="text-center" style="width: 50px;">列</th>
<th>姓名</th>
<th>手機號</th>
<th>身分別</th>
<th>身分證字號</th>
<th>餐點</th>
<th>電子郵件</th>
<th>失業給付認定次數</th>
<th>性別</th>
<th class="text-center" style="width: 60px;">狀態</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in previewData" :key="index" :class="{ 'error-row': item.errors.length > 0 }">
<td class="text-center">{{ index + 1 }}</td>
<td>{{ item.Name }}</td>
<td>{{ item.Mobile }}</td>
<td>{{ item.IDType || '未設定' }}</td>
<td>{{ item.IDNumber || '未填' }}</td>
<td>{{ item.MealType || '未設定' }}</td>
<td>{{ item.Email || '未填' }}</td>
<td>{{ item.LossJobTimes || '未填' }}</td>
<td>{{ item.Gender || '未填' }}</td>
<td class="text-center">
<span class="status-badge" v-if="item.errors.length === 0" :class="{ ok: true }">✓</span>
<span class="status-badge" v-else :class="{ error: true }">✗</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 匯入按鈕 -->
<div class="import-section" v-if="previewData.length > 0">
<h3 class="section-title">步驟 3:執行匯入</h3>
<div class="import-tips">
<div class="tip-item">️ 簽到時間和簽退時間不會匯入</div>
<div class="tip-item">️ 報名方式均為預約</div>
</div>
<div class="import-options">
<label class="checkbox-label">
<input type="checkbox" v-model="clearBefore" />
<span>先清除再匯入(刪除該課程的所有現有記錄)</span>
</label>
</div>
<button class="btn btn-success" @click="executeImport" :disabled="validRowCount === 0 || importing">
<span v-if="!importing">匯入 {{ validRowCount }} 筆資料</span>
<span v-else>匯入中...</span>
</button>
<button class="btn btn-secondary" @click="resetUpload">重新選擇檔案</button>
</div>
<!-- 匯入結果 -->
<div class="result-section" v-if="importResult">
<div class="result-box" :class="{ success: importResult.success, error: !importResult.success }">
<div class="result-title">{{ importResult.success ? '✓ 匯入完成' : '✗ 匯入失敗' }}</div>
<div class="result-message">{{ importResult.message }}</div>
<div class="result-details" v-if="importResult.details">
<span>成功:{{ importResult.details.SuccessCount }} 筆</span>
<span v-if="importResult.details.SkippedCount > 0">跳過:{{ importResult.details.SkippedCount }} 筆</span>
<span v-if="importResult.details.ErrorCount > 0">失敗:{{ importResult.details.ErrorCount }} 筆</span>
</div>
<div class="skipped-records" v-if="importResult.details && importResult.details.SkippedRecords && importResult.details.SkippedRecords.length > 0">
<div class="skipped-title">❌ 跳過的重複記錄:</div>
<div class="skipped-list">
<div class="skipped-item" v-for="(record, index) in importResult.details.SkippedRecords.slice(0, 10)" :key="index">
{{ record }}
</div>
<div class="skipped-item" v-if="importResult.details.SkippedRecords.length > 10" style="color: #666;">
... 還有 {{ importResult.details.SkippedRecords.length - 10 }} 筆
</div>
</div>
</div>
</div>
</div>
</div>
<div class="empty-message" v-else style="padding: 40px;">
請先選擇要匯入的課程
</div>
</div>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="script" runat="server">
<script src="<%= ResolveUrlWithSysVer("~/Scripts/SignUpImport.js") %>"></script>
</asp:Content>
+18
View File
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace .Forms
{
public partial class SignUpImport : PageBase
{
protected void Page_Load(object sender, EventArgs e)
{
// SignUpImport.aspx 現在使用 Vue.js
// 所有邏輯都在前端和 SignUpService.asmx 處理
}
}
}
+305
View File
@@ -0,0 +1,305 @@
<%@ Page Title="課程參加人員" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="SignUpList.aspx.cs" Inherits="報到系統.Forms.SignUpList" %>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
<link href="<%= ResolveUrlWithSysVer("~/Content/SignUpList.css") %>" rel="stylesheet" type="text/css" />
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<div id="signUpApp" class="page-container">
<div class="page-header">
<h1 class="page-title">課程參加人員</h1>
</div>
<!-- 課程選擇面板 -->
<div class="search-panel">
<div class="search-row">
<div class="search-field-inline w-6">
<label class="search-label-inline">選擇課程</label>
<div class="search-input-group">
<select v-model="selectedCourseID" class="search-input" @change="onCourseSelected">
<option value="">-- 請選擇課程 --</option>
<option v-for="course in courses" :key="course.ID" :value="course.ID">
{{ course.CourseName }} ({{ $format.date(course.CourseDate) }})
</option>
</select>
<label class="search-checkbox-label">
<input type="checkbox" v-model="includeClosed"> 包含已結案
</label>
</div>
</div>
<div class="search-field-inline w-3">
<label class="search-label-inline">序號</label>
<input type="number" v-model.number="search.seqNo" class="search-input" placeholder="請輸入序號" @keyup.enter="performSearch">
</div>
<div class="search-field-inline w-3">
<label class="search-label-inline">姓名搜尋</label>
<input type="text" v-model="search.name" class="search-input" placeholder="請輸入姓名" @keyup.enter="performSearch">
</div>
<div class="search-field-inline w-3">
<label class="search-label-inline">手機搜尋</label>
<input type="text" v-model="search.mobile" class="search-input" placeholder="請輸入手機號" @keyup.enter="performSearch">
</div>
<div class="search-field-inline w-3">
<label class="search-label-inline">身分別</label>
<select v-model="search.idType" class="search-input">
<option value="">-- 全部 --</option>
<option value="___EMPTY___">-- 未輸入 --</option>
<option value="失業給付">失業給付</option>
<option value="一般民眾">一般民眾</option>
<option value="青年尋職">青年尋職</option>
</select>
</div>
<div class="search-field-inline w-3">
<label class="search-label-inline">身分證字號</label>
<div class="search-input-group">
<input type="text" v-model="search.idNumber" class="search-input" placeholder="請輸入身分證字號" @keyup.enter="performSearch">
<label class="search-checkbox-label">
<input type="checkbox" v-model="search.idNumberEmpty"> 未輸入
</label>
</div>
</div>
<div class="search-field-inline w-3">
<label class="search-label-inline">餐點</label>
<select v-model="search.mealType" class="search-input">
<option value="">-- 全部 --</option>
<option value="___EMPTY___">-- 未輸入 --</option>
<option value="素食">素食</option>
<option value="非素食">非素食</option>
<option value="不用餐">不用餐</option>
</select>
</div>
<div class="search-field-inline w-3">
<label class="search-label-inline">電子郵件</label>
<div class="search-input-group">
<input type="text" v-model="search.email" class="search-input" placeholder="請輸入電子郵件" @keyup.enter="performSearch">
<label class="search-checkbox-label">
<input type="checkbox" v-model="search.emailEmpty"> 未輸入
</label>
</div>
</div>
</div>
<div class="search-actions">
<button class="btn btn-secondary" @click="clearSearch">清除</button>
<button class="btn btn-primary" @click="performSearch">搜尋</button>
</div>
</div>
<!-- 搜尋結果 -->
<div class="result-panel" v-if="selectedCourseID">
<div class="result-header">
<span class="result-title">參加人員清單</span>
<div class="header-actions">
<!-- 批發簡訊功能區 -->
<div class="sms-send-compact">
<span class="sms-selected-count">發送簡訊已選 {{ selectedIds.length }} 人</span>
<button class="btn btn-secondary btn-sm" @click="clearSmsSelection"
:disabled="selectedIds.length === 0">清除已選</button>
<button class="btn btn-primary btn-sm" @click="sendSmsBatch"
:disabled="selectedIds.length === 0 || sendingSmsBatch">
{{ sendingSmsBatch ? '發送中...' : '發送簡訊' }}
</button>
<button class="btn btn-secondary btn-sm" @click="checkBalance">查詢餘額</button>
</div>
</div>
</div>
<table class="result-table">
<thead>
<tr>
<th class="text-center" style="width: 50px;">
<input type="checkbox" v-model="selectAllCurrentPage" @change="toggleSelectAll" :indeterminate="hasIndeterminate">
</th>
<th class="text-center" style="width: 50px;">序號</th>
<th class="text-center">姓名</th>
<th class="text-center">性別</th>
<th class="text-center">手機號</th>
<th class="text-center">身分別</th>
<th class="text-center">身分證字號</th>
<th class="text-center">餐點</th>
<th class="text-center">電子郵件</th>
<th class="text-center">失業給付認定次數</th>
<th class="text-center">報名方式</th>
<th class="text-center">簡訊發送</th>
<th class="text-center">簽到時間</th>
<th class="text-center">簽退時間</th>
<th class="text-center">操作
<button class="btn btn-success btn-sm" style="margin-left: 10px;" @click="openAddModal">新增</button>
</th>
</tr>
</thead>
<tbody>
<tr v-if="loading">
<td colspan="15" class="empty-message">載入中...</td>
</tr>
<tr v-else-if="signups.length === 0">
<td colspan="15" class="empty-message">查無資料</td>
</tr>
<tr v-else v-for="item in signups" :key="item.ID">
<td class="text-center">
<input type="checkbox" :value="item.ID" v-model="selectedIds" @change="updateSelectAllState">
</td>
<td class="text-center">{{ item.SeqNo }}</td>
<td>{{ item.Name }}</td>
<td class="text-center">{{ item.Gender || '未設定' }}</td>
<td class="text-center">{{ item.Mobile }}</td>
<td class="text-center">{{ item.IDType || '未設定' }}</td>
<td class="text-center">{{ item.IDNumber || '未填' }}</td>
<td class="text-center">{{ item.MealType || '未設定' }}</td>
<td class="text-center">{{ item.Email || '未填' }}</td>
<td class="text-center">{{ item.LossJobTimes !== null && item.LossJobTimes !== undefined ? item.LossJobTimes : '未填' }}</td>
<td class="text-center">{{ item.SignUpType || '未設定' }}</td>
<td class="text-center">{{ item.MessageSending }}</td>
<td class="text-center">{{ $format.dateTime(item.CheckInTime) }}</td>
<td class="text-center">{{ $format.dateTime(item.CheckOutTime) }}</td>
<td class="text-center">
<button class="btn btn-primary btn-sm" @click="openEditModal(item)">更正</button>
<button class="btn btn-danger btn-sm" @click="deleteSignUp(item)">刪除</button>
</td>
</tr>
</tbody>
</table>
<!-- 分頁 -->
<div class="pagination" v-if="totalCount > 0">
<div class="pagination-control">
<button class="btn btn-secondary btn-sm" :disabled="currentPage === 1" @click="goToFirstPage">第一頁</button>
<button class="btn btn-secondary btn-sm" :disabled="currentPage === 1" @click="goToPage(currentPage - 1)">上一頁</button>
<span class="pagination-label">第</span>
<select v-model.number="currentPage" class="pagination-select" @change="loadSignUps">
<option v-for="page in totalPages" :key="page" :value="page">{{ page }}</option>
</select>
<span class="pagination-label">頁</span>
<button class="btn btn-secondary btn-sm" :disabled="currentPage === totalPages" @click="goToPage(currentPage + 1)">下一頁</button>
<button class="btn btn-secondary btn-sm" :disabled="currentPage === totalPages" @click="goToLastPage">最後頁</button>
<span class="pagination-label">|</span>
<span class="pagination-label">每頁</span>
<select v-model.number="pageSize" class="pagination-select" @change="onPageSizeChanged">
<option :value="10">10</option>
<option :value="20">20</option>
<option :value="50">50</option>
<option :value="100">100</option>
</select>
<span class="pagination-label">筆,共</span>
<span class="pagination-count">{{ totalCount }}</span>
<span class="pagination-label">筆</span>
</div>
</div>
</div>
<div class="result-panel" v-else>
<div class="empty-message" style="padding: 40px;">請先選擇要查詢的課程</div>
</div>
<!-- 新增/更正彈窗 -->
<div class="modal" :class="{ show: showModal }">
<div class="modal-dialog">
<div class="modal-header">
<h3 class="modal-title">{{ isNewMode ? '新增參加人員' : '更正參加人員' }}</h3>
</div>
<div class="modal-body">
<!-- 第一行:姓名、手機號 -->
<div class="form-row">
<div class="form-col-50">
<div class="form-group-inline">
<label class="form-label-inline required">姓名</label>
<input type="text" v-model="editItem.Name" class="form-control" maxlength="100" placeholder="請輸入姓名">
</div>
<div class="error-message" v-if="validationErrors.Name">{{ validationErrors.Name }}</div>
</div>
<div class="form-col-50">
<div class="form-group-inline">
<label class="form-label-inline required">手機號</label>
<input type="text" v-model="editItem.Mobile" class="form-control" maxlength="20" placeholder="請輸入手機號">
</div>
<div class="error-message" v-if="validationErrors.Mobile">{{ validationErrors.Mobile }}</div>
</div>
</div>
<!-- 第二行:身分別、身分證字號 -->
<div class="form-row">
<div class="form-col-50">
<div class="form-group-inline">
<label class="form-label-inline">身分別</label>
<select v-model="editItem.IDType" class="form-control">
<option value="">-- 請選擇 --</option>
<option value="失業給付">失業給付</option>
<option value="一般民眾">一般民眾</option>
<option value="青年尋職">青年尋職</option>
</select>
</div>
</div>
<div class="form-col-50">
<div class="form-group-inline">
<label class="form-label-inline">身分證字號</label>
<input type="text" v-model="editItem.IDNumber" class="form-control" maxlength="20" placeholder="請輸入身分證字號">
</div>
<div class="error-message" v-if="validationErrors.IDNumber">{{ validationErrors.IDNumber }}</div>
</div>
</div>
<!-- 第三行:餐點、報名方式 -->
<div class="form-row">
<div class="form-col-50">
<div class="form-group-inline">
<label class="form-label-inline">餐點</label>
<select v-model="editItem.MealType" class="form-control">
<option value="">-- 請選擇 --</option>
<option value="素食">素食</option>
<option value="非素食">非素食</option>
<option value="不用餐">不用餐</option>
</select>
</div>
</div>
<div class="form-col-50">
<div class="form-group-inline">
<label class="form-label-inline">報名方式</label>
<select v-model="editItem.SignUpType" class="form-control">
<option value="">-- 請選擇 --</option>
<option value="預約">預約</option>
<option value="現場">現場</option>
</select>
</div>
</div>
</div>
<!-- 第四行:電子郵件 -->
<div class="form-row">
<div class="form-col-100">
<div class="form-group-inline">
<label class="form-label-inline">電子郵件</label>
<input type="text" v-model="editItem.Email" class="form-control" maxlength="100" placeholder="請輸入電子郵件">
</div>
<div class="error-message" v-if="validationErrors.Email">{{ validationErrors.Email }}</div>
</div>
</div>
<!-- 第五行:性別、失業給付認定次數 -->
<div class="form-row">
<div class="form-col-50">
<div class="form-group-inline">
<label class="form-label-inline">性別</label>
<select v-model="editItem.Gender" class="form-control">
<option value="">-- 請選擇 --</option>
<option value="男">男</option>
<option value="女">女</option>
<option value="其他">其他</option>
</select>
</div>
</div>
<div class="form-col-50">
<div class="form-group-inline">
<label class="form-label-inline">失業給付認定次數</label>
<input type="number" v-model.number="editItem.LossJobTimes" class="form-control" min="0" placeholder="請輸入次數">
</div>
<div class="error-message" v-if="validationErrors.LossJobTimes">{{ validationErrors.LossJobTimes }}</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" @click="closeModal">取消</button>
<button class="btn btn-primary" @click="saveSignUp">存檔</button>
</div>
</div>
</div>
</div>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="script" runat="server">
<script src="<%= ResolveUrlWithSysVer("~/Scripts/SignUpList.js") %>"></script>
</asp:Content>
+18
View File
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace .Forms
{
public partial class SignUpList : PageBase
{
protected void Page_Load(object sender, EventArgs e)
{
// SignUpList.aspx 現在使用 Vue.js
// 所有邏輯都在前端和 SignUpService.asmx 處理
}
}
}