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
+12
View File
@@ -0,0 +1,12 @@
# All files
[*]
indent_style = space
# Xml files
[*.xml]
indent_size = 2
# 設定所有文字檔案使用 UTF-8 編碼
[*]
charset = utf-8
+158
View File
@@ -0,0 +1,158 @@
namespace
{
partial class AgreementConfirmForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.label1 = new System.Windows.Forms.Label();
this.lbAgreedTerms = new System.Windows.Forms.Label();
this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel();
this.btnNo = new System.Windows.Forms.Button();
this.btnYes = new System.Windows.Forms.Button();
this.tableLayoutPanel1.SuspendLayout();
this.flowLayoutPanel1.SuspendLayout();
this.SuspendLayout();
//
// tableLayoutPanel1
//
this.tableLayoutPanel1.BackColor = System.Drawing.Color.Cornsilk;
this.tableLayoutPanel1.ColumnCount = 1;
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel1.Controls.Add(this.flowLayoutPanel1, 0, 2);
this.tableLayoutPanel1.Controls.Add(this.lbAgreedTerms, 0, 1);
this.tableLayoutPanel1.Controls.Add(this.label1, 0, 0);
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
this.tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(4);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.Padding = new System.Windows.Forms.Padding(24);
this.tableLayoutPanel1.RowCount = 3;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 70F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 70F));
this.tableLayoutPanel1.Size = new System.Drawing.Size(975, 603);
this.tableLayoutPanel1.TabIndex = 1;
//
// label1
//
this.label1.AutoSize = true;
this.label1.Dock = System.Windows.Forms.DockStyle.Fill;
this.label1.Location = new System.Drawing.Point(28, 24);
this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(919, 70);
this.label1.TabIndex = 1;
this.label1.Text = "有以下事項需要你同意:";
this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// lbAgreedTerms
//
this.lbAgreedTerms.AutoSize = true;
this.lbAgreedTerms.Dock = System.Windows.Forms.DockStyle.Fill;
this.lbAgreedTerms.Font = new System.Drawing.Font("微軟正黑體", 32F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(136)));
this.lbAgreedTerms.ForeColor = System.Drawing.Color.Blue;
this.lbAgreedTerms.Location = new System.Drawing.Point(28, 94);
this.lbAgreedTerms.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lbAgreedTerms.Name = "lbAgreedTerms";
this.lbAgreedTerms.Size = new System.Drawing.Size(919, 415);
this.lbAgreedTerms.TabIndex = 8;
this.lbAgreedTerms.Text = "(同意事項)(同意事項)(同意事項)(同意事項)(同意事項)(同意事項)(同意事項)(同意事項)(同意事項)(同意事項)(同意事項)(同意事項)(同意事項)(同意" +
"事項)\r\n";
this.lbAgreedTerms.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// flowLayoutPanel1
//
this.flowLayoutPanel1.Controls.Add(this.btnNo);
this.flowLayoutPanel1.Controls.Add(this.btnYes);
this.flowLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.flowLayoutPanel1.FlowDirection = System.Windows.Forms.FlowDirection.RightToLeft;
this.flowLayoutPanel1.Location = new System.Drawing.Point(24, 509);
this.flowLayoutPanel1.Margin = new System.Windows.Forms.Padding(0);
this.flowLayoutPanel1.Name = "flowLayoutPanel1";
this.flowLayoutPanel1.Padding = new System.Windows.Forms.Padding(0, 5, 0, 0);
this.flowLayoutPanel1.Size = new System.Drawing.Size(927, 70);
this.flowLayoutPanel1.TabIndex = 9;
//
// btnNo
//
this.btnNo.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.btnNo.BackColor = System.Drawing.Color.HotPink;
this.btnNo.DialogResult = System.Windows.Forms.DialogResult.No;
this.btnNo.Location = new System.Drawing.Point(798, 9);
this.btnNo.Margin = new System.Windows.Forms.Padding(4);
this.btnNo.Name = "btnNo";
this.btnNo.Size = new System.Drawing.Size(125, 52);
this.btnNo.TabIndex = 1;
this.btnNo.TabStop = false;
this.btnNo.Text = "N 否";
this.btnNo.UseVisualStyleBackColor = false;
//
// btnYes
//
this.btnYes.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.btnYes.BackColor = System.Drawing.Color.Chartreuse;
this.btnYes.DialogResult = System.Windows.Forms.DialogResult.Yes;
this.btnYes.Location = new System.Drawing.Point(665, 9);
this.btnYes.Margin = new System.Windows.Forms.Padding(4);
this.btnYes.Name = "btnYes";
this.btnYes.Size = new System.Drawing.Size(125, 52);
this.btnYes.TabIndex = 0;
this.btnYes.TabStop = false;
this.btnYes.Text = "Y 是";
this.btnYes.UseVisualStyleBackColor = false;
//
// AgreementConfirmForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
this.ClientSize = new System.Drawing.Size(975, 603);
this.Controls.Add(this.tableLayoutPanel1);
this.Font = new System.Drawing.Font("微軟正黑體", 24F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(136)));
this.Margin = new System.Windows.Forms.Padding(11, 10, 11, 10);
this.Name = "AgreementConfirmForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
this.Text = "同意事項確認";
this.Shown += new System.EventHandler(this.AgreementConfirmForm_Shown);
this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.AgreementConfirmForm_KeyDown);
this.tableLayoutPanel1.ResumeLayout(false);
this.tableLayoutPanel1.PerformLayout();
this.flowLayoutPanel1.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1;
private System.Windows.Forms.Button btnNo;
private System.Windows.Forms.Button btnYes;
private System.Windows.Forms.Label lbAgreedTerms;
}
}
@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml.Linq;
namespace
{
public partial class AgreementConfirmForm : Form
{
public string { get { return lbAgreedTerms.Text; } set { lbAgreedTerms.Text = value; } }
public AgreementConfirmForm()
{
InitializeComponent();
}
private void AgreementConfirmForm_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Y)
{
DialogResult = DialogResult.Yes;
e.SuppressKeyPress = true;
return;
}
if (e.KeyCode == Keys.N)
{
DialogResult = DialogResult.No;
e.SuppressKeyPress = true;
return;
}
}
private void AgreementConfirmForm_Shown(object sender, EventArgs e)
{
Services.NotificationSoundService.Ding();
}
}
}
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
+38
View File
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<section name="報到系統客戶端.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false"/>
</sectionGroup>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
</startup>
<system.serviceModel>
<bindings/>
<client/>
</system.serviceModel>
<System.Windows.Forms.ApplicationConfigurationSection>
<add key="DpiAwareness" value="PerMonitorV2" />
<add key="EnableWindowsFormsHighDpiAutoResizing" value="true" />
</System.Windows.Forms.ApplicationConfigurationSection>
<applicationSettings>
<報到系統客戶端.Properties.Settings>
<setting name="ApiBaseUrl" serializeAs="String">
<value>http://localhost:58792</value>
</setting>
</報到系統客戶端.Properties.Settings>
</applicationSettings>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="SQLitePCLRaw.core" publicKeyToken="1488e028ca7ab535" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-3.0.2.2801" newVersion="3.0.2.2801"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
@@ -0,0 +1,66 @@
# 設定檔位置說明
## 問題背景
報到系統客戶端安裝在 `Program Files` 資料夾時,由於 Windows 安全限制,應用程式在執行期間無法修改 `Program Files` 目錄下的檔案(包括 `App.config`)。
## 解決方案
設定檔已改為存儲在用戶的 **AppData** 資料夾中,具有完整的寫入權限。
## 設定檔位置
### 用戶設定檔(可寫入)
```
%LOCALAPPDATA%\報到系統客戶端\AppSettings.config
```
展開後的實際路徑示例:
```
C:\Users\[用戶名]\AppData\Local\報到系統客戶端\AppSettings.config
```
### 預設設定檔(唯讀)
```
[應用程式安裝目錄]\報到系統客戶端.exe.config
```
## 如何運作
1. **首次運行**
- 應用程式檢查用戶 AppData 資料夾中是否存在 `AppSettings.config`
- 如果不存在,系統會自動建立並初始化預設設定
2. **讀取設定**
- 優先從用戶 AppData 資料夾中的 `AppSettings.config` 讀取
- 如果失敗,回退到 `App.config` 中的預設值
3. **寫入設定**
- 所有設定變更都會保存到用戶 AppData 資料夾中的 `AppSettings.config`
- 不會修改 `Program Files` 下的原始檔案
## 優勢
✓ 解決 Program Files 權限問題
✓ 使用標準的 Windows AppData 位置
✓ 每個用戶可擁有獨立的設定
✓ 支持應用程式自動更新
✓ 卸載應用程式時設定保留(用戶可選擇刪除)
## API 基礎 URL 更新
使用 `ConfigService.SetApiBaseUrl()` 方法更新 API 基礎 URL 時,變更會自動保存到用戶的設定檔:
```csharp
// 設定新的 API 基礎 URL
bool success = ConfigService.Instance.SetApiBaseUrl("http://new-api-server:8080");
// 讀取目前的 API 基礎 URL
string currentUrl = ConfigService.Instance.GetApiBaseUrl();
```
## 安裝程式更新
在安裝自訂動作中(`ConfigInstaller.cs`),如有必要可以:
- 在首次安裝時預先初始化用戶設定檔
- 或保持現有邏輯,讓應用程式在首次運行時自動初始化
當前實現選擇由應用程式在首次運行時自動初始化,無需修改安裝程式邏輯。
+216
View File
@@ -0,0 +1,216 @@
namespace
{
partial class CheckoutOnlyConfirmForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.label3 = new System.Windows.Forms.Label();
this.lbName = new System.Windows.Forms.Label();
this.lbMobile = new System.Windows.Forms.Label();
this.label4 = new System.Windows.Forms.Label();
this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel();
this.btnNo = new System.Windows.Forms.Button();
this.btnYes = new System.Windows.Forms.Button();
this.tableLayoutPanel1.SuspendLayout();
this.flowLayoutPanel1.SuspendLayout();
this.SuspendLayout();
//
// tableLayoutPanel1
//
this.tableLayoutPanel1.ColumnCount = 4;
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 24F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 142F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 24F));
this.tableLayoutPanel1.Controls.Add(this.label1, 1, 1);
this.tableLayoutPanel1.Controls.Add(this.label2, 1, 2);
this.tableLayoutPanel1.Controls.Add(this.label3, 1, 3);
this.tableLayoutPanel1.Controls.Add(this.lbName, 2, 2);
this.tableLayoutPanel1.Controls.Add(this.lbMobile, 2, 3);
this.tableLayoutPanel1.Controls.Add(this.label4, 1, 4);
this.tableLayoutPanel1.Controls.Add(this.flowLayoutPanel1, 1, 5);
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
this.tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(4);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.RowCount = 7;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 24F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 56F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 56F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 56F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 56F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 56F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 24F));
this.tableLayoutPanel1.Size = new System.Drawing.Size(506, 328);
this.tableLayoutPanel1.TabIndex = 1;
//
// label1
//
this.label1.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.label1.AutoSize = true;
this.tableLayoutPanel1.SetColumnSpan(this.label1, 3);
this.label1.Location = new System.Drawing.Point(28, 32);
this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(369, 40);
this.label1.TabIndex = 0;
this.label1.Text = "此位學員沒有簽到時間:";
//
// label2
//
this.label2.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(81, 88);
this.label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(81, 40);
this.label2.TabIndex = 1;
this.label2.Text = "姓名";
//
// label3
//
this.label3.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(49, 144);
this.label3.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(113, 40);
this.label3.TabIndex = 2;
this.label3.Text = "手機號";
//
// lbName
//
this.lbName.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.lbName.AutoSize = true;
this.lbName.Font = new System.Drawing.Font("微軟正黑體", 24F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(136)));
this.lbName.ForeColor = System.Drawing.Color.Blue;
this.lbName.Location = new System.Drawing.Point(170, 88);
this.lbName.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lbName.Name = "lbName";
this.lbName.Size = new System.Drawing.Size(81, 40);
this.lbName.TabIndex = 3;
this.lbName.Text = "姓名";
//
// lbMobile
//
this.lbMobile.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.lbMobile.AutoSize = true;
this.lbMobile.Font = new System.Drawing.Font("微軟正黑體", 24F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(136)));
this.lbMobile.ForeColor = System.Drawing.Color.Blue;
this.lbMobile.Location = new System.Drawing.Point(170, 144);
this.lbMobile.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lbMobile.Name = "lbMobile";
this.lbMobile.Size = new System.Drawing.Size(113, 40);
this.lbMobile.TabIndex = 4;
this.lbMobile.Text = "手機號";
//
// label4
//
this.label4.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.label4.AutoSize = true;
this.tableLayoutPanel1.SetColumnSpan(this.label4, 3);
this.label4.Location = new System.Drawing.Point(28, 200);
this.label4.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(241, 40);
this.label4.TabIndex = 5;
this.label4.Text = "是否確認簽退?";
//
// flowLayoutPanel1
//
this.tableLayoutPanel1.SetColumnSpan(this.flowLayoutPanel1, 2);
this.flowLayoutPanel1.Controls.Add(this.btnNo);
this.flowLayoutPanel1.Controls.Add(this.btnYes);
this.flowLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.flowLayoutPanel1.FlowDirection = System.Windows.Forms.FlowDirection.RightToLeft;
this.flowLayoutPanel1.Location = new System.Drawing.Point(24, 248);
this.flowLayoutPanel1.Margin = new System.Windows.Forms.Padding(0);
this.flowLayoutPanel1.Name = "flowLayoutPanel1";
this.flowLayoutPanel1.Size = new System.Drawing.Size(458, 56);
this.flowLayoutPanel1.TabIndex = 6;
//
// btnNo
//
this.btnNo.DialogResult = System.Windows.Forms.DialogResult.No;
this.btnNo.Location = new System.Drawing.Point(329, 4);
this.btnNo.Margin = new System.Windows.Forms.Padding(4);
this.btnNo.Name = "btnNo";
this.btnNo.Size = new System.Drawing.Size(125, 52);
this.btnNo.TabIndex = 1;
this.btnNo.TabStop = false;
this.btnNo.Text = "N 否";
this.btnNo.UseVisualStyleBackColor = true;
//
// btnYes
//
this.btnYes.DialogResult = System.Windows.Forms.DialogResult.Yes;
this.btnYes.Location = new System.Drawing.Point(196, 4);
this.btnYes.Margin = new System.Windows.Forms.Padding(4);
this.btnYes.Name = "btnYes";
this.btnYes.Size = new System.Drawing.Size(125, 52);
this.btnYes.TabIndex = 0;
this.btnYes.TabStop = false;
this.btnYes.Text = "Y 是";
this.btnYes.UseVisualStyleBackColor = true;
//
// CheckoutOnlyConfirmForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
this.ClientSize = new System.Drawing.Size(506, 328);
this.Controls.Add(this.tableLayoutPanel1);
this.Font = new System.Drawing.Font("微軟正黑體", 24F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(136)));
this.KeyPreview = true;
this.Name = "CheckoutOnlyConfirmForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
this.Text = "簽退確認";
this.Shown += new System.EventHandler(this.CheckoutOnlyConfirmForm_Shown);
this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.CheckoutOnlyConfirmForm_KeyDown);
this.tableLayoutPanel1.ResumeLayout(false);
this.tableLayoutPanel1.PerformLayout();
this.flowLayoutPanel1.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.Label lbName;
private System.Windows.Forms.Label lbMobile;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1;
private System.Windows.Forms.Button btnNo;
private System.Windows.Forms.Button btnYes;
}
}
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace
{
public partial class CheckoutOnlyConfirmForm : Form
{
public string { get { return lbName.Text; } set { lbName.Text = value; } }
public string { get { return lbMobile.Text; } set { lbMobile.Text = value; } }
public CheckoutOnlyConfirmForm()
{
InitializeComponent();
}
private void CheckoutOnlyConfirmForm_Shown(object sender, EventArgs e)
{
Services.NotificationSoundService.Ding();
}
private void CheckoutOnlyConfirmForm_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Y)
{
DialogResult = DialogResult.Yes;
e.SuppressKeyPress = true;
return;
}
if (e.KeyCode == Keys.N)
{
DialogResult = DialogResult.No;
e.SuppressKeyPress = true;
return;
}
}
}
}
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
+300
View File
@@ -0,0 +1,300 @@
using System;
using System.Windows.Forms;
using .Data.Repositories;
using .Models;
using .Services;
namespace
{
/// <summary>
/// Session 狀態改變事件委派
/// </summary>
public delegate void OnModeChangeEventHandler(object sender, ModeChangeEventArgs e);
/// <summary>
/// Session 狀態改變事件參數
/// </summary>
public class ModeChangeEventArgs : EventArgs
{
public bool OnlineMode { get; set; }
public string UserID { get; set; }
public string UserName { get; set; }
public DateTime ChangeTime { get; set; }
}
/// <summary>
/// 客戶端 Session 管理
/// 使用新的 ApiService 進行 API 呼叫
/// </summary>
public class ClientSession
{
private static ClientSession instance = null;
private static readonly object lockObject = new object();
private string userID = string.Empty;
private string userName = string.Empty;
private bool onlineMode = false;
private DateTime loginTime = DateTime.MinValue;
/// <summary>
/// Session 狀態改變事件
/// </summary>
public static event OnModeChangeEventHandler OnModeChange;
private ClientSession()
{
}
private static void InitInstance()
{
if (instance == null)
{
lock (lockObject)
{
if (instance == null)
instance = new ClientSession();
}
}
}
/// <summary>
/// 觸發 Session 狀態改變事件
/// </summary>
private static void RaiseModeChangeEvent()
{
OnModeChange?.Invoke(null, new ModeChangeEventArgs
{
OnlineMode = instance.onlineMode,
UserID = instance.userID,
UserName = instance.userName,
ChangeTime = DateTime.Now
});
}
/// <summary>
/// 程式版本號
/// </summary>
public static string Version
{
get
{
var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
return string.Format("版本 {0}.{1}.{2}", version.Major, version.Minor, version.Build);
// Build: 8
// Major: 2026
// MajorRevision: 0
// Minor: 2
// MinorRevision: 0
// Revision: 0
}
}
/// <summary>
/// 取得目前登入的使用者ID
/// </summary>
public static string UserID
{
get
{
InitInstance();
return instance.userID;
}
}
/// <summary>
/// 取得目前登入的使用者名稱
/// </summary>
public static string UserName
{
get
{
InitInstance();
return instance.userName;
}
}
/// <summary>
/// 取得是否為連線模式
/// </summary>
public static bool OnlineMode
{
get
{
InitInstance();
return instance.onlineMode;
}
}
/// <summary>
/// 取得登入時間
/// </summary>
public static DateTime LoginTime
{
get
{
InitInstance();
return instance.loginTime;
}
}
/// <summary>
/// 設定登入資訊
/// </summary>
internal static void SetLoginInfo(string userID, string userName, bool onLineMode)
{
InitInstance();
instance.userID = userID;
instance.userName = userName;
instance.onlineMode = onLineMode;
instance.loginTime = DateTime.Now;
RaiseModeChangeEvent();
}
/// <summary>
/// 檢查是否已登入,若未登入則導向登入畫面
/// </summary>
public static void CheckLogin()
{
InitInstance();
BaseResponse response = LoginService.CheckSession();
if (!response.Success)
{
ShowLoginForm();
}
}
/// <summary>
/// 進行心跳檢測
/// </summary>
public static void HeartBeat()
{
InitInstance();
LoginService.CheckSession();
}
/// <summary>
/// 進行通訊測試
/// </summary>
public static bool Ping()
{
InitInstance();
return LoginService.Ping();
}
/// <summary>
/// 執行登入(非同步版本)
/// </summary>
public static async System.Threading.Tasks.Task<(bool, string)> LoginAsync(string username, string password)
{
BaseResponse response = await LoginService.LoginAsync(username, password);
if (response.Success)
{
try
{
// 解析登入回應資料
dynamic data = response.Data;
string returnedUserID = data?.UserID ?? username;
string returnedUsername = data?.UserName ?? username;
SetLoginInfo(returnedUserID, returnedUsername, true);
return (true, string.Empty);
}
catch (Exception ex)
{
SetLoginInfo(string.Empty, string.Empty, false);
return (false, string.Format("登入錯誤:{0}", ex.Message));
}
}
return (false, response.Message);
}
/// <summary>
/// 執行登入(同步版本)
/// </summary>
public static bool Login(string username, string password, out string message)
{
message = string.Empty;
BaseResponse response = LoginService.Login(username, password);
if (response.Success)
{
try
{
// 解析登入回應資料
dynamic data = response.Data;
string returnedUserID = data?.UserID ?? username;
string returnedUsername = data?.UserName ?? username;
SetLoginInfo(returnedUserID, returnedUsername, true);
return true;
}
catch (Exception ex)
{
SetLoginInfo(string.Empty, string.Empty, false);
message = string.Format("登入錯誤:{0}", ex.Message);
return false;
}
}
message = response.Message;
return false;
}
/// <summary>
/// 執行登出
/// </summary>
public static void Logout()
{
InitInstance();
LoginService.Logout();
LoginService.ClearSession();
instance.userID = string.Empty;
instance.userName = string.Empty;
instance.onlineMode = false;
instance.loginTime = DateTime.MinValue;
RaiseModeChangeEvent();
}
/// <summary>
/// 顯示登入表單
/// </summary>
public static void ShowLoginForm()
{
var loginForm = new LoginForm();
if (loginForm.ShowDialog() == DialogResult.Cancel)
{
Application.Exit();
}
}
public static (bool, string) LoginOffline(string bpeno)
{
try
{
BPSNRepository bpsnRepo = new BPSNRepository();
var bpsn = bpsnRepo.GetByBPENO(bpeno);
if (bpsn == null)
{
return (false, "登入帳號錯誤");
}
SetLoginInfo(bpsn.BPENO, bpsn.BPNME, false);
// 離線模式不驗證密碼
// string combined = bpsn.BPENO + password;
// using (SHA1 sha1 = SHA1.Create())
// {
// byte[] hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(combined));
// if (string.Compare(bpsn.BPAWD, Convert.ToBase64String(hash)) != 0)
// {
// return (false, "帳號或密碼錯誤");
// }
// }
return (true, string.Empty);
}
catch (Exception ex)
{
SetLoginInfo(string.Empty, string.Empty, false);
return (false, string.Format("登入錯誤:{0}", ex.Message));
}
}
}
}
@@ -0,0 +1,25 @@
// 使用 ConfigService 的示例
using System;
using 報到系統客戶端.Services;
// 1. 讀取 API 基礎 URL
string apiUrl = ConfigService.Instance.GetApiBaseUrl();
Console.WriteLine($"目前 API URL: {apiUrl}");
// 2. 設定新的 API 基礎 URL
bool success = ConfigService.Instance.SetApiBaseUrl("http://new-server:8080");
if (success)
{
Console.WriteLine("API URL 已更新");
// 更新 ApiService 的 BaseUrl
ApiService.Instance.SetBaseUrl("http://new-server:8080");
}
else
{
Console.WriteLine("API URL 更新失敗");
}
// 3. 取得設定檔路徑
string configPath = ConfigService.Instance.GetConfigFilePath();
Console.WriteLine($"設定檔路徑: {configPath}");
+27
View File
@@ -0,0 +1,27 @@
# API服務
## 說明
* 這是呼叫後端 API 的服務類別
* 可由 app.config 決定後端 API 的基底URL
* 使用 HttpClient 做非同步呼叫
* 登入成功後要儲存 Session cookie
* 登出時要銷毀 Session cookie
* 呼叫其他 API 時要帶上 Session cookie
## 後端API清單
* Services/LoginService.asmx
* Login:登入
* Logout:登出
* CheckSession:檢查Session,當作心跳使用
* Services/ClassesService.asmx
* SearchClasses:取得活動清單
* Services/SignUpService.asmx
* SearchSignUps:取得活動參加人員清單
* UploadSignUps:上傳活動參加人員簽到簽退資料
* UploadCheckIns:上傳活動參加人員簽到簽退資料
* CheckIn:掃碼簽到
* CheckOut:掃碼簽退
* OnSiteCheckIn:現場簽到
* OnSiteCheckOut:現場簽退
* Services/BPSNService.asmx
* Download:下載員工資料
@@ -0,0 +1,307 @@
# 報到系統客戶端 - Models 模型層改造報告
**完成日期**2024年
**狀態**:✅ **已完成**
**建置結果**:成功編譯無誤
---
## 📋 概述
本次改造完成了報到系統客戶端 Models 層(API 模型層)的更新,添加了新欄位以支援後端 API 的數據交換。這些模型用於 Web Service 通信,與前面完成的本地資料庫模型(LocalSignUpModel、LocalCheckInItem 等)相對應。
---
## 🔄 修改內容詳述
### 1️⃣ SignUpModel.cs - 報名人員模型
📍 位置:`..\報到系統客戶端\Models\SignUpModel.cs`
#### **新增 4 個屬性**
```csharp
/// <summary>
/// 序號(用於識別活動中的參加人員順序)
/// </summary>
[JsonProperty("SeqNo")]
public int SeqNo { get; set; }
/// <summary>
/// 身分證字號
/// </summary>
[JsonProperty("IDNumber")]
public string IDNumber { get; set; }
/// <summary>
/// 身分別(例如:學生、教職員、其他)
/// </summary>
[JsonProperty("IDType")]
public string IDType { get; set; }
/// <summary>
/// 用餐別(例如:葷食、素食、無)
/// </summary>
[JsonProperty("MealType")]
public string MealType { get; set; }
```
#### **完整屬性列表(更新後)**
| 屬性名 | 資料型別 | JsonProperty | 備註 |
|-------|---------|-------------|------|
| ID | int | "ID" | 流水號 |
| CourseID | int | "CourseID" | 課程 ID |
| **SeqNo** | int | "SeqNo" | ⭐ **新增** 序號 |
| Name | string | "Name" | 學員姓名 |
| Mobile | string | "Mobile" | 手機號 |
| Email | string | "Email" | 電子郵件 |
| SignUpType | string | "SignUpType" | 報名方式(預約/現場) |
| **IDNumber** | string | "IDNumber" | ⭐ **新增** 身分證字號 |
| **IDType** | string | "IDType" | ⭐ **新增** 身分別 |
| **MealType** | string | "MealType" | ⭐ **新增** 用餐別 |
| QRCode | string | "QRCode" | QR Code 內容 |
| CheckInTime | DateTime? | "CheckInTime" | 簽到時間 |
| CheckOutTime | DateTime? | "CheckOutTime" | 簽退時間 |
| Remark | string | "Remark" | 備註 |
| DBAppNo | int | "DBAppNo" | 資料庫應用編號 |
---
### 2️⃣ CheckInModel.cs - 簽到簽退紀錄模型
📍 位置:`..\報到系統客戶端\Models\CheckInModel.cs`
#### **新增 4 個屬性**
```csharp
/// <summary>
/// 身分證字號
/// </summary>
[JsonProperty("IDNumber")]
public string IDNumber { get; set; }
/// <summary>
/// 身分別(例如:學生、教職員、其他)
/// </summary>
[JsonProperty("IDType")]
public string IDType { get; set; }
/// <summary>
/// 用餐別(例如:葷食、素食、無)
/// </summary>
[JsonProperty("MealType")]
public string MealType { get; set; }
/// <summary>
/// 電子郵件
/// </summary>
[JsonProperty("Email")]
public string Email { get; set; }
```
#### **完整屬性列表(更新後)**
| 屬性名 | 資料型別 | JsonProperty | 備註 |
|-------|---------|-------------|------|
| ID | int | "ID" | 流水號 |
| CourseID | int | "CourseID" | 課程 ID |
| Name | string | "Name" | 學員姓名 |
| Mobile | string | "Mobile" | 手機號 |
| SignUpType | string | "SignUpType" | 報名方式(預約/現場) |
| **IDNumber** | string | "IDNumber" | ⭐ **新增** 身分證字號 |
| **IDType** | string | "IDType" | ⭐ **新增** 身分別 |
| **MealType** | string | "MealType" | ⭐ **新增** 用餐別 |
| **Email** | string | "Email" | ⭐ **新增** 電子郵件 |
| CheckInType | string | "CheckInType" | 簽到類別(CHECKIN/CHECKOUT |
| CheckInTime | string | "CheckInTime" | 簽到/簽退時間 |
---
## 📊 變更統計
### 模型修改統計
| 文件名 | 新增屬性 | 修改內容 |
|--------|--------|--------|
| SignUpModel.cs | 4 | 在現有 9 個屬性基礎上添加 4 個新屬性 |
| CheckInModel.cs | 4 | 在現有 7 個屬性基礎上添加 4 個新屬性 |
| **總計** | **8** | **新增 8 個屬性** |
### 相關類別(未修改)
| 文件名 | 狀態 | 理由 |
|--------|------|------|
| ClassesModel.cs | ✅ 無需修改 | 已包含所有必要欄位 |
| BPSNModel.cs | ✅ 無需修改 | 已包含所有必要欄位 |
---
## 🔗 数据流向映射
### SignUpItem (API) ↔ LocalSignUpItem (本地DB)
```
API層 (Models/SignUpModel.cs)
↓ ConvertFromApiModel()
本地層 (Data/Models/LocalSignUpModel.cs)
↓ 數據插入/更新
本地DB (SignUps 表)
```
**映射關係**
```csharp
public static LocalSignUpItem ConvertFromApiModel(SignUpItem apiModel)
{
return new LocalSignUpItem
{
ID = apiModel.ID,
CourseID = apiModel.CourseID,
SeqNo = apiModel.SeqNo, // ⭐ 新增欄位
Name = apiModel.Name,
Mobile = apiModel.Mobile,
QRCode = apiModel.QRCode,
SignUpType = apiModel.SignUpType,
IDNumber = apiModel.IDNumber, // ⭐ 新增欄位
IDType = apiModel.IDType, // ⭐ 新增欄位
MealType = apiModel.MealType, // ⭐ 新增欄位
Email = apiModel.Email,
CheckInTime = apiModel.CheckInTime,
CheckOutTime = apiModel.CheckOutTime,
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now
};
}
```
### CheckInItem (API) ↔ LocalCheckInItem (本地DB)
**完整映射**
```
CheckInItem.IDNumber → LocalCheckInItem.IDNumber
CheckInItem.IDType → LocalCheckInItem.IDType
CheckInItem.MealType → LocalCheckInItem.MealType
CheckInItem.Email → LocalCheckInItem.Email
CheckInItem.CheckInType → LocalCheckInItem.CheckInType
... (其他欄位)
```
---
## ✅ 設計要點
### 1. JsonProperty 特性一致性
- ✅ 所有屬性都使用 `[JsonProperty]` 特性,確保 JSON 序列化/反序列化正確
- ✅ 屬性名與後端 API 命名保持一致
### 2. 類型安全性
- ✅ 新增的字符串屬性(IDNumber、IDType、MealType、Email)可為 null
- ✅ SeqNo 為 int 類型,符合資料庫定義
### 3. 向後相容性
- ✅ 所有修改都是添加性的,不影響現有屬性
- ✅ 舊代碼無需修改即可繼續使用
### 4. 集合類定義
```csharp
public class SignUpList : List<SignUpItem> { }
public class CheckInList : List<CheckInItem> { }
```
- ✅ SignUpList、CheckInList 等集合類保持不變
- ✅ 支援新屬性的自動包含
---
## 📝 使用示例
### 從後端 API 接收數據
```csharp
// 後端 API 返回的 JSON
{
"ID": 1,
"CourseID": 101,
"SeqNo": 1, // ⭐ 新欄位
"Name": "李明",
"Mobile": "0912345678",
"Email": "li@example.com",
"SignUpType": "預約",
"IDNumber": "A123456789", // ⭐ 新欄位
"IDType": "學生", // ⭐ 新欄位
"MealType": "素食", // ⭐ 新欄位
"QRCode": "...",
"CheckInTime": "2024-01-15T09:30:00",
"CheckOutTime": null,
"Remark": "無",
"DBAppNo": 0
}
// Newtonsoft.Json 自動反序列化為 SignUpItem
// 所有新欄位都會被正確賦值
SignUpItem item = JsonConvert.DeserializeObject<SignUpItem>(json);
```
### 本地資料庫存儲
```csharp
// API 模型轉換為本地模型
LocalSignUpItem localItem = LocalSignUpItem.ConvertFromApiModel(apiItem);
// 插入本地資料庫
var repo = new SignUpsRepository();
repo.Insert(localItem);
// SeqNo, IDNumber, IDType, MealType, Email 都會被正確保存
```
---
## 🔍 編譯驗證
### 編譯測試結果
- ✅ 項目編譯成功
- ✅ 無編譯警告
- ✅ 所有屬性定義正確
- ✅ JsonProperty 特性正確應用
---
## 📚 相關文檔
| 文檔 | 說明 |
|-----|------|
| 資料儲存服務.md | 本地資料庫表結構規格 |
| API服務.md | 後端 API 接口定義 |
| 資料存取層改造完成報告.md | 本地資料層修改詳情 |
---
## ✨ 完成清單
- ✅ SignUpModel.cs 添加 4 個新屬性
- ✅ CheckInModel.cs 添加 4 個新屬性
- ✅ ClassesModel.cs 驗證(無需修改)
- ✅ BPSNModel.cs 驗證(無需修改)
- ✅ 代碼編譯驗證
- ✅ 文檔生成
---
## 🎯 結論
報到系統客戶端的 Models 層已成功更新,與後端 API 完全對應,與本地資料層結構一致。所有修改都經過編譯驗證,可直接用於生產環境。
**整體狀態**
| 層級 | 狀態 | 說明 |
|-----|------|------|
| 資料庫層 | 🟢 完成 | DatabaseService 結構已更新 |
| 本地模型層 | 🟢 完成 | LocalSignUpModel、LocalCheckInItem 已更新 |
| API 模型層 | 🟢 完成 | SignUpModel、CheckInModel 已更新 |
| 資料存取層 | 🟢 完成 | SignUpsRepository、CheckInRepository 已更新 |
| **整體** | 🟢 **生產就緒** | 所有層級已同步更新 |
---
**報告版本**1.0
**最後更新**2024年
+16
View File
@@ -0,0 +1,16 @@
# 登入
## 畫面檔
* LoginForm.cs
## 後端服務
* Services/LoginService
* 登入服務
* Login, LoginAsync:登入
* Logout:登出
* CheckSessionCheckSessionAsync:檢查 Session,可當作發送心跳信號來使用
## 技術規格
* 呼叫後端服務使用 HttpClient,不要使用服務參考
* 登入成功之後要把 Session 相關資料
+57
View File
@@ -0,0 +1,57 @@
# 簽到簽退
## 畫面
* MainForm 上的 tabPage1
* 上面有兩個區域:預約報名、現場報到
* 使用銜接鍵盤介面的二維碼掃描器來掃碼
### 預約報名
* txtQRCode 為掃碼輸入框
* 掃碼之後查詢本地數據庫的 SignUp 表的 QRCode 欄位
* 查到時
* 依照 cbCheckMode 的值(簽到 / 簽退)更新本地數據庫欄位(CheckInTime / CheckOutTime)
* 本地數據庫 CheckIn 表新增一筆
* lbCheckResult 顯示綠底白字 "報到成功"
* lbName 顯示 Name
* lbMobile 顯示 Mobile
* lbCheckInTime 顯示 CheckInTime
* lbCheckOutTime 顯示 CheckOutTime
* 未查到
* lbCheckResult 顯示紅底白字 "無此筆資料"
* lbName 內容清空
* lbMobile 內容清空
* lbCheckInTime 內容清空
* lbCheckOutTime 內容清空
### 現場報到
* txtName 輸入姓名 (必須輸入)
* txtMobile 輸入手機號 (必須輸入)
* 點擊 btnMenualCheckIn 按鈕執行現場報到
* cbCheckMode 的值 = "簽到" 時
* 用姓名與手機號查找本地數據庫 SignUp 表
* 查到:
* 本地數據庫 SignUp 表更新 CheckInTime
* 本地數據庫 CheckIn 表新增一筆
* 未查到:
* 本地數據庫 SignUp 表新增一筆
* 本地數據庫 CheckIn 表新增一筆
* lbMenualCheckInTime 顯示 CheckInTime
* lbMenualCheckOutTime 空白
* cbCheckMode 的值 = "簽退" 時
* 用姓名與手機號查找本地數據庫 SignUp 表
* 查到:
* 本地數據庫 SignUp 表更新 CheckOutTime
* 本地數據庫 CheckIn 表新增一筆
* 未查到:
* 本地數據庫 SignUp 表新增一筆
* 本地數據庫 CheckIn 表新增一筆
* lbMenualCheckInTime 顯示 CheckInTime
* lbMenualCheckOutTime 顯示 CheckOutTime
## 資料庫表結構
* 已有數據庫表結構以及存取類別,在 Data/Models 和 Data/Repositories 目錄
* 亦可參照 資料儲存服務.md
## 邏輯澄清
* CheckInTime/CheckOutTime:若已有值則不覆蓋,只在為 NULL 時才寫入
* 異常處理:數據庫操作失敗時顯示錯誤訊息,不清空UI
@@ -0,0 +1,76 @@
# 資料儲存服務
## 說明
* 使用 SQLite 資料庫儲存本地資料,供客戶端離線使用
* 使用 Microsoft.Data.Sqlite 套件
* 支援完整的 CRUD 操作(新增、修改、查詢、刪除)
* 支援條件查詢和分頁
* 自動建立資料庫和表格結構
## 技術規格
* 資料庫檔案位置:DB 資料夾
* 資料庫名稱:data.db
* 建立 Classes 資料表 (對應後端 DAC_Classes1.cs 的 ClassesItem
* 建立 SignUps 資料表 (對應後端 DAC_SignUp1.cs 的 SignUpItem
* 建立 BPSN 資料表 (對應後端 DAC_BPSN.cs 的 BPSNItem
* 建立 CheckIn 資料表
## BPSN 表結構 -- 員工資料
* string BPENO (PK)
* string BPNME
* string BPAWD
* bool Is系統管理群組
* bool Is客戶管理群組
* bool Is現場工作群組
* bool Is查詢群組
## Classes 表結構 -- 活動資料
* ID (INTEGER PRIMARY KEY) - 流水號(不自動編號,需要外部提供)
* CourseCode (TEXT) - 課程編號
* CourseName (TEXT NOT NULL) - 課程名稱
* CourseDate (DATETIME) - 課程日期
* CourseLocation (TEXT) - 課程地點
* NotificationTemplate (TEXT) - 通知模板
## SignUps 表結構 -- 參加人員資料
* ID (INTEGER PRIMARY KEY AUTOINCREMENT) - 流水號
* CourseID (INTEGER NOT NULL) - 課程ID
* SeqNo (INTEGER NOT NULL) - 序號
* Name (TEXT NOT NULL) - 學員姓名
* Mobile (TEXT NOT NULL) - 手機號
* QRCode (TEXT) - QRCode內容
* SignUpType (TEXT) - 報名方式(預約/現場)
* IDNumber (TEXT) - 身分證字號
* IDType (TEXT) - 身分別
* MealType (TEXT) - 用餐別
* Email (TEXT) - 電子郵件
* CheckInTime (DATETIME) - 簽到時間
* CheckOutTime (DATETIME) - 簽退時間
## CheckIn 表結構 -- 簽到簽退紀錄
* ID (INTEGER PRIMARY KEY AUTOINCREMENT) - 流水號
* CourseID (INTEGER NOT NULL) - 課程ID
* Name (TEXT NOT NULL) - 學員姓名
* Mobile (TEXT NOT NULL) - 手機號
* SignUpType (TEXT) - 報名方式(預約 / 現場)
* IDNumber (TEXT) - 身分證字號
* IDType (TEXT) - 身分別
* MealType (TEXT) - 用餐別
* Email (TEXT) - 電子郵件
* CheckInType (TEXT) - 簽到類別(CHECKIN / CHECKOUT
* CheckInTime (DATETIME) - 簽到/簽退時間
* UploadTime (DATETIME) - 上傳時間
## 操作功能
* Classes:新增、修改、查詢、刪除
* SignUps:新增、修改、查詢、刪除
* BPSN:新增、修改、查詢、刪除
* CheckIn:新增、修改、查詢、刪除
* 清除所有資料表
## 查詢支援
* 條件查詢(WHERE
* 分頁查詢(LIMIT, OFFSET
* 排序(ORDER BY
@@ -0,0 +1,296 @@
# 報到系統客戶端 - 資料存取層改造完成報告
**完成日期**2024年
**狀態**:✅ **已完成**
**建置結果**:成功編譯無誤
---
## 📋 概述
本次改造根據《資料儲存服務.md》規格,完整更新了報到系統客戶端的本地 SQLite 資料庫結構及資料存取層代碼,添加了缺失的欄位並實現了新的查詢方法。
---
## 🔄 修改內容總結
### 1️⃣ 資料庫表結構更新
#### **SignUps 表 - 新增 5 個欄位**
| 欄位名稱 | 資料型別 | 備註 |
|---------|---------|------|
| `SeqNo` | INTEGER NOT NULL DEFAULT 0 | ⭐ 序號(活動人員序號改造) |
| `IDNumber` | TEXT | 身分證字號 |
| `IDType` | TEXT | 身分別 |
| `MealType` | TEXT | 用餐別 |
| `Email` | TEXT | 電子郵件 |
**新增索引**
- `idx_signups_seqno` - 序號查詢加速
- `idx_signups_idnumber` - 身分證字號查詢加速
- `idx_signups_email` - 電子郵件查詢加速
#### **CheckIn 表 - 新增 4 個欄位**
| 欄位名稱 | 資料型別 | 備註 |
|---------|---------|------|
| `IDNumber` | TEXT | 身分證字號 |
| `IDType` | TEXT | 身分別 |
| `MealType` | TEXT | 用餐別 |
| `Email` | TEXT | 電子郵件 |
**新增索引**
- `idx_checkin_idnumber` - 身分證字號查詢加速
- `idx_checkin_email` - 電子郵件查詢加速
---
### 2️⃣ 文件修改清單
#### **DatabaseService.cs** ✅
📍 位置:`..\報到系統客戶端\Data\DatabaseService.cs`
**修改項目**
-`CreateSignUpsTable()` - 添加新欄位及索引
-`CreateCheckInTable()` - 添加新欄位及索引
```sql
-- SignUps 新欄位
SeqNo INTEGER NOT NULL DEFAULT 0,
IDNumber TEXT,
IDType TEXT,
MealType TEXT,
Email TEXT,
-- CheckIn 新欄位
IDNumber TEXT,
IDType TEXT,
MealType TEXT,
Email TEXT,
```
---
#### **LocalSignUpModel.cs** ✅
📍 位置:`..\報到系統客戶端\Data\Models\LocalSignUpModel.cs`
**新增屬性**
```csharp
[JsonProperty("SeqNo")]
public int SeqNo { get; set; }
[JsonProperty("IDNumber")]
public string IDNumber { get; set; }
[JsonProperty("IDType")]
public string IDType { get; set; }
[JsonProperty("MealType")]
public string MealType { get; set; }
[JsonProperty("Email")]
public string Email { get; set; }
```
---
#### **LocalCheckInItem.cs** ✅
📍 位置:`..\報到系統客戶端\Data\Models\LocalCheckInItem.cs`
**新增屬性**
```csharp
[JsonProperty("IDNumber")]
public string IDNumber { get; set; }
[JsonProperty("IDType")]
public string IDType { get; set; }
[JsonProperty("MealType")]
public string MealType { get; set; }
[JsonProperty("Email")]
public string Email { get; set; }
```
---
#### **SignUpsRepository.cs** ✅
📍 位置:`..\報到系統客戶端\Data\Repositories\SignUpsRepository.cs`
**修改方法**
| 方法名 | 修改內容 |
|-------|---------|
| `Insert()` | 添加 SeqNo, IDNumber, IDType, MealType, Email 參數 |
| `Update()` | 添加新欄位參數及 SET 語句 |
| `ReadSignUpItem()` | 添加新欄位讀取邏輯 |
**新增方法**
```csharp
/// <summary>
/// 按序號查詢單筆記錄
/// </summary>
public LocalSignUpItem GetBySeqNo(int seqNo)
{
// 實現:WHERE SeqNo = @SeqNo
}
/// <summary>
/// 按活動編號和序號查詢單筆記錄
/// </summary>
public LocalSignUpItem GetByCourseIDAndSeqNo(int courseID, int seqNo)
{
// 實現:WHERE CourseID = @CourseID AND SeqNo = @SeqNo
}
```
---
#### **CheckInRepository.cs** ✅
📍 位置:`..\報到系統客戶端\Data\Repositories\CheckInRepository.cs`
**修改方法**
| 方法名 | 修改內容 |
|-------|---------|
| `Insert()` | 添加 IDNumber, IDType, MealType, Email 參數 |
| `InsertBatch()` | 添加新欄位參數 |
| `Update()` | 添加新欄位參數及 SET 語句 |
| `ReadCheckInItem()` | 添加新欄位讀取邏輯 |
---
### 3️⃣ SQL 語句更新示例
#### SignUps - Insert 語句
```sql
INSERT INTO SignUps
(CourseID, SeqNo, Name, Mobile, QRCode, SignUpType, IDNumber, IDType, MealType, Email,
CheckInTime, CheckOutTime, CreatedAt, UpdatedAt)
VALUES (@CourseID, @SeqNo, @Name, @Mobile, @QRCode, @SignUpType, @IDNumber, @IDType,
@MealType, @Email, @CheckInTime, @CheckOutTime, @CreatedAt, @UpdatedAt)
```
#### CheckIn - Insert 語句
```sql
INSERT INTO CheckIn
(CourseID, Name, Mobile, SignUpType, IDNumber, IDType, MealType, Email, CheckInType,
CheckInTime, UploadTime, CreatedAt, UpdatedAt)
VALUES (@CourseID, @Name, @Mobile, @SignUpType, @IDNumber, @IDType, @MealType, @Email,
@CheckInType, @CheckInTime, @UploadTime, @CreatedAt, @UpdatedAt)
```
---
## 📊 變更統計
### 代碼修改量
| 檔案 | 修改類型 | 數量 |
|-----|---------|------|
| DatabaseService.cs | 欄位+索引 | 9 項 |
| LocalSignUpModel.cs | 新屬性 | 5 個 |
| LocalCheckInItem.cs | 新屬性 | 4 個 |
| SignUpsRepository.cs | 方法更新+新增 | 3 個方法 |
| CheckInRepository.cs | 方法更新 | 4 個方法 |
| **總計** | | **25+ 項修改** |
---
## ✅ 測試驗證
### 編譯測試
- ✅ 項目編譯成功,無警告
- ✅ 所有新方法簽名正確
- ✅ SQL 參數綁定完整
### 運行檢查
1. ✅ DatabaseService 初始化時自動建立更新後的表結構
2. ✅ 新欄位的 NULL 值處理正確
3. ✅ 索引創建成功
---
## 🔍 向後相容性
| 項目 | 評估 |
|-----|------|
| 現有應用程式 | ✅ 完全相容 |
| 舊資料遷移 | ✅ 新欄位預設值為 NULL 或 0 |
| 查詢效能 | ✅ 新索引提升查詢速度 |
| API 兼容性 | ✅ Model 轉換邏輯無變化 |
---
## 📝 後續建議
### 1. 資料初始化(如有現存資料庫)
若客戶端已有舊的 `data.db` 文件,需要執行以下 SQL
```sql
-- 為 SignUps 表添加新欄位(如果 CREATE TABLE IF NOT EXISTS 未自動添加)
ALTER TABLE SignUps ADD COLUMN SeqNo INTEGER NOT NULL DEFAULT 0;
ALTER TABLE SignUps ADD COLUMN IDNumber TEXT;
ALTER TABLE SignUps ADD COLUMN IDType TEXT;
ALTER TABLE SignUps ADD COLUMN MealType TEXT;
ALTER TABLE SignUps ADD COLUMN Email TEXT;
-- 為 CheckIn 表添加新欄位
ALTER TABLE CheckIn ADD COLUMN IDNumber TEXT;
ALTER TABLE CheckIn ADD COLUMN IDType TEXT;
ALTER TABLE CheckIn ADD COLUMN MealType TEXT;
ALTER TABLE CheckIn ADD COLUMN Email TEXT;
```
### 2. 序號初始化(重要)
若需要為現有 SignUps 記錄初始化 SeqNo
```csharp
public void InitializeSeqNo()
{
var signups = GetAll();
int seqNo = 1;
foreach (var signup in signups)
{
signup.SeqNo = seqNo++;
Update(signup);
}
}
```
### 3. UI 層同步
確保 UI 層(WinForm)的相關控制項已更新,以支援新欄位的顯示和編輯:
- CheckInModel、SignUpModel 等視圖模型
- 表格列定義
- 表單欄位
---
## 📚 參考文檔
- 《資料儲存服務.md》- 原始規格
- 《活動人員序號欄位改造說明.md》- 序號改造詳情
---
## ✨ 完成清單
- ✅ 資料庫表結構更新
- ✅ 模型類別更新
- ✅ 存儲庫類別更新
- ✅ SQL 語句完整性驗證
- ✅ 新查詢方法實現
- ✅ 代碼編譯驗證
- ✅ 向後相容性檢查
- ✅ 完成報告生成
---
## 🎯 結論
報到系統客戶端的資料存取層已全面更新,完全符合最新的《資料儲存服務.md》規格。所有修改都經過編譯驗證,可直接投入生產環境使用。
**狀態**:🟢 **生產就緒**
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,39 @@
using System;
using Newtonsoft.Json;
namespace .Data.Models
{
/// <summary>
/// 本地 BPSN 表資料項目
/// 對應後端 DAC_BPSN.cs 的 BPSNItem
/// </summary>
public class LocalBPSNItem
{
[JsonProperty("BPENO")]
public string BPENO { get; set; }
[JsonProperty("BPNME")]
public string BPNME { get; set; }
[JsonProperty("BPAWD")]
public string BPAWD { get; set; }
[JsonProperty("Is系統管理群組")]
public bool Is系統管理群組 { get; set; }
[JsonProperty("Is客戶管理群組")]
public bool Is客戶管理群組 { get; set; }
[JsonProperty("Is現場工作群組")]
public bool Is現場工作群組 { get; set; }
[JsonProperty("Is查詢群組")]
public bool Is查詢群組 { get; set; }
[JsonProperty("CreatedAt")]
public DateTime CreatedAt { get; set; }
[JsonProperty("UpdatedAt")]
public DateTime UpdatedAt { get; set; }
}
}
@@ -0,0 +1,63 @@
using System;
using Newtonsoft.Json;
namespace .Data.Models
{
/// <summary>
/// 本地 CheckIn 表資料項目
/// 簽到簽退紀錄表
/// </summary>
public class LocalCheckInItem
{
[JsonProperty("ID")]
public int ID { get; set; }
[JsonProperty("CourseID")]
public int CourseID { get; set; }
[JsonProperty("Name")]
public string Name { get; set; }
[JsonProperty("Mobile")]
public string Mobile { get; set; }
[JsonProperty("SignUpType")]
public string SignUpType { get; set; }
[JsonProperty("IDNumber")]
public string IDNumber { get; set; }
[JsonProperty("IDType")]
public string IDType { get; set; }
[JsonProperty("MealType")]
public string MealType { get; set; }
[JsonProperty("Email")]
public string Email { get; set; }
[JsonProperty("LossJobTimes")]
public int? LossJobTimes { get; set; }
[JsonProperty("Gender")]
public string Gender { get; set; }
[JsonProperty("AgreementStatus")]
public string AgreementStatus { get; set; }
[JsonProperty("CheckInType")]
public string CheckInType { get; set; }
[JsonProperty("CheckInTime")]
public string CheckInTime { get; set; }
[JsonProperty("UploadTime")]
public DateTime? UploadTime { get; set; }
[JsonProperty("CreatedAt")]
public DateTime CreatedAt { get; set; }
[JsonProperty("UpdatedAt")]
public DateTime UpdatedAt { get; set; }
}
}
@@ -0,0 +1,75 @@
using System;
using Newtonsoft.Json;
namespace .Data.Models
{
/// <summary>
/// 本地 Classes 表資料項目
/// 對應後端 DAC_Classes1.cs 的 ClassesItem
/// </summary>
public class LocalClassesItem
{
[JsonProperty("ID")]
public int ID { get; set; }
[JsonProperty("CourseCode")]
public string CourseCode { get; set; }
[JsonProperty("CourseName")]
public string CourseName { get; set; }
[JsonProperty("CourseDate")]
public string CourseDate { get; set; }
[JsonProperty("CourseLocation")]
public string CourseLocation { get; set; }
[JsonProperty("NotificationTemplate")]
public string NotificationTemplate { get; set; }
[JsonProperty("AgreedTerms")]
public string AgreedTerms { get; set; }
[JsonProperty("StartTime")]
public string StartTime { get; set; }
[JsonProperty("EndTime")]
public string EndTime { get; set; }
[JsonProperty("CreatedAt")]
public DateTime CreatedAt { get; set; }
[JsonProperty("UpdatedAt")]
public DateTime UpdatedAt { get; set; }
[JsonIgnore]
public string DisplayText {
get {
return string.Format("[{0}] ({1:yyyy-MM-dd} {2}) {3} ", CourseCode, CourseDate, StartTime, CourseName);
}
}
/// <summary>
/// 從後端 API 模型轉換
/// </summary>
public static LocalClassesItem ConvertFromApiModel(.Models.ClassesItem apiModel)
{
if (apiModel == null) return null;
return new LocalClassesItem
{
ID = apiModel.ID,
CourseCode = apiModel.CourseCode,
CourseName = apiModel.CourseName,
CourseDate = apiModel.CourseDate.ToString("yyyy-MM-dd"),
CourseLocation = apiModel.CourseLocation,
NotificationTemplate = apiModel.NotificationTemplate,
AgreedTerms = apiModel.AgreedTerms,
StartTime = apiModel.StartTime,
EndTime = apiModel.EndTime,
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now
};
}
}
}
@@ -0,0 +1,120 @@
using System;
using Newtonsoft.Json;
namespace .Data.Models
{
/// <summary>
/// 本地 SignUps 表資料項目
/// 對應後端 DAC_SignUp1.cs 的 SignUpItem
/// </summary>
public class LocalSignUpItem
{
[JsonProperty("ID")]
public int ID { get; set; }
[JsonProperty("CourseID")]
public int CourseID { get; set; }
[JsonProperty("SeqNo")]
public int SeqNo { get; set; }
[JsonProperty("Name")]
public string Name { get; set; }
[JsonProperty("Mobile")]
public string Mobile { get; set; }
[JsonProperty("QRCode")]
public string QRCode { get; set; }
[JsonProperty("SignUpType")]
public string SignUpType { get; set; }
[JsonProperty("IDNumber")]
public string IDNumber { get; set; }
[JsonProperty("IDType")]
public string IDType { get; set; }
[JsonProperty("MealType")]
public string MealType { get; set; }
[JsonProperty("Email")]
public string Email { get; set; }
[JsonProperty("LossJobTimes")]
public int? LossJobTimes { get; set; }
[JsonProperty("Gender")]
public string Gender { get; set; }
[JsonProperty("AgreementStatus")]
public string AgreementStatus { get; set; }
[JsonProperty("CheckInTime")]
public string CheckInTime { get; set; }
[JsonProperty("CheckOutTime")]
public string CheckOutTime { get; set; }
[JsonProperty("CreatedAt")]
public DateTime CreatedAt { get; set; }
[JsonProperty("UpdatedAt")]
public DateTime UpdatedAt { get; set; }
/// <summary>
/// 轉換為後端 API 使用的模型
/// </summary>
public static .Models.SignUpItem ConvertToApiModel(LocalSignUpItem local)
{
if (local == null) return null;
return new .Models.SignUpItem
{
ID = local.ID,
CourseID = local.CourseID,
Name = local.Name,
Mobile = local.Mobile,
QRCode = local.QRCode,
SignUpType = local.SignUpType,
Email = local.Email,
LossJobTimes = local.LossJobTimes,
Gender = local.Gender,
AgreementStatus = local.AgreementStatus,
CheckInTime = local.CheckInTime,
CheckOutTime = local.CheckOutTime
};
}
/// <summary>
/// 從後端 API 模型轉換
/// </summary>
public static LocalSignUpItem ConvertFromApiModel(.Models.SignUpItem apiModel)
{
if (apiModel == null) return null;
return new LocalSignUpItem
{
ID = apiModel.ID,
CourseID = apiModel.CourseID,
SeqNo = apiModel.SeqNo,
Name = apiModel.Name,
Mobile = apiModel.Mobile,
QRCode = apiModel.QRCode,
SignUpType = apiModel.SignUpType,
IDNumber = apiModel.IDNumber,
IDType = apiModel.IDType,
MealType = apiModel.MealType,
Email = apiModel.Email,
LossJobTimes = apiModel.LossJobTimes,
Gender = apiModel.Gender,
AgreementStatus = apiModel.AgreementStatus,
CheckInTime = apiModel.CheckInTime,
CheckOutTime = apiModel.CheckOutTime,
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now
};
}
}
}
@@ -0,0 +1,359 @@
using System;
using System.Collections.Generic;
using System.Data;
using Microsoft.Data.Sqlite;
using .Data.Models;
namespace .Data.Repositories
{
/// <summary>
/// BPSN 本地資料庫操作類別
/// 對應員工資料
/// </summary>
public class BPSNRepository
{
private readonly DatabaseService _dbService;
public BPSNRepository()
{
_dbService = DatabaseService.Instance;
}
/// <summary>
/// 新增一筆 BPSN 資料
/// </summary>
public bool Insert(LocalBPSNItem item)
{
try
{
var conn = _dbService.GetConnection();
string sql = @"
INSERT OR REPLACE INTO BPSN
(BPENO, BPNME, BPAWD, Is系統管理群組, Is客戶管理群組, Is現場工作群組, Is查詢群組, CreatedAt, UpdatedAt)
VALUES (@BPENO, @BPNME, @BPAWD, @Is系統管理群組, @Is客戶管理群組, @Is現場工作群組, @Is查詢群組, @CreatedAt, @UpdatedAt)
";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@BPENO", item.BPENO ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@BPNME", item.BPNME ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@BPAWD", item.BPAWD ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@Is系統管理群組", item.Is系統管理群組 ? 1 : 0);
cmd.Parameters.AddWithValue("@Is客戶管理群組", item.Is客戶管理群組 ? 1 : 0);
cmd.Parameters.AddWithValue("@Is現場工作群組", item.Is現場工作群組 ? 1 : 0);
cmd.Parameters.AddWithValue("@Is查詢群組", item.Is查詢群組 ? 1 : 0);
cmd.Parameters.AddWithValue("@CreatedAt", DateTime.Now);
cmd.Parameters.AddWithValue("@UpdatedAt", DateTime.Now);
return cmd.ExecuteNonQuery() > 0;
}
}
catch (Exception ex)
{
throw new Exception($"新增 BPSN 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 批量新增 BPSN 資料
/// </summary>
public void InsertBatch(List<LocalBPSNItem> items)
{
try
{
var conn = _dbService.GetConnection();
using (SqliteTransaction transaction = conn.BeginTransaction())
{
try
{
foreach (LocalBPSNItem item in items)
{
string sql = @"
INSERT OR REPLACE INTO BPSN
(BPENO, BPNME, BPAWD, Is系統管理群組, Is客戶管理群組, Is現場工作群組, Is查詢群組, CreatedAt, UpdatedAt)
VALUES (@BPENO, @BPNME, @BPAWD, @Is系統管理群組, @Is客戶管理群組, @Is現場工作群組, @Is查詢群組, @CreatedAt, @UpdatedAt)
";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
cmd.Transaction = transaction;
cmd.Parameters.AddWithValue("@BPENO", item.BPENO ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@BPNME", item.BPNME ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@BPAWD", item.BPAWD ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@Is系統管理群組", item.Is系統管理群組 ? 1 : 0);
cmd.Parameters.AddWithValue("@Is客戶管理群組", item.Is客戶管理群組 ? 1 : 0);
cmd.Parameters.AddWithValue("@Is現場工作群組", item.Is現場工作群組 ? 1 : 0);
cmd.Parameters.AddWithValue("@Is查詢群組", item.Is查詢群組 ? 1 : 0);
cmd.Parameters.AddWithValue("@CreatedAt", DateTime.Now);
cmd.Parameters.AddWithValue("@UpdatedAt", DateTime.Now);
cmd.ExecuteNonQuery();
}
}
transaction.Commit();
}
catch (Exception)
{
transaction.Rollback();
throw;
}
}
}
catch (Exception ex)
{
throw new Exception($"批量新增 BPSN 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 更新一筆 BPSN 資料
/// </summary>
public bool Update(LocalBPSNItem item)
{
try
{
var conn = _dbService.GetConnection();
string sql = @"
UPDATE BPSN SET
BPNME = @BPNME,
BPAWD = @BPAWD,
Is系統管理群組 = @Is系統管理群組,
Is客戶管理群組 = @Is客戶管理群組,
Is現場工作群組 = @Is現場工作群組,
Is查詢群組 = @Is查詢群組,
UpdatedAt = @UpdatedAt
WHERE BPENO = @BPENO
";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@BPENO", item.BPENO ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@BPNME", item.BPNME ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@BPAWD", item.BPAWD ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@Is系統管理群組", item.Is系統管理群組 ? 1 : 0);
cmd.Parameters.AddWithValue("@Is客戶管理群組", item.Is客戶管理群組 ? 1 : 0);
cmd.Parameters.AddWithValue("@Is現場工作群組", item.Is現場工作群組 ? 1 : 0);
cmd.Parameters.AddWithValue("@Is查詢群組", item.Is查詢群組 ? 1 : 0);
cmd.Parameters.AddWithValue("@UpdatedAt", DateTime.Now);
return cmd.ExecuteNonQuery() > 0;
}
}
catch (Exception ex)
{
throw new Exception($"更新 BPSN 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 刪除一筆 BPSN 資料
/// </summary>
public bool Delete(string bpeno)
{
try
{
var conn = _dbService.GetConnection();
string sql = "DELETE FROM BPSN WHERE BPENO = @BPENO";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@BPENO", bpeno ?? (object)DBNull.Value);
return cmd.ExecuteNonQuery() > 0;
}
}
catch (Exception ex)
{
throw new Exception($"刪除 BPSN 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 查詢所有 BPSN 資料
/// </summary>
public List<LocalBPSNItem> GetAll()
{
return Query("SELECT * FROM BPSN ORDER BY BPENO ASC");
}
/// <summary>
/// 查詢單筆 BPSN 資料
/// </summary>
public LocalBPSNItem GetByBPENO(string bpeno)
{
try
{
var conn = _dbService.GetConnection();
string sql = "SELECT * FROM BPSN WHERE BPENO = @BPENO LIMIT 1";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@BPENO", bpeno ?? (object)DBNull.Value);
using (SqliteDataReader reader = cmd.ExecuteReader())
{
if (reader.Read())
{
return ReadBPSNItem(reader);
}
}
}
return null;
}
catch (Exception ex)
{
throw new Exception($"查詢 BPSN 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 條件查詢 BPSN 資料(分頁)
/// </summary>
public List<LocalBPSNItem> GetByCondition(int pageIndex, int pageSize, string bpeno = "", string bpnme = "")
{
try
{
var conn = _dbService.GetConnection();
string sql = "SELECT * FROM BPSN WHERE 1=1";
if (!string.IsNullOrWhiteSpace(bpeno))
sql += " AND BPENO LIKE '%' || @BPENO || '%'";
if (!string.IsNullOrWhiteSpace(bpnme))
sql += " AND BPNME LIKE '%' || @BPNME || '%'";
sql += " ORDER BY BPENO ASC LIMIT @PageSize OFFSET @Offset";
List<LocalBPSNItem> result = new List<LocalBPSNItem>();
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
if (!string.IsNullOrWhiteSpace(bpeno))
cmd.Parameters.AddWithValue("@BPENO", bpeno);
if (!string.IsNullOrWhiteSpace(bpnme))
cmd.Parameters.AddWithValue("@BPNME", bpnme);
cmd.Parameters.AddWithValue("@PageSize", pageSize);
cmd.Parameters.AddWithValue("@Offset", (pageIndex - 1) * pageSize);
using (SqliteDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
result.Add(ReadBPSNItem(reader));
}
}
}
return result;
}
catch (Exception ex)
{
throw new Exception($"查詢 BPSN 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 查詢 BPSN 資料總數
/// </summary>
public int GetCount(string bpeno = "", string bpnme = "")
{
try
{
var conn = _dbService.GetConnection();
string sql = "SELECT COUNT(*) FROM BPSN WHERE 1=1";
if (!string.IsNullOrWhiteSpace(bpeno))
sql += " AND BPENO LIKE '%' || @BPENO || '%'";
if (!string.IsNullOrWhiteSpace(bpnme))
sql += " AND BPNME LIKE '%' || @BPNME || '%'";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
if (!string.IsNullOrWhiteSpace(bpeno))
cmd.Parameters.AddWithValue("@BPENO", bpeno);
if (!string.IsNullOrWhiteSpace(bpnme))
cmd.Parameters.AddWithValue("@BPNME", bpnme);
return Convert.ToInt32(cmd.ExecuteScalar());
}
}
catch (Exception ex)
{
throw new Exception($"統計 BPSN 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 清除所有 BPSN 資料
/// </summary>
public void DeleteAll()
{
try
{
var conn = _dbService.GetConnection();
using (SqliteCommand cmd = new SqliteCommand("DELETE FROM BPSN", conn))
{
cmd.ExecuteNonQuery();
}
}
catch (Exception ex)
{
throw new Exception($"清除 BPSN 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 私用方法 - 執行查詢
/// </summary>
private List<LocalBPSNItem> Query(string sql)
{
try
{
var conn = _dbService.GetConnection();
List<LocalBPSNItem> result = new List<LocalBPSNItem>();
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
using (SqliteDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
result.Add(ReadBPSNItem(reader));
}
}
}
return result;
}
catch (Exception ex)
{
throw new Exception($"查詢 BPSN 失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 私用方法 - 讀取資料列
/// </summary>
private LocalBPSNItem ReadBPSNItem(SqliteDataReader reader)
{
return new LocalBPSNItem
{
BPENO = reader.IsDBNull(reader.GetOrdinal("BPENO")) ? null : reader.GetString(reader.GetOrdinal("BPENO")),
BPNME = reader.IsDBNull(reader.GetOrdinal("BPNME")) ? null : reader.GetString(reader.GetOrdinal("BPNME")),
BPAWD = reader.IsDBNull(reader.GetOrdinal("BPAWD")) ? null : reader.GetString(reader.GetOrdinal("BPAWD")),
Is系統管理群組 = reader.GetInt32(reader.GetOrdinal("Is系統管理群組")) != 0,
Is客戶管理群組 = reader.GetInt32(reader.GetOrdinal("Is客戶管理群組")) != 0,
Is現場工作群組 = reader.GetInt32(reader.GetOrdinal("Is現場工作群組")) != 0,
Is查詢群組 = reader.GetInt32(reader.GetOrdinal("Is查詢群組")) != 0,
CreatedAt = reader.GetDateTime(reader.GetOrdinal("CreatedAt")),
UpdatedAt = reader.GetDateTime(reader.GetOrdinal("UpdatedAt"))
};
}
}
}
@@ -0,0 +1,560 @@
using System;
using System.Collections.Generic;
using System.Data;
using Microsoft.Data.Sqlite;
using .Data.Models;
namespace .Data.Repositories
{
/// <summary>
/// CheckIn 本地資料庫操作類別
/// 簽到簽退紀錄表
/// </summary>
public class CheckInRepository
{
private readonly DatabaseService _dbService;
public CheckInRepository()
{
_dbService = DatabaseService.Instance;
}
/// <summary>
/// 新增一筆 CheckIn 資料
/// </summary>
public int Insert(LocalCheckInItem item)
{
try
{
var conn = _dbService.GetConnection();
string sql = @"
INSERT INTO CheckIn
(CourseID, Name, Mobile, SignUpType, IDNumber, IDType, MealType, Email, LossJobTimes, Gender, AgreementStatus, CheckInType, CheckInTime, UploadTime, CreatedAt, UpdatedAt)
VALUES (@CourseID, @Name, @Mobile, @SignUpType, @IDNumber, @IDType, @MealType, @Email, @LossJobTimes, @Gender, @AgreementStatus,@CheckInType, @CheckInTime, @UploadTime, @CreatedAt, @UpdatedAt)
";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@CourseID", item.CourseID);
cmd.Parameters.AddWithValue("@Name", item.Name ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@Mobile", item.Mobile ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@SignUpType", item.SignUpType ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@IDNumber", item.IDNumber ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@IDType", item.IDType ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@MealType", item.MealType ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@Email", item.Email ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@LossJobTimes", item.LossJobTimes ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@Gender", item.Gender ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@AgreementStatus", item.AgreementStatus ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@CheckInType", item.CheckInType ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@CheckInTime", item.CheckInTime);
cmd.Parameters.AddWithValue("@UploadTime", item.UploadTime ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@CreatedAt", DateTime.Now);
cmd.Parameters.AddWithValue("@UpdatedAt", DateTime.Now);
cmd.ExecuteNonQuery();
}
// 取得新增資料的 ID
using (SqliteCommand cmd = new SqliteCommand("SELECT last_insert_rowid()", conn))
{
return Convert.ToInt32(cmd.ExecuteScalar());
}
}
catch (Exception ex)
{
throw new Exception($"新增 CheckIn 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 批量新增 CheckIn 資料
/// </summary>
public void InsertBatch(List<LocalCheckInItem> items)
{
try
{
var conn = _dbService.GetConnection();
using (SqliteTransaction transaction = conn.BeginTransaction())
{
try
{
foreach (LocalCheckInItem item in items)
{
string sql = @"
INSERT INTO CheckIn
(CourseID, Name, Mobile, SignUpType, IDNumber, IDType, MealType, Email, LossJobTimes, Gender, AgreementStatus, CheckInType, CheckInTime, UploadTime, CreatedAt, UpdatedAt)
VALUES (@CourseID, @Name, @Mobile, @SignUpType, @IDNumber, @IDType, @MealType, @Email, @LossJobTimes, @Gender, @AgreementStatus, @CheckInType, @CheckInTime, @UploadTime, @CreatedAt, @UpdatedAt)
";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
cmd.Transaction = transaction;
cmd.Parameters.AddWithValue("@CourseID", item.CourseID);
cmd.Parameters.AddWithValue("@Name", item.Name ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@Mobile", item.Mobile ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@SignUpType", item.SignUpType ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@IDNumber", item.IDNumber ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@IDType", item.IDType ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@MealType", item.MealType ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@Email", item.Email ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@LossJobTimes", item.LossJobTimes ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@Gender", item.Gender ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@AgreementStatus", item.AgreementStatus ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@CheckInType", item.CheckInType ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@CheckInTime", item.CheckInTime);
cmd.Parameters.AddWithValue("@UploadTime", item.UploadTime ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@CreatedAt", DateTime.Now);
cmd.Parameters.AddWithValue("@UpdatedAt", DateTime.Now);
cmd.ExecuteNonQuery();
}
}
transaction.Commit();
}
catch (Exception)
{
transaction.Rollback();
throw;
}
}
}
catch (Exception ex)
{
throw new Exception($"批量新增 CheckIn 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 更新一筆 CheckIn 資料
/// </summary>
public bool Update(LocalCheckInItem item)
{
try
{
var conn = _dbService.GetConnection();
string sql = @"
UPDATE CheckIn SET
CourseID = @CourseID,
Name = @Name,
Mobile = @Mobile,
SignUpType = @SignUpType,
IDNumber = @IDNumber,
IDType = @IDType,
MealType = @MealType,
Email = @Email,
LossJobTimes = @LossJobTimes,
Gender = @Gender,
AgreementStatus = @AgreementStatus,
CheckInType = @CheckInType,
CheckInTime = @CheckInTime,
UploadTime = @UploadTime,
UpdatedAt = @UpdatedAt
WHERE ID = @ID
";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@ID", item.ID);
cmd.Parameters.AddWithValue("@CourseID", item.CourseID);
cmd.Parameters.AddWithValue("@Name", item.Name ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@Mobile", item.Mobile ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@SignUpType", item.SignUpType ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@IDNumber", item.IDNumber ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@IDType", item.IDType ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@MealType", item.MealType ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@Email", item.Email ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@LossJobTimes", item.LossJobTimes ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@Gender", item.Gender ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@AgreementStatus", item.AgreementStatus ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@CheckInType", item.CheckInType ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@CheckInTime", item.CheckInTime);
cmd.Parameters.AddWithValue("@UploadTime", item.UploadTime ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@UpdatedAt", DateTime.Now);
return cmd.ExecuteNonQuery() > 0;
}
}
catch (Exception ex)
{
throw new Exception($"更新 CheckIn 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 刪除一筆 CheckIn 資料
/// </summary>
public bool Delete(int id)
{
try
{
var conn = _dbService.GetConnection();
string sql = "DELETE FROM CheckIn WHERE ID = @ID";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@ID", id);
return cmd.ExecuteNonQuery() > 0;
}
}
catch (Exception ex)
{
throw new Exception($"刪除 CheckIn 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 查詢所有 CheckIn 資料
/// </summary>
public List<LocalCheckInItem> GetAll()
{
return Query("SELECT * FROM CheckIn ORDER BY CheckInTime DESC");
}
/// <summary>
/// 查詢單筆 CheckIn 資料
/// </summary>
public LocalCheckInItem GetById(int id)
{
var result = Query($"SELECT * FROM CheckIn WHERE ID = {id}");
return result.Count > 0 ? result[0] : null;
}
/// <summary>
/// 按課程 ID 查詢簽到記錄
/// </summary>
public List<LocalCheckInItem> GetByCourseID(int courseID)
{
try
{
var conn = _dbService.GetConnection();
string sql = "SELECT * FROM CheckIn WHERE CourseID = @CourseID ORDER BY CheckInTime DESC";
List<LocalCheckInItem> result = new List<LocalCheckInItem>();
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@CourseID", courseID);
using (SqliteDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
result.Add(ReadCheckInItem(reader));
}
}
}
return result;
}
catch (Exception ex)
{
throw new Exception($"按課程 ID 查詢簽到記錄失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 按課程和姓名手機查詢簽到記錄
/// </summary>
public List<LocalCheckInItem> GetByCourseNameMobile(int courseID, string name, string mobile)
{
try
{
var conn = _dbService.GetConnection();
string sql = "SELECT * FROM CheckIn WHERE CourseID = @CourseID AND Name = @Name AND Mobile = @Mobile ORDER BY CheckInTime DESC";
List<LocalCheckInItem> result = new List<LocalCheckInItem>();
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@CourseID", courseID);
cmd.Parameters.AddWithValue("@Name", name ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@Mobile", mobile ?? (object)DBNull.Value);
using (SqliteDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
result.Add(ReadCheckInItem(reader));
}
}
}
return result;
}
catch (Exception ex)
{
throw new Exception($"按課程和姓名手機查詢簽到記錄失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 條件查詢 CheckIn 資料(分頁)
/// </summary>
public List<LocalCheckInItem> GetByCondition(int pageIndex, int pageSize, int courseID = 0, string name = "", string mobile = "", string checkInType = "", DateTime? startDate = null, DateTime? endDate = null)
{
try
{
var conn = _dbService.GetConnection();
string sql = "SELECT * FROM CheckIn WHERE 1=1";
if (courseID > 0)
sql += " AND CourseID = @CourseID";
if (!string.IsNullOrWhiteSpace(name))
sql += " AND Name LIKE '%' || @Name || '%'";
if (!string.IsNullOrWhiteSpace(mobile))
sql += " AND Mobile LIKE '%' || @Mobile || '%'";
if (!string.IsNullOrWhiteSpace(checkInType))
sql += " AND CheckInType = @CheckInType";
if (startDate.HasValue && endDate.HasValue)
sql += " AND CheckInTime >= @StartDate AND CheckInTime <= @EndDate";
else if (startDate.HasValue)
sql += " AND CheckInTime >= @StartDate";
else if (endDate.HasValue)
sql += " AND CheckInTime <= @EndDate";
sql += " ORDER BY CheckInTime DESC LIMIT @PageSize OFFSET @Offset";
List<LocalCheckInItem> result = new List<LocalCheckInItem>();
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
if (courseID > 0)
cmd.Parameters.AddWithValue("@CourseID", courseID);
if (!string.IsNullOrWhiteSpace(name))
cmd.Parameters.AddWithValue("@Name", name);
if (!string.IsNullOrWhiteSpace(mobile))
cmd.Parameters.AddWithValue("@Mobile", mobile);
if (!string.IsNullOrWhiteSpace(checkInType))
cmd.Parameters.AddWithValue("@CheckInType", checkInType);
if (startDate.HasValue)
cmd.Parameters.AddWithValue("@StartDate", startDate.Value);
if (endDate.HasValue)
cmd.Parameters.AddWithValue("@EndDate", endDate.Value);
cmd.Parameters.AddWithValue("@PageSize", pageSize);
cmd.Parameters.AddWithValue("@Offset", (pageIndex - 1) * pageSize);
using (SqliteDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
result.Add(ReadCheckInItem(reader));
}
}
}
return result;
}
catch (Exception ex)
{
throw new Exception($"查詢 CheckIn 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 查詢 CheckIn 資料總數
/// </summary>
public int GetCount(int courseID = 0, string name = "", string mobile = "", string checkInType = "", DateTime? startDate = null, DateTime? endDate = null)
{
try
{
var conn = _dbService.GetConnection();
string sql = "SELECT COUNT(*) FROM CheckIn WHERE 1=1";
if (courseID > 0)
sql += " AND CourseID = @CourseID";
if (!string.IsNullOrWhiteSpace(name))
sql += " AND Name LIKE '%' || @Name || '%'";
if (!string.IsNullOrWhiteSpace(mobile))
sql += " AND Mobile LIKE '%' || @Mobile || '%'";
if (!string.IsNullOrWhiteSpace(checkInType))
sql += " AND CheckInType = @CheckInType";
if (startDate.HasValue && endDate.HasValue)
sql += " AND CheckInTime >= @StartDate AND CheckInTime <= @EndDate";
else if (startDate.HasValue)
sql += " AND CheckInTime >= @StartDate";
else if (endDate.HasValue)
sql += " AND CheckInTime <= @EndDate";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
if (courseID > 0)
cmd.Parameters.AddWithValue("@CourseID", courseID);
if (!string.IsNullOrWhiteSpace(name))
cmd.Parameters.AddWithValue("@Name", name);
if (!string.IsNullOrWhiteSpace(mobile))
cmd.Parameters.AddWithValue("@Mobile", mobile);
if (!string.IsNullOrWhiteSpace(checkInType))
cmd.Parameters.AddWithValue("@CheckInType", checkInType);
if (startDate.HasValue)
cmd.Parameters.AddWithValue("@StartDate", startDate.Value);
if (endDate.HasValue)
cmd.Parameters.AddWithValue("@EndDate", endDate.Value);
return Convert.ToInt32(cmd.ExecuteScalar());
}
}
catch (Exception ex)
{
throw new Exception($"統計 CheckIn 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 清除所有 CheckIn 資料
/// </summary>
public void DeleteAll()
{
try
{
var conn = _dbService.GetConnection();
using (SqliteCommand cmd = new SqliteCommand("DELETE FROM CheckIn", conn))
{
cmd.ExecuteNonQuery();
}
}
catch (Exception ex)
{
throw new Exception($"清除 CheckIn 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 清除所有 CheckIn 資料
/// </summary>
public void DeleteByCourseID(int courseID)
{
try
{
var conn = _dbService.GetConnection();
using (SqliteCommand cmd = new SqliteCommand("DELETE FROM CheckIn WHERE CourseID=@CourseID", conn))
{
cmd.Parameters.AddWithValue("@CourseID", courseID);
cmd.ExecuteNonQuery();
}
}
catch (Exception ex)
{
throw new Exception($"清除 CheckIn 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 私用方法 - 執行查詢
/// </summary>
private List<LocalCheckInItem> Query(string sql)
{
try
{
var conn = _dbService.GetConnection();
List<LocalCheckInItem> result = new List<LocalCheckInItem>();
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
using (SqliteDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
result.Add(ReadCheckInItem(reader));
}
}
}
return result;
}
catch (Exception ex)
{
throw new Exception($"查詢 CheckIn 失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 私用方法 - 讀取資料列
/// </summary>
private LocalCheckInItem ReadCheckInItem(SqliteDataReader reader)
{
return new LocalCheckInItem
{
ID = reader.GetInt32(reader.GetOrdinal("ID")),
CourseID = reader.GetInt32(reader.GetOrdinal("CourseID")),
Name = reader.IsDBNull(reader.GetOrdinal("Name")) ? null : reader.GetString(reader.GetOrdinal("Name")),
Mobile = reader.IsDBNull(reader.GetOrdinal("Mobile")) ? null : reader.GetString(reader.GetOrdinal("Mobile")),
SignUpType = reader.IsDBNull(reader.GetOrdinal("SignUpType")) ? null : reader.GetString(reader.GetOrdinal("SignUpType")),
IDNumber = reader.IsDBNull(reader.GetOrdinal("IDNumber")) ? null : reader.GetString(reader.GetOrdinal("IDNumber")),
IDType = reader.IsDBNull(reader.GetOrdinal("IDType")) ? null : reader.GetString(reader.GetOrdinal("IDType")),
MealType = reader.IsDBNull(reader.GetOrdinal("MealType")) ? null : reader.GetString(reader.GetOrdinal("MealType")),
Email = reader.IsDBNull(reader.GetOrdinal("Email")) ? null : reader.GetString(reader.GetOrdinal("Email")),
AgreementStatus = reader.IsDBNull(reader.GetOrdinal("AgreementStatus")) ? null : reader.GetString(reader.GetOrdinal("AgreementStatus")),
CheckInType = reader.IsDBNull(reader.GetOrdinal("CheckInType")) ? null : reader.GetString(reader.GetOrdinal("CheckInType")),
CheckInTime = reader.IsDBNull(reader.GetOrdinal("CheckInTime")) ? null : reader.GetString(reader.GetOrdinal("CheckInTime")),
UploadTime = reader.IsDBNull(reader.GetOrdinal("UploadTime")) ? (DateTime?)null : reader.GetDateTime(reader.GetOrdinal("UploadTime")),
CreatedAt = reader.GetDateTime(reader.GetOrdinal("CreatedAt")),
UpdatedAt = reader.GetDateTime(reader.GetOrdinal("UpdatedAt"))
};
}
public void UpdateUploadTime(string[] successIDs)
{
try
{
if (successIDs == null || successIDs.Length == 0)
return;
var conn = _dbService.GetConnection();
using (SqliteTransaction transaction = conn.BeginTransaction())
{
try
{
foreach (string id in successIDs)
{
string sql = "UPDATE CheckIn SET UploadTime = @UploadTime, UpdatedAt = @UpdatedAt WHERE ID = @ID";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
cmd.Transaction = transaction;
cmd.Parameters.AddWithValue("@ID", id);
cmd.Parameters.AddWithValue("@UploadTime", DateTime.Now);
cmd.Parameters.AddWithValue("@UpdatedAt", DateTime.Now);
cmd.ExecuteNonQuery();
}
}
transaction.Commit();
}
catch (Exception)
{
transaction.Rollback();
throw;
}
}
}
catch (Exception ex)
{
throw new Exception($"更新 CheckIn UploadTime 失敗:{ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,321 @@
using System;
using System.Collections.Generic;
using System.Data;
using Microsoft.Data.Sqlite;
using .Data;
using .Data.Models;
namespace .Data.Repositories
{
/// <summary>
/// Classes 本地資料庫操作類別
/// </summary>
public class ClassesRepository
{
private readonly DatabaseService _dbService;
public ClassesRepository()
{
_dbService = DatabaseService.Instance;
}
/// <summary>
/// 新增一筆 Classes 資料
/// </summary>
public int Insert(LocalClassesItem item)
{
try
{
var conn = _dbService.GetConnection();
string sql = @"
INSERT INTO Classes
(ID, CourseCode, CourseName, CourseDate, CourseLocation, NotificationTemplate, AgreedTerms, StartTime, EndTime, CreatedAt, UpdatedAt)
VALUES (@ID, @CourseCode, @CourseName, @CourseDate, @CourseLocation, @NotificationTemplate, @AgreedTerms, @StartTime, @EndTime, @CreatedAt, @UpdatedAt)
";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@ID", item.ID);
cmd.Parameters.AddWithValue("@CourseCode", item.CourseCode ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@CourseName", item.CourseName ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@CourseDate", item.CourseDate ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@CourseLocation", item.CourseLocation ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@NotificationTemplate", item.NotificationTemplate ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@AgreedTerms", item.AgreedTerms ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@StartTime", item.StartTime ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@EndTime", item.EndTime ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@CreatedAt", DateTime.Now);
cmd.Parameters.AddWithValue("@UpdatedAt", DateTime.Now);
cmd.ExecuteNonQuery();
}
// 返回傳入的 ID
return item.ID;
}
catch (Exception ex)
{
throw new Exception($"新增 Classes 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 更新一筆 Classes 資料
/// </summary>
public bool Update(LocalClassesItem item)
{
try
{
var conn = _dbService.GetConnection();
string sql = @"
UPDATE Classes SET
CourseCode = @CourseCode,
CourseName = @CourseName,
CourseDate = @CourseDate,
CourseLocation = @CourseLocation,
NotificationTemplate = @NotificationTemplate,
AgreedTerms = @AgreedTerms,
StartTime = @StartTime,
EndTime = @EndTime,
UpdatedAt = @UpdatedAt
WHERE ID = @ID
";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@ID", item.ID);
cmd.Parameters.AddWithValue("@CourseCode", item.CourseCode ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@CourseName", item.CourseName ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@CourseDate", item.CourseDate ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@CourseLocation", item.CourseLocation ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@NotificationTemplate", item.NotificationTemplate ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@AgreedTerms", item.AgreedTerms ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@StartTime", item.StartTime ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@EndTime", item.EndTime ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@UpdatedAt", DateTime.Now);
return cmd.ExecuteNonQuery() > 0;
}
}
catch (Exception ex)
{
throw new Exception($"更新 Classes 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 刪除一筆 Classes 資料
/// </summary>
public bool Delete(int id)
{
try
{
var conn = _dbService.GetConnection();
string sql = "DELETE FROM Classes WHERE ID = @ID";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@ID", id);
return cmd.ExecuteNonQuery() > 0;
}
}
catch (Exception ex)
{
throw new Exception($"刪除 Classes 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 查詢所有 Classes 資料
/// </summary>
public List<LocalClassesItem> GetAll()
{
return Query("SELECT * FROM Classes ORDER BY ID");
}
/// <summary>
/// 查詢單筆 Classes 資料
/// </summary>
public LocalClassesItem GetById(int id)
{
var result = Query($"SELECT * FROM Classes WHERE ID = {id}");
return result.Count > 0 ? result[0] : null;
}
/// <summary>
/// 條件查詢 Classes 資料(分頁)
/// </summary>
public List<LocalClassesItem> GetByCondition(int pageIndex, int pageSize, string courseName = "", string courseCode = "", DateTime? startDate = null, DateTime? endDate = null)
{
try
{
var conn = _dbService.GetConnection();
string sql = "SELECT * FROM Classes WHERE 1=1";
if (!string.IsNullOrWhiteSpace(courseCode))
sql += " AND CourseCode LIKE '%' || @CourseCode || '%'";
if (!string.IsNullOrWhiteSpace(courseName))
sql += " AND CourseName LIKE '%' || @CourseName || '%'";
if (startDate.HasValue && endDate.HasValue)
sql += " AND CourseDate >= @StartDate AND CourseDate <= @EndDate";
else if (startDate.HasValue)
sql += " AND CourseDate >= @StartDate";
else if (endDate.HasValue)
sql += " AND CourseDate <= @EndDate";
sql += " ORDER BY CourseDate DESC LIMIT @PageSize OFFSET @Offset";
List<LocalClassesItem> result = new List<LocalClassesItem>();
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
if (!string.IsNullOrWhiteSpace(courseCode))
cmd.Parameters.AddWithValue("@CourseCode", courseCode);
if (!string.IsNullOrWhiteSpace(courseName))
cmd.Parameters.AddWithValue("@CourseName", courseName);
if (startDate.HasValue)
cmd.Parameters.AddWithValue("@StartDate", startDate.Value);
if (endDate.HasValue)
cmd.Parameters.AddWithValue("@EndDate", endDate.Value);
cmd.Parameters.AddWithValue("@PageSize", pageSize);
cmd.Parameters.AddWithValue("@Offset", (pageIndex - 1) * pageSize);
using (SqliteDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
result.Add(ReadClassesItem(reader));
}
}
}
return result;
}
catch (Exception ex)
{
throw new Exception($"查詢 Classes 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 查詢 Classes 資料總數
/// </summary>
public int GetCount(string courseName = "", string courseCode = "", DateTime? startDate = null, DateTime? endDate = null)
{
try
{
var conn = _dbService.GetConnection();
string sql = "SELECT COUNT(*) FROM Classes WHERE 1=1";
if (!string.IsNullOrWhiteSpace(courseCode))
sql += " AND CourseCode LIKE '%' || @CourseCode || '%'";
if (!string.IsNullOrWhiteSpace(courseName))
sql += " AND CourseName LIKE '%' || @CourseName || '%'";
if (startDate.HasValue && endDate.HasValue)
sql += " AND CourseDate >= @StartDate AND CourseDate <= @EndDate";
else if (startDate.HasValue)
sql += " AND CourseDate >= @StartDate";
else if (endDate.HasValue)
sql += " AND CourseDate <= @EndDate";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
if (!string.IsNullOrWhiteSpace(courseCode))
cmd.Parameters.AddWithValue("@CourseCode", courseCode);
if (!string.IsNullOrWhiteSpace(courseName))
cmd.Parameters.AddWithValue("@CourseName", courseName);
if (startDate.HasValue)
cmd.Parameters.AddWithValue("@StartDate", startDate.Value);
if (endDate.HasValue)
cmd.Parameters.AddWithValue("@EndDate", endDate.Value);
return Convert.ToInt32(cmd.ExecuteScalar());
}
}
catch (Exception ex)
{
throw new Exception($"統計 Classes 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 清除所有 Classes 資料
/// </summary>
public void DeleteAll()
{
try
{
var conn = _dbService.GetConnection();
using (SqliteCommand cmd = new SqliteCommand("DELETE FROM Classes", conn))
{
cmd.ExecuteNonQuery();
}
}
catch (Exception ex)
{
throw new Exception($"清除 Classes 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 私用方法 - 執行查詢
/// </summary>
private List<LocalClassesItem> Query(string sql)
{
try
{
var conn = _dbService.GetConnection();
List<LocalClassesItem> result = new List<LocalClassesItem>();
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
using (SqliteDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
result.Add(ReadClassesItem(reader));
}
}
}
return result;
}
catch (Exception ex)
{
throw new Exception($"查詢 Classes 失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 私用方法 - 讀取資料列
/// </summary>
private LocalClassesItem ReadClassesItem(SqliteDataReader reader)
{
return new LocalClassesItem
{
ID = reader.GetInt32(reader.GetOrdinal("ID")),
CourseCode = reader.IsDBNull(reader.GetOrdinal("CourseCode")) ? null : reader.GetString(reader.GetOrdinal("CourseCode")),
CourseName = reader.IsDBNull(reader.GetOrdinal("CourseName")) ? null : reader.GetString(reader.GetOrdinal("CourseName")),
CourseDate = reader.IsDBNull(reader.GetOrdinal("CourseDate")) ? null : reader.GetString(reader.GetOrdinal("CourseDate")),
CourseLocation = reader.IsDBNull(reader.GetOrdinal("CourseLocation")) ? null : reader.GetString(reader.GetOrdinal("CourseLocation")),
NotificationTemplate = reader.IsDBNull(reader.GetOrdinal("NotificationTemplate")) ? null : reader.GetString(reader.GetOrdinal("NotificationTemplate")),
AgreedTerms = reader.IsDBNull(reader.GetOrdinal("AgreedTerms")) ? null : reader.GetString(reader.GetOrdinal("AgreedTerms")),
StartTime = reader.IsDBNull(reader.GetOrdinal("StartTime")) ? null : reader.GetString(reader.GetOrdinal("StartTime")),
EndTime = reader.IsDBNull(reader.GetOrdinal("EndTime")) ? null : reader.GetString(reader.GetOrdinal("EndTime")),
CreatedAt = reader.GetDateTime(reader.GetOrdinal("CreatedAt")),
UpdatedAt = reader.GetDateTime(reader.GetOrdinal("UpdatedAt"))
};
}
}
}
@@ -0,0 +1,528 @@
using System;
using System.Collections.Generic;
using System.Data;
using Microsoft.Data.Sqlite;
using .Data.Models;
namespace .Data.Repositories
{
/// <summary>
/// SignUps 本地資料庫操作類別
/// </summary>
public class SignUpsRepository
{
private readonly DatabaseService _dbService;
public SignUpsRepository()
{
_dbService = DatabaseService.Instance;
}
/// <summary>
/// 新增一筆 SignUps 資料
/// </summary>
public int Insert(LocalSignUpItem item)
{
try
{
var conn = _dbService.GetConnection();
string sql = @"
INSERT INTO SignUps
(CourseID, SeqNo, Name, Mobile, QRCode, SignUpType, IDNumber, IDType, MealType, Email, LossJobTimes, Gender, AgreementStatus, CheckInTime, CheckOutTime, CreatedAt, UpdatedAt)
VALUES (@CourseID, @SeqNo, @Name, @Mobile, @QRCode, @SignUpType, @IDNumber, @IDType, @MealType, @Email, @LossJobTimes, @Gender, @AgreementStatus, @CheckInTime, @CheckOutTime, @CreatedAt, @UpdatedAt)
";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@CourseID", item.CourseID);
cmd.Parameters.AddWithValue("@SeqNo", item.SeqNo);
cmd.Parameters.AddWithValue("@Name", item.Name ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@Mobile", item.Mobile ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@QRCode", item.QRCode ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@SignUpType", item.SignUpType ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@IDNumber", item.IDNumber ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@IDType", item.IDType ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@MealType", item.MealType ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@Email", item.Email ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@LossJobTimes", item.LossJobTimes.HasValue ? (object)item.LossJobTimes.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@Gender", item.Gender ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@AgreementStatus", item.AgreementStatus ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@CheckInTime", item.CheckInTime ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@CheckOutTime", item.CheckOutTime ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@CreatedAt", DateTime.Now);
cmd.Parameters.AddWithValue("@UpdatedAt", DateTime.Now);
cmd.ExecuteNonQuery();
}
// 取得新增資料的 ID
using (SqliteCommand cmd = new SqliteCommand("SELECT last_insert_rowid()", conn))
{
return Convert.ToInt32(cmd.ExecuteScalar());
}
}
catch (Exception ex)
{
throw new Exception($"新增 SignUps 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 更新一筆 SignUps 資料
/// </summary>
public bool Update(LocalSignUpItem item)
{
try
{
var conn = _dbService.GetConnection();
string sql = @"
UPDATE SignUps SET
CourseID = @CourseID,
SeqNo = @SeqNo,
Name = @Name,
Mobile = @Mobile,
QRCode = @QRCode,
SignUpType = @SignUpType,
IDNumber = @IDNumber,
IDType = @IDType,
MealType = @MealType,
Email = @Email,
LossJobTimes = @LossJobTimes,
Gender = @Gender,
AgreementStatus = @AgreementStatus,
CheckInTime = @CheckInTime,
CheckOutTime = @CheckOutTime,
UpdatedAt = @UpdatedAt
WHERE ID = @ID
";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@ID", item.ID);
cmd.Parameters.AddWithValue("@CourseID", item.CourseID);
cmd.Parameters.AddWithValue("@SeqNo", item.SeqNo);
cmd.Parameters.AddWithValue("@Name", item.Name ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@Mobile", item.Mobile ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@QRCode", item.QRCode ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@SignUpType", item.SignUpType ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@IDNumber", item.IDNumber ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@IDType", item.IDType ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@MealType", item.MealType ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@Email", item.Email ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@LossJobTimes", item.LossJobTimes.HasValue ? (object)item.LossJobTimes.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@Gender", item.Gender ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@AgreementStatus", item.AgreementStatus ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@CheckInTime", item.CheckInTime ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@CheckOutTime", item.CheckOutTime ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@UpdatedAt", DateTime.Now);
return cmd.ExecuteNonQuery() > 0;
}
}
catch (Exception ex)
{
throw new Exception($"更新 SignUps 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 刪除一筆 SignUps 資料
/// </summary>
public bool Delete(int id)
{
try
{
var conn = _dbService.GetConnection();
string sql = "DELETE FROM SignUps WHERE ID = @ID";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@ID", id);
return cmd.ExecuteNonQuery() > 0;
}
}
catch (Exception ex)
{
throw new Exception($"刪除 SignUps 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 查詢所有 SignUps 資料
/// </summary>
public List<LocalSignUpItem> GetAll()
{
return Query("SELECT * FROM SignUps ORDER BY CourseID DESC, ID ASC");
}
/// <summary>
/// 查詢單筆 SignUps 資料
/// </summary>
public LocalSignUpItem GetById(int id)
{
var result = Query($"SELECT * FROM SignUps WHERE ID = {id}");
return result.Count > 0 ? result[0] : null;
}
/// <summary>
/// 透過 QRCode 查詢
/// </summary>
public LocalSignUpItem GetByQRCode(string qrCode)
{
try
{
var conn = _dbService.GetConnection();
string sql = "SELECT * FROM SignUps WHERE QRCode = @QRCode LIMIT 1";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@QRCode", qrCode);
using (SqliteDataReader reader = cmd.ExecuteReader())
{
if (reader.Read())
{
return ReadSignUpItem(reader);
}
}
}
return null;
}
catch (Exception ex)
{
throw new Exception($"按 QRCode 查詢失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 按課程和姓名手機查詢
/// </summary>
public LocalSignUpItem GetByCourseNameMobile(int courseID, string name, string mobile)
{
try
{
var conn = _dbService.GetConnection();
string sql = "SELECT * FROM SignUps WHERE CourseID = @CourseID";
using (SqliteCommand cmd = new SqliteCommand())
{
cmd.Parameters.AddWithValue("@CourseID", courseID);
if (!string.IsNullOrEmpty(name))
{
sql += " AND Name = @Name";
cmd.Parameters.AddWithValue("@Name", name);
}
if (!string.IsNullOrEmpty(mobile))
{
sql += " AND Mobile = @Mobile";
cmd.Parameters.AddWithValue("@Mobile", mobile);
}
sql += " LIMIT 1";
cmd.Connection = conn;
cmd.CommandText = sql;
using (SqliteDataReader reader = cmd.ExecuteReader())
{
if (reader.Read())
{
return ReadSignUpItem(reader);
}
}
}
return null;
}
catch (Exception ex)
{
throw new Exception($"按課程和姓名手機查詢失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 條件查詢 SignUps 資料(分頁)
/// </summary>
public List<LocalSignUpItem> GetByCondition(int pageIndex, int pageSize, int courseID = 0, string name = "", string mobile = "")
{
try
{
var conn = _dbService.GetConnection();
string sql = "SELECT * FROM SignUps WHERE 1=1";
if (courseID > 0)
sql += " AND CourseID = @CourseID";
if (!string.IsNullOrWhiteSpace(name))
sql += " AND Name LIKE '%' || @Name || '%'";
if (!string.IsNullOrWhiteSpace(mobile))
sql += " AND Mobile LIKE '%' || @Mobile || '%'";
sql += " ORDER BY CourseID DESC, ID ASC LIMIT @PageSize OFFSET @Offset";
List<LocalSignUpItem> result = new List<LocalSignUpItem>();
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
if (courseID > 0)
cmd.Parameters.AddWithValue("@CourseID", courseID);
if (!string.IsNullOrWhiteSpace(name))
cmd.Parameters.AddWithValue("@Name", name);
if (!string.IsNullOrWhiteSpace(mobile))
cmd.Parameters.AddWithValue("@Mobile", mobile);
cmd.Parameters.AddWithValue("@PageSize", pageSize);
cmd.Parameters.AddWithValue("@Offset", (pageIndex - 1) * pageSize);
using (SqliteDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
result.Add(ReadSignUpItem(reader));
}
}
}
return result;
}
catch (Exception ex)
{
throw new Exception($"查詢 SignUps 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 查詢 SignUps 資料總數
/// </summary>
public int GetCount(int courseID = 0, string name = "", string mobile = "")
{
try
{
var conn = _dbService.GetConnection();
string sql = "SELECT COUNT(*) FROM SignUps WHERE 1=1";
if (courseID > 0)
sql += " AND CourseID = @CourseID";
if (!string.IsNullOrWhiteSpace(name))
sql += " AND Name LIKE '%' || @Name || '%'";
if (!string.IsNullOrWhiteSpace(mobile))
sql += " AND Mobile LIKE '%' || @Mobile || '%'";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
if (courseID > 0)
cmd.Parameters.AddWithValue("@CourseID", courseID);
if (!string.IsNullOrWhiteSpace(name))
cmd.Parameters.AddWithValue("@Name", name);
if (!string.IsNullOrWhiteSpace(mobile))
cmd.Parameters.AddWithValue("@Mobile", mobile);
return Convert.ToInt32(cmd.ExecuteScalar());
}
}
catch (Exception ex)
{
throw new Exception($"統計 SignUps 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 按課程 ID 查詢所有報名人員
/// </summary>
public List<LocalSignUpItem> GetByCourseID(int courseID)
{
try
{
var conn = _dbService.GetConnection();
string sql = "SELECT * FROM SignUps WHERE CourseID = @CourseID ORDER BY ID ASC";
List<LocalSignUpItem> result = new List<LocalSignUpItem>();
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@CourseID", courseID);
using (SqliteDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
result.Add(ReadSignUpItem(reader));
}
}
}
return result;
}
catch (Exception ex)
{
throw new Exception($"按課程 ID 查詢失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 按序號查詢單筆記錄
/// </summary>
public LocalSignUpItem GetBySeqNo(int seqNo)
{
try
{
var conn = _dbService.GetConnection();
string sql = "SELECT * FROM SignUps WHERE SeqNo = @SeqNo LIMIT 1";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@SeqNo", seqNo);
using (SqliteDataReader reader = cmd.ExecuteReader())
{
if (reader.Read())
{
return ReadSignUpItem(reader);
}
}
}
return null;
}
catch (Exception ex)
{
throw new Exception($"按序號查詢失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 按活動編號和序號查詢單筆記錄
/// </summary>
public LocalSignUpItem GetByCourseIDAndSeqNo(int courseID, int seqNo)
{
try
{
var conn = _dbService.GetConnection();
string sql = "SELECT * FROM SignUps WHERE CourseID = @CourseID AND SeqNo = @SeqNo LIMIT 1";
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@CourseID", courseID);
cmd.Parameters.AddWithValue("@SeqNo", seqNo);
using (SqliteDataReader reader = cmd.ExecuteReader())
{
if (reader.Read())
{
return ReadSignUpItem(reader);
}
}
}
return null;
}
catch (Exception ex)
{
throw new Exception($"按課程編號和序號查詢失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 清除所有 SignUps 資料
/// </summary>
public void DeleteByCourseID(int courseID)
{
try
{
var conn = _dbService.GetConnection();
using (SqliteCommand cmd = new SqliteCommand("DELETE FROM SignUps WHERE CourseID = @CourseID", conn))
{
cmd.Parameters.AddWithValue("@CourseID", courseID);
cmd.ExecuteNonQuery();
}
}
catch (Exception ex)
{
throw new Exception($"清除 SignUps 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 清除所有 SignUps 資料
/// </summary>
public void DeleteAll()
{
try
{
var conn = _dbService.GetConnection();
using (SqliteCommand cmd = new SqliteCommand("DELETE FROM SignUps", conn))
{
cmd.ExecuteNonQuery();
}
}
catch (Exception ex)
{
throw new Exception($"清除 SignUps 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 私用方法 - 執行查詢
/// </summary>
private List<LocalSignUpItem> Query(string sql)
{
try
{
var conn = _dbService.GetConnection();
List<LocalSignUpItem> result = new List<LocalSignUpItem>();
using (SqliteCommand cmd = new SqliteCommand(sql, conn))
{
using (SqliteDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
result.Add(ReadSignUpItem(reader));
}
}
}
return result;
}
catch (Exception ex)
{
throw new Exception($"查詢 SignUps 失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 私用方法 - 讀取資料列
/// </summary>
private LocalSignUpItem ReadSignUpItem(SqliteDataReader reader)
{
return new LocalSignUpItem
{
ID = reader.GetInt32(reader.GetOrdinal("ID")),
CourseID = reader.GetInt32(reader.GetOrdinal("CourseID")),
SeqNo = reader.IsDBNull(reader.GetOrdinal("SeqNo")) ? 0 : reader.GetInt32(reader.GetOrdinal("SeqNo")),
Name = reader.IsDBNull(reader.GetOrdinal("Name")) ? null : reader.GetString(reader.GetOrdinal("Name")),
Mobile = reader.IsDBNull(reader.GetOrdinal("Mobile")) ? null : reader.GetString(reader.GetOrdinal("Mobile")),
QRCode = reader.IsDBNull(reader.GetOrdinal("QRCode")) ? null : reader.GetString(reader.GetOrdinal("QRCode")),
SignUpType = reader.IsDBNull(reader.GetOrdinal("SignUpType")) ? null : reader.GetString(reader.GetOrdinal("SignUpType")),
IDNumber = reader.IsDBNull(reader.GetOrdinal("IDNumber")) ? null : reader.GetString(reader.GetOrdinal("IDNumber")),
IDType = reader.IsDBNull(reader.GetOrdinal("IDType")) ? null : reader.GetString(reader.GetOrdinal("IDType")),
MealType = reader.IsDBNull(reader.GetOrdinal("MealType")) ? null : reader.GetString(reader.GetOrdinal("MealType")),
Email = reader.IsDBNull(reader.GetOrdinal("Email")) ? null : reader.GetString(reader.GetOrdinal("Email")),
LossJobTimes = reader.IsDBNull(reader.GetOrdinal("LossJobTimes")) ? null : (int?)reader.GetInt32(reader.GetOrdinal("LossJobTimes")),
Gender = reader.IsDBNull(reader.GetOrdinal("Gender")) ? null : reader.GetString(reader.GetOrdinal("Gender")),
AgreementStatus = reader.IsDBNull(reader.GetOrdinal("AgreementStatus")) ? null : reader.GetString(reader.GetOrdinal("AgreementStatus")),
CheckInTime = reader.IsDBNull(reader.GetOrdinal("CheckInTime")) ? null : reader.GetString(reader.GetOrdinal("CheckInTime")),
CheckOutTime = reader.IsDBNull(reader.GetOrdinal("CheckOutTime")) ? null : reader.GetString(reader.GetOrdinal("CheckOutTime")),
CreatedAt = reader.GetDateTime(reader.GetOrdinal("CreatedAt")),
UpdatedAt = reader.GetDateTime(reader.GetOrdinal("UpdatedAt"))
};
}
}
}
@@ -0,0 +1,345 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using .Data.Models;
using .Data.Repositories;
using .Models;
using .Services;
namespace .Data.Services
{
/// <summary>
/// 資料同步服務
/// 協調後端 API 和本地 SQLite 資料庫的資料同步
/// </summary>
public class DataSyncService
{
private readonly ClassesRepository _classesRepo;
private readonly SignUpsRepository _signUpsRepo;
public DataSyncService()
{
_classesRepo = new ClassesRepository();
_signUpsRepo = new SignUpsRepository();
}
/// <summary>
/// 同步 Classes 資料(從 API 到本地)
/// </summary>
public async Task<bool> SyncClassesFromApiAsync()
{
try
{
// 從 API 查詢資料
var response = await ClassesService.SearchClassesAsync();
if (!response.Success)
return false;
// 轉換資料
dynamic dataObj = response.Data;
if (dataObj == null)
return false;
_classesRepo.DeleteAll();
// 插入資料
foreach (var item in dataObj)
{
try
{
// 轉換為本地模型
var apiModel = (ClassesItem)item;
var localItem = LocalClassesItem.ConvertFromApiModel(apiModel);
// 檢查是否已存在
var existing = _classesRepo.GetById(localItem.ID);
if (existing != null)
{
_classesRepo.Update(localItem);
}
else
{
_classesRepo.Insert(localItem);
}
}
catch
{
// 忽略單筆資料錯誤,繼續同步其他資料
}
}
return true;
}
catch (Exception ex)
{
throw new Exception($"同步 Classes 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 同步 Classes 資料(同步版本)
/// </summary>
public bool SyncClassesFromApi()
{
try
{
// 從 API 查詢資料
var response = ClassesService.SearchClasses();
if (!response.Success)
return false;
// 轉換資料
dynamic dataObj = response.Data;
if (dataObj == null)
return false;
_classesRepo.DeleteAll();
// 插入資料
foreach (var item in dataObj)
{
try
{
// 轉換為本地模型
var apiModel = (ClassesItem)item;
var localItem = LocalClassesItem.ConvertFromApiModel(apiModel);
// 檢查是否已存在
var existing = _classesRepo.GetById(localItem.ID);
if (existing != null)
{
_classesRepo.Update(localItem);
}
else
{
_classesRepo.Insert(localItem);
}
}
catch
{
// 忽略單筆資料錯誤,繼續同步其他資料
}
}
return true;
}
catch (Exception ex)
{
throw new Exception($"同步 Classes 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 同步 SignUps 資料(從 API 到本地)
/// </summary>
public async Task<bool> SyncSignUpsFromApiAsync(int courseID, int pageIndex, int pageSize)
{
try
{
// 從 API 查詢資料
var response = await SignUpService.SearchSignUpsAsync(pageIndex, pageSize, courseID);
if (!response.Success)
return false;
// 轉換資料
dynamic dataObj = response.Data;
if (dataObj == null)
return false;
// 如果第一頁,先清除該課程的本地資料
if (pageIndex == 1)
{
var existingList = _signUpsRepo.GetByCourseID(courseID);
foreach (var item in existingList)
{
_signUpsRepo.Delete(item.ID);
}
}
// 插入資料
foreach (var item in dataObj)
{
try
{
// 轉換為本地模型
var apiModel = (SignUpItem)item;
var localItem = LocalSignUpItem.ConvertFromApiModel(apiModel);
// 檢查是否已存在
var existing = _signUpsRepo.GetById(localItem.ID);
if (existing != null)
{
_signUpsRepo.Update(localItem);
}
else
{
_signUpsRepo.Insert(localItem);
}
}
catch
{
// 忽略單筆資料錯誤,繼續同步其他資料
}
}
return true;
}
catch (Exception ex)
{
throw new Exception($"同步 SignUps 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 同步 SignUps 資料(同步版本)
/// </summary>
public bool SyncSignUpsFromApi(int courseID, int pageIndex, int pageSize)
{
try
{
// 從 API 查詢資料
var response = SignUpService.SearchSignUps(pageIndex, pageSize, courseID);
if (!response.Success)
return false;
// 轉換資料
dynamic dataObj = response.Data;
if (dataObj == null)
return false;
// 如果第一頁,先清除該課程的本地資料
if (pageIndex == 1)
{
var existingList = _signUpsRepo.GetByCourseID(courseID);
foreach (var item in existingList)
{
_signUpsRepo.Delete(item.ID);
}
}
// 插入資料
foreach (var item in dataObj)
{
try
{
// 轉換為本地模型
var apiModel = (SignUpItem)item;
var localItem = LocalSignUpItem.ConvertFromApiModel(apiModel);
// 檢查是否已存在
var existing = _signUpsRepo.GetById(localItem.ID);
if (existing != null)
{
_signUpsRepo.Update(localItem);
}
else
{
_signUpsRepo.Insert(localItem);
}
}
catch
{
// 忽略單筆資料錯誤,繼續同步其他資料
}
}
return true;
}
catch (Exception ex)
{
throw new Exception($"同步 SignUps 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 上傳本地 SignUps 簽到簽退資料到 API
/// </summary>
public async Task<bool> UploadSignUpsToApiAsync(int courseID)
{
try
{
var localItems = _signUpsRepo.GetByCourseID(courseID);
if (localItems.Count == 0)
return true;
// 轉換為 API 模型
var apiItems = new List<SignUpItem>();
foreach (var item in localItems)
{
apiItems.Add(LocalSignUpItem.ConvertToApiModel(item));
}
// 上傳到 API
var response = await SignUpService.UploadSignUpsAsync(courseID, apiItems);
return response.Success;
}
catch (Exception ex)
{
throw new Exception($"上傳 SignUps 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 上傳本地 SignUps 簽到簽退資料到 API(同步版本)
/// </summary>
public bool UploadSignUpsToApi(int courseID)
{
try
{
var localItems = _signUpsRepo.GetByCourseID(courseID);
if (localItems.Count == 0)
return true;
// 轉換為 API 模型
var apiItems = new List<SignUpItem>();
foreach (var item in localItems)
{
apiItems.Add(LocalSignUpItem.ConvertToApiModel(item));
}
// 上傳到 API
var response = SignUpService.UploadSignUps(courseID, apiItems);
return response.Success;
}
catch (Exception ex)
{
throw new Exception($"上傳 SignUps 資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 清除所有本地資料
/// </summary>
public void ClearAllLocalData()
{
try
{
DatabaseService.Instance.ClearAllTables();
}
catch (Exception ex)
{
throw new Exception($"清除本地資料失敗:{ex.Message}", ex);
}
}
/// <summary>
/// 取得 Classes Repository
/// </summary>
public ClassesRepository GetClassesRepository()
{
return _classesRepo;
}
/// <summary>
/// 取得 SignUps Repository
/// </summary>
public SignUpsRepository GetSignUpsRepository()
{
return _signUpsRepo;
}
}
}
+231
View File
@@ -0,0 +1,231 @@
namespace
{
partial class ExistConfirmForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.label3 = new System.Windows.Forms.Label();
this.lbName = new System.Windows.Forms.Label();
this.lbMobile = new System.Windows.Forms.Label();
this.label4 = new System.Windows.Forms.Label();
this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel();
this.button1 = new System.Windows.Forms.Button();
this.btnNo = new System.Windows.Forms.Button();
this.btnYes = new System.Windows.Forms.Button();
this.tableLayoutPanel1.SuspendLayout();
this.flowLayoutPanel1.SuspendLayout();
this.SuspendLayout();
//
// tableLayoutPanel1
//
this.tableLayoutPanel1.ColumnCount = 4;
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 24F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 142F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 24F));
this.tableLayoutPanel1.Controls.Add(this.label1, 1, 1);
this.tableLayoutPanel1.Controls.Add(this.label2, 1, 2);
this.tableLayoutPanel1.Controls.Add(this.label3, 1, 3);
this.tableLayoutPanel1.Controls.Add(this.lbName, 2, 2);
this.tableLayoutPanel1.Controls.Add(this.lbMobile, 2, 3);
this.tableLayoutPanel1.Controls.Add(this.label4, 1, 4);
this.tableLayoutPanel1.Controls.Add(this.flowLayoutPanel1, 1, 5);
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
this.tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(4);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.RowCount = 7;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 24F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 56F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 56F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 56F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 56F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 56F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 24F));
this.tableLayoutPanel1.Size = new System.Drawing.Size(598, 332);
this.tableLayoutPanel1.TabIndex = 0;
//
// label1
//
this.label1.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.label1.AutoSize = true;
this.tableLayoutPanel1.SetColumnSpan(this.label1, 3);
this.label1.Location = new System.Drawing.Point(28, 32);
this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(529, 40);
this.label1.TabIndex = 0;
this.label1.Text = "用姓名或手機號找到預約報名資料:";
//
// label2
//
this.label2.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(81, 88);
this.label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(81, 40);
this.label2.TabIndex = 1;
this.label2.Text = "姓名";
//
// label3
//
this.label3.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(49, 144);
this.label3.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(113, 40);
this.label3.TabIndex = 2;
this.label3.Text = "手機號";
//
// lbName
//
this.lbName.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.lbName.AutoSize = true;
this.lbName.Font = new System.Drawing.Font("微軟正黑體", 24F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(136)));
this.lbName.ForeColor = System.Drawing.Color.Blue;
this.lbName.Location = new System.Drawing.Point(170, 88);
this.lbName.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lbName.Name = "lbName";
this.lbName.Size = new System.Drawing.Size(81, 40);
this.lbName.TabIndex = 3;
this.lbName.Text = "姓名";
//
// lbMobile
//
this.lbMobile.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.lbMobile.AutoSize = true;
this.lbMobile.Font = new System.Drawing.Font("微軟正黑體", 24F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(136)));
this.lbMobile.ForeColor = System.Drawing.Color.Blue;
this.lbMobile.Location = new System.Drawing.Point(170, 144);
this.lbMobile.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lbMobile.Name = "lbMobile";
this.lbMobile.Size = new System.Drawing.Size(113, 40);
this.lbMobile.TabIndex = 4;
this.lbMobile.Text = "手機號";
//
// label4
//
this.label4.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.label4.AutoSize = true;
this.tableLayoutPanel1.SetColumnSpan(this.label4, 3);
this.label4.Location = new System.Drawing.Point(28, 200);
this.label4.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(401, 40);
this.label4.TabIndex = 5;
this.label4.Text = "是否確認是此位學員資料?";
//
// flowLayoutPanel1
//
this.tableLayoutPanel1.SetColumnSpan(this.flowLayoutPanel1, 2);
this.flowLayoutPanel1.Controls.Add(this.button1);
this.flowLayoutPanel1.Controls.Add(this.btnNo);
this.flowLayoutPanel1.Controls.Add(this.btnYes);
this.flowLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.flowLayoutPanel1.FlowDirection = System.Windows.Forms.FlowDirection.RightToLeft;
this.flowLayoutPanel1.Location = new System.Drawing.Point(24, 248);
this.flowLayoutPanel1.Margin = new System.Windows.Forms.Padding(0);
this.flowLayoutPanel1.Name = "flowLayoutPanel1";
this.flowLayoutPanel1.Size = new System.Drawing.Size(550, 56);
this.flowLayoutPanel1.TabIndex = 6;
//
// button1
//
this.button1.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.button1.Location = new System.Drawing.Point(273, 4);
this.button1.Margin = new System.Windows.Forms.Padding(4);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(273, 52);
this.button1.TabIndex = 2;
this.button1.Text = "C 取消重新輸入";
this.button1.UseVisualStyleBackColor = true;
//
// btnNo
//
this.btnNo.DialogResult = System.Windows.Forms.DialogResult.No;
this.btnNo.Location = new System.Drawing.Point(140, 4);
this.btnNo.Margin = new System.Windows.Forms.Padding(4);
this.btnNo.Name = "btnNo";
this.btnNo.Size = new System.Drawing.Size(125, 52);
this.btnNo.TabIndex = 1;
this.btnNo.TabStop = false;
this.btnNo.Text = "N 否";
this.btnNo.UseVisualStyleBackColor = true;
//
// btnYes
//
this.btnYes.DialogResult = System.Windows.Forms.DialogResult.Yes;
this.btnYes.Location = new System.Drawing.Point(7, 4);
this.btnYes.Margin = new System.Windows.Forms.Padding(4);
this.btnYes.Name = "btnYes";
this.btnYes.Size = new System.Drawing.Size(125, 52);
this.btnYes.TabIndex = 0;
this.btnYes.TabStop = false;
this.btnYes.Text = "Y 是";
this.btnYes.UseVisualStyleBackColor = true;
//
// ExistConfirmForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(19F, 40F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(598, 332);
this.Controls.Add(this.tableLayoutPanel1);
this.Font = new System.Drawing.Font("微軟正黑體", 24F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(136)));
this.KeyPreview = true;
this.Margin = new System.Windows.Forms.Padding(10, 9, 10, 9);
this.Name = "ExistConfirmForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
this.Text = "確認";
this.Shown += new System.EventHandler(this.ExistConfirmForm_Shown);
this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.ExistConfirmForm_KeyDown);
this.tableLayoutPanel1.ResumeLayout(false);
this.tableLayoutPanel1.PerformLayout();
this.flowLayoutPanel1.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.Label lbName;
private System.Windows.Forms.Label lbMobile;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1;
private System.Windows.Forms.Button btnNo;
private System.Windows.Forms.Button btnYes;
private System.Windows.Forms.Button button1;
}
}
+50
View File
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace
{
public partial class ExistConfirmForm : Form
{
public string { get { return lbName.Text; } set { lbName.Text = value; } }
public string { get { return lbMobile.Text; } set { lbMobile.Text = value; } }
public ExistConfirmForm()
{
InitializeComponent();
}
private void ExistConfirmForm_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Y)
{
DialogResult = DialogResult.Yes;
e.SuppressKeyPress = true;
return;
}
if (e.KeyCode == Keys.N)
{
DialogResult = DialogResult.No;
e.SuppressKeyPress = true;
return;
}
if (e.KeyCode == Keys.C)
{
DialogResult = DialogResult.Cancel;
e.SuppressKeyPress = true;
return;
}
}
private void ExistConfirmForm_Shown(object sender, EventArgs e)
{
Services.NotificationSoundService.Ding();
}
}
}
+120
View File
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
+227
View File
@@ -0,0 +1,227 @@
namespace
{
partial class LoginForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.label3 = new System.Windows.Forms.Label();
this.txtUserID = new System.Windows.Forms.TextBox();
this.txtPassword = new System.Windows.Forms.TextBox();
this.btnLogin = new System.Windows.Forms.Button();
this.btnCancel = new System.Windows.Forms.Button();
this.cbOffline = new System.Windows.Forms.CheckBox();
this.lbApiUrlBase = new System.Windows.Forms.Label();
this.lbVersion = new System.Windows.Forms.Label();
this.tableLayoutPanel1.SuspendLayout();
this.SuspendLayout();
//
// tableLayoutPanel1
//
this.tableLayoutPanel1.ColumnCount = 4;
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 40F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 45F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 55F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 49F));
this.tableLayoutPanel1.Controls.Add(this.label1, 1, 1);
this.tableLayoutPanel1.Controls.Add(this.label2, 1, 2);
this.tableLayoutPanel1.Controls.Add(this.label3, 1, 3);
this.tableLayoutPanel1.Controls.Add(this.txtUserID, 2, 2);
this.tableLayoutPanel1.Controls.Add(this.txtPassword, 2, 3);
this.tableLayoutPanel1.Controls.Add(this.btnLogin, 1, 5);
this.tableLayoutPanel1.Controls.Add(this.btnCancel, 2, 5);
this.tableLayoutPanel1.Controls.Add(this.cbOffline, 2, 4);
this.tableLayoutPanel1.Controls.Add(this.lbApiUrlBase, 1, 6);
this.tableLayoutPanel1.Controls.Add(this.lbVersion, 2, 0);
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
this.tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.RowCount = 7;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 60F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 42F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 42F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 42F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 42F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel1.Size = new System.Drawing.Size(469, 289);
this.tableLayoutPanel1.TabIndex = 0;
//
// label1
//
this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.label1.AutoSize = true;
this.tableLayoutPanel1.SetColumnSpan(this.label1, 2);
this.label1.Font = new System.Drawing.Font("微軟正黑體", 21.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label1.Location = new System.Drawing.Point(45, 41);
this.label1.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(370, 37);
this.label1.TabIndex = 0;
this.label1.Text = "新譽管理課程報到系統";
this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// label2
//
this.label2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(43, 101);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(165, 20);
this.label2.TabIndex = 1;
this.label2.Text = "登入帳號:";
this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// label3
//
this.label3.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(43, 143);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(165, 20);
this.label3.TabIndex = 2;
this.label3.Text = "登入密碼:";
this.label3.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// txtUserID
//
this.txtUserID.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.txtUserID.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.txtUserID.ImeMode = System.Windows.Forms.ImeMode.Close;
this.txtUserID.Location = new System.Drawing.Point(214, 96);
this.txtUserID.Name = "txtUserID";
this.txtUserID.Size = new System.Drawing.Size(150, 29);
this.txtUserID.TabIndex = 5;
//
// txtPassword
//
this.txtPassword.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.txtPassword.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.txtPassword.ImeMode = System.Windows.Forms.ImeMode.Close;
this.txtPassword.Location = new System.Drawing.Point(214, 138);
this.txtPassword.Name = "txtPassword";
this.txtPassword.PasswordChar = '*';
this.txtPassword.Size = new System.Drawing.Size(150, 29);
this.txtPassword.TabIndex = 6;
//
// btnLogin
//
this.btnLogin.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.btnLogin.DialogResult = System.Windows.Forms.DialogResult.OK;
this.btnLogin.Location = new System.Drawing.Point(123, 219);
this.btnLogin.Name = "btnLogin";
this.btnLogin.Size = new System.Drawing.Size(85, 36);
this.btnLogin.TabIndex = 3;
this.btnLogin.Text = "登入";
this.btnLogin.UseVisualStyleBackColor = true;
this.btnLogin.Click += new System.EventHandler(this.btnLogin_Click);
//
// btnCancel
//
this.btnCancel.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.btnCancel.Location = new System.Drawing.Point(214, 219);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(85, 36);
this.btnCancel.TabIndex = 4;
this.btnCancel.Text = "取消";
this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
//
// cbOffline
//
this.cbOffline.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.cbOffline.AutoSize = true;
this.cbOffline.Location = new System.Drawing.Point(214, 183);
this.cbOffline.Name = "cbOffline";
this.cbOffline.Size = new System.Drawing.Size(203, 24);
this.cbOffline.TabIndex = 7;
this.cbOffline.Text = "離線模式";
this.cbOffline.UseVisualStyleBackColor = true;
this.cbOffline.CheckStateChanged += new System.EventHandler(this.cbOffline_CheckStateChanged);
//
// lbApiUrlBase
//
this.lbApiUrlBase.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.lbApiUrlBase.AutoSize = true;
this.tableLayoutPanel1.SetColumnSpan(this.lbApiUrlBase, 2);
this.lbApiUrlBase.Font = new System.Drawing.Font("微軟正黑體", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(136)));
this.lbApiUrlBase.Location = new System.Drawing.Point(43, 265);
this.lbApiUrlBase.Name = "lbApiUrlBase";
this.lbApiUrlBase.Size = new System.Drawing.Size(374, 17);
this.lbApiUrlBase.TabIndex = 1;
this.lbApiUrlBase.Text = "(主機網址)";
this.lbApiUrlBase.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// lbVersion
//
this.lbVersion.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.lbVersion.AutoSize = true;
this.lbVersion.Font = new System.Drawing.Font("微軟正黑體", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(136)));
this.lbVersion.Location = new System.Drawing.Point(336, 6);
this.lbVersion.Name = "lbVersion";
this.lbVersion.Size = new System.Drawing.Size(81, 17);
this.lbVersion.TabIndex = 8;
this.lbVersion.Text = "(程式版本號)";
//
// LoginForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
this.ClientSize = new System.Drawing.Size(469, 289);
this.Controls.Add(this.tableLayoutPanel1);
this.Font = new System.Drawing.Font("微軟正黑體", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.Margin = new System.Windows.Forms.Padding(5, 4, 5, 4);
this.Name = "LoginForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "登入";
this.Activated += new System.EventHandler(this.LoginForm_Activated);
this.tableLayoutPanel1.ResumeLayout(false);
this.tableLayoutPanel1.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.Button btnLogin;
private System.Windows.Forms.Button btnCancel;
private System.Windows.Forms.TextBox txtUserID;
private System.Windows.Forms.TextBox txtPassword;
private System.Windows.Forms.CheckBox cbOffline;
private System.Windows.Forms.Label lbApiUrlBase;
private System.Windows.Forms.Label lbVersion;
}
}
+84
View File
@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using .Services;
namespace
{
public partial class LoginForm : Form
{
public LoginForm()
{
InitializeComponent();
lbApiUrlBase.Text = string.Format("主機:{0}", ApiService.Instance.BaseUrl);
lbVersion.Text = ClientSession.Version;
}
private void btnCancel_Click(object sender, EventArgs e)
{
Application.Exit();
}
private async void btnLogin_Click(object sender, EventArgs e)
{
try
{
btnLogin.Enabled = false;
btnCancel.Enabled = false;
btnLogin.Text = "登入中";
Cursor.Current = Cursors.WaitCursor;
Application.DoEvents();
if (cbOffline.Checked)
{
var loginResult = ClientSession.LoginOffline(txtUserID.Text);
if (loginResult.Item1)
{
DialogResult = DialogResult.OK;
}
else
{
MessageBox.Show(loginResult.Item2, "登入錯誤");
}
}
else
{
var loginResult = await ClientSession.LoginAsync(txtUserID.Text, txtPassword.Text);
if (loginResult.Item1)
{
DialogResult = DialogResult.OK;
}
else
{
MessageBox.Show(loginResult.Item2, "登入錯誤");
}
}
}
finally
{
Cursor.Current = Cursors.Default;
btnLogin.Enabled = true;
btnCancel.Enabled = true;
btnLogin.Text = "登入";
}
}
private void LoginForm_Activated(object sender, EventArgs e)
{
txtUserID.Focus();
}
private void cbOffline_CheckStateChanged(object sender, EventArgs e)
{
txtPassword.Enabled = !cbOffline.Checked;
}
}
}
+120
View File
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+174
View File
@@ -0,0 +1,174 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>34, 15</value>
</metadata>
<metadata name="ColSeqNo.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="ColName.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="ColMobile.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="ColCheckInTime.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="ColCheckOutTime.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="ColIDNumber.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="ColIDType.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="ColMealType.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="ColEmail.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="ColGender.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="ColLossJobTimes.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="AgreementStatus.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="bsSignUp.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>271, 15</value>
</metadata>
<metadata name="downloadWorker.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>137, 15</value>
</metadata>
<metadata name="uploadWorker.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>362, 15</value>
</metadata>
<metadata name="uploadTimer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>475, 15</value>
</metadata>
<metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>59</value>
</metadata>
</root>
+40
View File
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace .Models
{
/// <summary>
/// 員工資訊項目
/// </summary>
public class BPSNItem
{
[JsonProperty("BPENO")]
public string BPENO { get; set; }
[JsonProperty("BPNME")]
public string BPNME { get; set; }
[JsonProperty("BPAWD")]
public string BPAWD { get; set; }
[JsonProperty("Is系統管理群組")]
public int Is系統管理群組 { get; set; }
[JsonProperty("Is客戶管理群組")]
public int Is客戶管理群組 { get; set; }
[JsonProperty("Is現場工作群組")]
public int Is現場工作群組 { get; set; }
[JsonProperty("Is查詢群組")]
public int Is查詢群組 { get; set; }
}
/// <summary>
/// 員工資訊清單
/// </summary>
public class BPSNList : List<BPSNItem>
{
}
}
@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace .Models
{
/// <summary>
/// API 回應基礎類別
/// </summary>
public class BaseResponse
{
[JsonProperty("Success")]
public bool Success { get; set; }
[JsonProperty("Message")]
public string Message { get; set; }
[JsonProperty("Data")]
public dynamic Data { get; set; }
[JsonProperty("TotalCount")]
public int TotalCount { get; set; }
}
/// <summary>
/// 登入回應資料
/// </summary>
public class LoginResponse
{
[JsonProperty("UserName")]
public string UserName { get; set; }
}
/// <summary>
/// 檢查 Session 回應資料
/// </summary>
public class CheckSessionResponse
{
[JsonProperty("UserName")]
public string UserName { get; set; }
[JsonProperty("LoginTime")]
public DateTime LoginTime { get; set; }
}
}
@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace .Models
{
/// <summary>
/// 簽到簽退紀錄項目
/// </summary>
public class CheckInItem
{
[JsonProperty("ID")]
public int ID { get; set; }
[JsonProperty("CourseID")]
public int CourseID { get; set; }
[JsonProperty("Name")]
public string Name { get; set; }
[JsonProperty("Mobile")]
public string Mobile { get; set; }
[JsonProperty("SignUpType")]
public string SignUpType { get; set; }
[JsonProperty("IDNumber")]
public string IDNumber { get; set; }
[JsonProperty("IDType")]
public string IDType { get; set; }
[JsonProperty("MealType")]
public string MealType { get; set; }
[JsonProperty("Email")]
public string Email { get; set; }
[JsonProperty("LossJobTimes")]
public int? LossJobTimes { get; set; }
[JsonProperty("Gender")]
public string Gender { get; set; }
[JsonProperty("AgreementStatus")]
public string AgreementStatus { get; set; }
[JsonProperty("CheckInType")]
public string CheckInType { get; set; }
[JsonProperty("CheckInTime")]
public string CheckInTime { get; set; }
}
/// <summary>
/// 簽到簽退紀錄清單
/// </summary>
public class CheckInList : List<CheckInItem>
{
}
}
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace .Models
{
/// <summary>
/// 活動資訊項目
/// </summary>
public class ClassesItem
{
[JsonProperty("ID")]
public int ID { get; set; }
[JsonProperty("CourseCode")]
public string CourseCode { get; set; }
[JsonProperty("CourseName")]
public string CourseName { get; set; }
[JsonProperty("CourseDate")]
public DateTime CourseDate { get; set; }
[JsonProperty("CourseLocation")]
public string CourseLocation { get; set; }
[JsonProperty("StartTime")]
public string StartTime { get; set; }
[JsonProperty("EndTime")]
public string EndTime { get; set; }
[JsonProperty("Status")]
public string Status { get; set; }
[JsonProperty("Description")]
public string Description { get; set; }
[JsonProperty("NotificationTemplate")]
public string NotificationTemplate { get; set; }
[JsonProperty("AgreedTerms")]
public string AgreedTerms { get; set; }
}
/// <summary>
/// 活動資訊清單
/// </summary>
public class ClassesList : List<ClassesItem>
{
}
}
@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace .Models
{
/// <summary>
/// 報名人員項目
/// </summary>
public class SignUpItem
{
[JsonProperty("ID")]
public int ID { get; set; }
[JsonProperty("CourseID")]
public int CourseID { get; set; }
[JsonProperty("SeqNo")]
public int SeqNo { get; set; }
[JsonProperty("Name")]
public string Name { get; set; }
[JsonProperty("Mobile")]
public string Mobile { get; set; }
[JsonProperty("Email")]
public string Email { get; set; }
[JsonProperty("SignUpType")]
public string SignUpType { get; set; }
[JsonProperty("IDNumber")]
public string IDNumber { get; set; }
[JsonProperty("IDType")]
public string IDType { get; set; }
[JsonProperty("MealType")]
public string MealType { get; set; }
[JsonProperty("QRCode")]
public string QRCode { get; set; }
[JsonProperty("LossJobTimes")]
public int? LossJobTimes { get; set; }
[JsonProperty("Gender")]
public string Gender { get; set; }
[JsonProperty("AgreementStatus")]
public string AgreementStatus { get; set; }
[JsonProperty("CheckInTime")]
public string CheckInTime { get; set; }
[JsonProperty("CheckOutTime")]
public string CheckOutTime { get; set; }
[JsonProperty("Remark")]
public string Remark { get; set; }
[JsonProperty("DBAppNo")]
public int DBAppNo { get; set; }
}
/// <summary>
/// 報名人員清單
/// </summary>
public class SignUpList : List<SignUpItem>
{
}
}
+66
View File
@@ -0,0 +1,66 @@
namespace
{
partial class ProcessingForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.label1 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// label1
//
this.label1.AutoSize = true;
this.label1.ForeColor = System.Drawing.Color.White;
this.label1.Location = new System.Drawing.Point(55, 34);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(320, 35);
this.label1.TabIndex = 0;
this.label1.Text = "上下傳處理中,請稍候.....";
//
// ProcessingForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
this.BackColor = System.Drawing.Color.DodgerBlue;
this.ClientSize = new System.Drawing.Size(444, 105);
this.Controls.Add(this.label1);
this.Font = new System.Drawing.Font("微軟正黑體", 20F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(136)));
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.Margin = new System.Windows.Forms.Padding(8, 9, 8, 9);
this.Name = "ProcessingForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "處理中";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label label1;
}
}
+20
View File
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace
{
public partial class ProcessingForm : Form
{
public ProcessingForm()
{
InitializeComponent();
}
}
}
+120
View File
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
+22
View File
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace
{
internal static class Program
{
/// <summary>
/// 應用程式的主要進入點。
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}
@@ -0,0 +1,33 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// 組件的一般資訊是由下列的屬性集控制。
// 變更這些屬性的值即可修改組件的相關
// 資訊。
[assembly: AssemblyTitle("報到系統客戶端")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Maolaoda Studio")]
[assembly: AssemblyProduct("報到系統客戶端")]
[assembly: AssemblyCopyright("Copyright © Maolaoda Studio 2026")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// 將 ComVisible 設為 false 可對 COM 元件隱藏
// 組件中的類型。若必須從 COM 存取此組件中的類型,
// 的類型,請在該類型上將 ComVisible 屬性設定為 true。
[assembly: ComVisible(false)]
// 下列 GUID 為專案公開 (Expose) 至 COM 時所要使用的 typelib ID
[assembly: Guid("8b1277b9-7958-4b49-b8e5-3d32a3a0311c")]
// 組件的版本資訊由下列四個值所組成:
//
// 主要版本
// 次要版本
// 組建編號
// 修訂編號
//
[assembly: AssemblyVersion("26.7.24")]
[assembly: AssemblyFileVersion("26.7.24")]
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This file is automatically generated by Visual Studio .Net. It is
used to store generic object data source configuration information.
Renaming the file extension or editing the content of this file may
cause the file to be unrecognizable by the program.
-->
<GenericObjectDataSource DisplayName="LocalSignUpItem" Version="1.0" xmlns="urn:schemas-microsoft-com:xml-msdatasource">
<TypeInfo>報到系統客戶端.Data.Models.LocalSignUpItem, 報到系統客戶端, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</TypeInfo>
</GenericObjectDataSource>
+63
View File
@@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 這段程式碼是由工具產生的。
// 執行階段版本:4.0.30319.42000
//
// 對這個檔案所做的變更可能會造成錯誤的行為,而且如果重新產生程式碼,
// 變更將會遺失。
// </auto-generated>
//------------------------------------------------------------------------------
namespace .Properties {
using System;
/// <summary>
/// 用於查詢當地語系化字串等的強類型資源類別。
/// </summary>
// 這個類別是自動產生的,是利用 StronglyTypedResourceBuilder
// 類別透過 ResGen 或 Visual Studio 這類工具。
// 若要加入或移除成員,請編輯您的 .ResX 檔,然後重新執行 ResGen
// (利用 /str 選項),或重建您的 VS 專案。
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// 傳回這個類別使用的快取的 ResourceManager 執行個體。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("報到系統客戶端.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// 覆寫目前執行緒的 CurrentUICulture 屬性,對象是所有
/// 使用這個強類型資源類別的資源查閱。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}
@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
+35
View File
@@ -0,0 +1,35 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 這段程式碼是由工具產生的。
// 執行階段版本:4.0.30319.42000
//
// 對這個檔案所做的變更可能會造成錯誤的行為,而且如果重新產生程式碼,
// 變更將會遺失。
// </auto-generated>
//------------------------------------------------------------------------------
namespace .Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.14.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
[global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("http://localhost:58792")]
public string ApiBaseUrl {
get {
return ((string)(this["ApiBaseUrl"]));
}
}
}
}
@@ -0,0 +1,9 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="報到系統客戶端.Properties" GeneratedClassName="Settings">
<Profiles />
<Settings>
<Setting Name="ApiBaseUrl" Type="System.String" Scope="Application">
<Value Profile="(Default)">http://localhost:58792</Value>
</Setting>
</Settings>
</SettingsFile>
@@ -0,0 +1,328 @@
using Newtonsoft.Json;
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using .Models;
namespace .Services
{
/// <summary>
/// API 服務基礎類別
/// 處理 HTTP 請求、Session cookie、錯誤處理
/// </summary>
public class ApiService
{
private static ApiService _instance;
private static readonly object _lockObject = new object();
private string _baseUrl;
private CookieContainer _cookieContainer;
// 用於儲存 cookie 的檔案路徑
private string _cookiePath;
public static JsonSerializerSettings jsonConvertSettings = new JsonSerializerSettings { DateTimeZoneHandling = DateTimeZoneHandling.Local };
public static object DeserializeObject(string value)
{
return JsonConvert.DeserializeObject(value, jsonConvertSettings);
}
public static T DeserializeObject<T>(string value)
{
return (T)JsonConvert.DeserializeObject(value, typeof(T), jsonConvertSettings);
}
/// <summary>
/// 取得單例實例
/// </summary>
public static ApiService Instance
{
get
{
if (_instance == null)
{
lock (_lockObject)
{
if (_instance == null)
{
_instance = new ApiService();
}
}
}
return _instance;
}
}
private ApiService()
{
// 從設定檔讀取基底 URL
_baseUrl = ConfigService.Instance.GetApiBaseUrl();
if (string.IsNullOrWhiteSpace(_baseUrl))
{
_baseUrl = "http://localhost:58792";
}
// 初始化 Cookie 容器
_cookieContainer = new CookieContainer();
_cookiePath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"報到系統客戶端",
"cookies.txt"
);
// 嘗試載入已儲存的 Cookie
LoadCookies();
}
/// <summary>
/// 設定基底 URL
/// </summary>
public string BaseUrl
{
set
{
_baseUrl = value;
}
get
{
return _baseUrl;
}
}
/// <summary>
/// 執行 POST 請求
/// </summary>
public async Task<BaseResponse> PostAsync(string servicePath, string methodName, object parameters)
{
try
{
string url = $"{_baseUrl}{servicePath}/{methodName}";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.ContentType = "application/json; charset=utf-8";
request.CookieContainer = _cookieContainer;
request.KeepAlive = true;
// 序列化參數
string jsonData = JsonConvert.SerializeObject(parameters ?? new { });
// 寫入請求體
byte[] data = Encoding.UTF8.GetBytes(jsonData);
request.ContentLength = data.Length;
using (Stream requestStream = request.GetRequestStream())
{
await requestStream.WriteAsync(data, 0, data.Length);
}
// 取得回應
using (HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync())
{
// 更新 Cookie(用於 Session 管理)
_cookieContainer.Add(response.ResponseUri, response.Cookies);
SaveCookies();
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
string responseJson = await reader.ReadToEndAsync();
// 解析回應
dynamic result = DeserializeObject(responseJson);
// ASP.NET Web Service 會將結果包在 "d" 屬性中
if (result.d != null)
{
string dataJson = JsonConvert.SerializeObject(result.d);
return DeserializeObject<BaseResponse>(dataJson);
}
else
{
return DeserializeObject<BaseResponse>(responseJson);
}
}
}
}
catch (WebException ex)
{
return new BaseResponse
{
Success = false,
Message = $"網路錯誤: {ex.Message}"
};
}
catch (Exception ex)
{
return new BaseResponse
{
Success = false,
Message = $"錯誤: {ex.Message}"
};
}
}
/// <summary>
/// 同步版本的 POST 請求(用於向後相容)
/// </summary>
public BaseResponse Post(string servicePath, string methodName, object parameters)
{
try
{
string url = $"{_baseUrl}{servicePath}/{methodName}";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.ContentType = "application/json; charset=utf-8";
request.CookieContainer = _cookieContainer;
request.KeepAlive = true;
// 序列化參數
string jsonData = JsonConvert.SerializeObject(parameters ?? new { });
// 寫入請求體
byte[] data = Encoding.UTF8.GetBytes(jsonData);
request.ContentLength = data.Length;
using (Stream requestStream = request.GetRequestStream())
{
requestStream.Write(data, 0, data.Length);
}
// 取得回應
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
// 更新 Cookie(用於 Session 管理)
_cookieContainer.Add(response.ResponseUri, response.Cookies);
SaveCookies();
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
string responseJson = reader.ReadToEnd();
// 解析回應
dynamic result = DeserializeObject(responseJson);
// ASP.NET Web Service 會將結果包在 "d" 屬性中
if (result.d != null)
{
string dataJson = JsonConvert.SerializeObject(result.d);
return DeserializeObject<BaseResponse>(dataJson);
}
else
{
return DeserializeObject<BaseResponse>(responseJson);
}
}
}
}
catch (WebException ex)
{
return new BaseResponse
{
Success = false,
Message = $"網路錯誤: {ex.Message}"
};
}
catch (Exception ex)
{
return new BaseResponse
{
Success = false,
Message = $"錯誤: {ex.Message}"
};
}
}
/// <summary>
/// 清除所有 Session cookie
/// </summary>
public void ClearSession()
{
_cookieContainer = new CookieContainer();
DeleteCookies();
}
/// <summary>
/// 儲存 Cookie 到檔案
/// </summary>
private void SaveCookies()
{
try
{
string directory = Path.GetDirectoryName(_cookiePath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
using (StreamWriter sw = new StreamWriter(_cookiePath, false, Encoding.UTF8))
{
foreach (Cookie cookie in _cookieContainer.GetCookies(new Uri(_baseUrl)))
{
sw.WriteLine($"{cookie.Name}={cookie.Value}");
}
}
}
catch
{
// 忽略儲存錯誤
}
}
/// <summary>
/// 從檔案載入 Cookie
/// </summary>
private void LoadCookies()
{
try
{
if (File.Exists(_cookiePath))
{
using (StreamReader sr = new StreamReader(_cookiePath, Encoding.UTF8))
{
string line;
while ((line = sr.ReadLine()) != null)
{
if (string.IsNullOrWhiteSpace(line)) continue;
string[] parts = line.Split(new[] { '=' }, 2);
if (parts.Length == 2)
{
Cookie cookie = new Cookie(parts[0], parts[1])
{
Path = "/",
HttpOnly = true
};
_cookieContainer.Add(new Uri(_baseUrl), cookie);
}
}
}
}
}
catch
{
// 忽略載入錯誤
}
}
/// <summary>
/// 刪除儲存的 Cookie 檔案
/// </summary>
private void DeleteCookies()
{
try
{
if (File.Exists(_cookiePath))
{
File.Delete(_cookiePath);
}
}
catch
{
// 忽略刪除錯誤
}
}
}
}
@@ -0,0 +1,32 @@
using System;
using System.Threading.Tasks;
using .Models;
namespace .Services
{
/// <summary>
/// 員工資料服務客戶端
/// 呼叫後端 Services/BPSNService.asmx
/// </summary>
public class BPSNService
{
private const string SERVICE_PATH = "/Services/BPSNService.asmx";
/// <summary>
/// 下載員工資料
/// </summary>
/// <returns>下載結果,包含員工資料清單</returns>
public static BaseResponse Download()
{
return ApiService.Instance.Post(SERVICE_PATH, "Download", null);
}
/// <summary>
/// 下載員工資料(非同步版本)
/// </summary>
//public static async Task<BaseResponse> DownloadAsync()
//{
// return await ApiService.Instance.PostAsync(SERVICE_PATH, "Download", null);
//}
}
}
@@ -0,0 +1,66 @@
using System;
using System.Threading.Tasks;
using .Models;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.ToolTip;
namespace .Services
{
/// <summary>
/// 活動服務客戶端
/// 呼叫後端 Services/ClassesService.asmx
/// </summary>
public class ClassesService
{
private const string SERVICE_PATH = "/Services/ClassesService.asmx";
/// <summary>
/// 搜尋活動清單
/// </summary>
/// <param name="pageIndex">頁碼(從 1 開始)</param>
/// <param name="pageSize">每頁筆數</param>
/// <param name="courseCode">活動代碼</param>
/// <param name="courseName">活動名稱</param>
/// <param name="courseDate1">活動開始日期</param>
/// <param name="courseDate2">活動結束日期</param>
/// <param name="courseLocation">活動地點</param>
/// <returns>搜尋結果</returns>
public static BaseResponse SearchClasses()
{
var parameters = new
{
pageIndex = 1,
pageSize = Int32.MaxValue,
courseCode = string.Empty,
courseName = string.Empty,
courseDate1 = new DateTime?(),
courseDate2 = new DateTime?(),
courseLocation = string.Empty,
projectID = string.Empty,
closed = false
};
return ApiService.Instance.Post(SERVICE_PATH, "SearchClasses", parameters);
}
/// <summary>
/// 搜尋活動清單(非同步版本)
/// </summary>
//public static async Task<BaseResponse> SearchClassesAsync()
//{
// var parameters = new
// {
// pageIndex = 1,
// pageSize = Int32.MaxValue,
// courseCode = string.Empty,
// courseName = string.Empty,
// courseDate1 = new DateTime?(),
// courseDate2 = new DateTime?(),
// courseLocation = string.Empty,
// projectID = string.Empty,
// closed = false
// };
// return await ApiService.Instance.PostAsync(SERVICE_PATH, "SearchClasses", parameters);
//}
}
}
@@ -0,0 +1,254 @@
using System;
using System.Configuration;
using System.IO;
using System.Xml;
namespace .Services
{
/// <summary>
/// 系統設定管理服務
/// 提供讀取和寫入應用程式設定的功能
/// 設定檔存儲在用戶的 AppData 資料夾中,以避免 Program Files 權限問題
/// </summary>
public class ConfigService
{
private static ConfigService _instance;
private static readonly object _lockObject = new object();
private string _userConfigPath;
private string _defaultConfigPath;
/// <summary>
/// 取得 ConfigService 的單一實例
/// </summary>
public static ConfigService Instance
{
get
{
if (_instance == null)
{
lock (_lockObject)
{
if (_instance == null)
{
_instance = new ConfigService();
}
}
}
return _instance;
}
}
private ConfigService()
{
_defaultConfigPath = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
// 用戶設定檔存儲在 AppData\Roaming\報到系統客戶端 資料夾
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string configDirectory = Path.Combine(appDataPath, "報到系統客戶端");
// 確保目錄存在
if (!Directory.Exists(configDirectory))
{
Directory.CreateDirectory(configDirectory);
}
_userConfigPath = Path.Combine(configDirectory, "AppSettings.config");
// 如果用戶設定檔不存在,從預設設定檔複製
if (!File.Exists(_userConfigPath))
{
InitializeUserConfig();
}
}
/// <summary>
/// 初始化用戶設定檔
/// 從預設設定檔複製所需的設定項目
/// </summary>
private void InitializeUserConfig()
{
try
{
XmlDocument defaultDoc = new XmlDocument();
defaultDoc.Load(_defaultConfigPath);
XmlDocument userDoc = new XmlDocument();
// 建立新的設定檔結構
XmlDeclaration declaration = userDoc.CreateXmlDeclaration("1.0", "utf-8", null);
userDoc.AppendChild(declaration);
XmlElement configElement = userDoc.CreateElement("configuration");
userDoc.AppendChild(configElement);
// 建立 applicationSettings 節點
XmlElement appSettingsNode = userDoc.CreateElement("applicationSettings");
configElement.AppendChild(appSettingsNode);
// 建立報到系統客戶端.Properties.Settings 節點
XmlElement settingsNode = userDoc.CreateElement("報到系統客戶端.Properties.Settings");
appSettingsNode.AppendChild(settingsNode);
// 建立 ApiBaseUrl 設定項目
XmlElement settingElement = userDoc.CreateElement("setting");
settingElement.SetAttribute("name", "ApiBaseUrl");
settingElement.SetAttribute("serializeAs", "String");
XmlElement valueElement = userDoc.CreateElement("value");
valueElement.InnerText = "https://thinkyustaff.com/event";
settingElement.AppendChild(valueElement);
settingsNode.AppendChild(settingElement);
userDoc.Save(_userConfigPath);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"初始化用戶設定檔失敗: {ex.Message}");
}
}
/// <summary>
/// 讀取 API 基礎 URL
/// </summary>
/// <returns>API 基礎 URL</returns>
public string GetApiBaseUrl()
{
try
{
if (File.Exists(_userConfigPath))
{
XmlDocument doc = new XmlDocument();
doc.Load(_userConfigPath);
XmlNode valueNode = doc.SelectSingleNode(
"/configuration/applicationSettings/報到系統客戶端.Properties.Settings/setting[@name='ApiBaseUrl']/value"
);
if (valueNode != null && !string.IsNullOrWhiteSpace(valueNode.InnerText))
{
return valueNode.InnerText;
}
}
// 回退到預設值或 App.config
return Properties.Settings.Default.ApiBaseUrl ?? "https://thinkyustaff.com/event";
}
catch
{
return "https://thinkyustaff.com/event";
}
}
/// <summary>
/// 設定 API 基礎 URL
/// </summary>
/// <param name="baseUrl">新的 API 基礎 URL</param>
/// <returns>是否設定成功</returns>
public bool SetApiBaseUrl(string baseUrl)
{
if (string.IsNullOrWhiteSpace(baseUrl))
{
return false;
}
try
{
// 確保用戶設定檔存在
if (!File.Exists(_userConfigPath))
{
InitializeUserConfig();
}
XmlDocument doc = new XmlDocument();
doc.Load(_userConfigPath);
// 找到或建立 applicationSettings 節點
XmlNode appSettingsNode = doc.SelectSingleNode("//applicationSettings");
if (appSettingsNode == null)
{
XmlNode configNode = doc.SelectSingleNode("//configuration");
if (configNode == null)
{
return false;
}
appSettingsNode = doc.CreateElement("applicationSettings");
configNode.AppendChild(appSettingsNode);
}
// 找到或建立 報到系統客戶端.Properties.Settings 節點
XmlNode settingsNode = appSettingsNode.SelectSingleNode("報到系統客戶端.Properties.Settings");
if (settingsNode == null)
{
settingsNode = doc.CreateElement("報到系統客戶端.Properties.Settings");
appSettingsNode.AppendChild(settingsNode);
}
// 找到 ApiBaseUrl 設定項目
XmlNode apiBaseUrlNode = settingsNode.SelectSingleNode("setting[@name='ApiBaseUrl']");
if (apiBaseUrlNode == null)
{
apiBaseUrlNode = doc.CreateElement("setting");
apiBaseUrlNode.Attributes.Append(doc.CreateAttribute("name"));
apiBaseUrlNode.Attributes["name"].Value = "ApiBaseUrl";
apiBaseUrlNode.Attributes.Append(doc.CreateAttribute("serializeAs"));
apiBaseUrlNode.Attributes["serializeAs"].Value = "String";
settingsNode.AppendChild(apiBaseUrlNode);
}
// 更新值
XmlNode valueNode = apiBaseUrlNode.SelectSingleNode("value");
if (valueNode != null)
{
valueNode.InnerText = baseUrl;
}
else
{
// 如果沒有 value 節點,則建立一個
valueNode = doc.CreateElement("value");
valueNode.InnerText = baseUrl;
apiBaseUrlNode.AppendChild(valueNode);
}
// 保存設定檔到用戶的 AppData 資料夾
doc.Save(_userConfigPath);
// 刷新設定以更新記憶體中的值
RefreshSettings();
return true;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"設定 ApiBaseUrl 失敗: {ex.Message}");
return false;
}
}
/// <summary>
/// 刷新設定值
/// </summary>
private void RefreshSettings()
{
try
{
Properties.Settings.Default.Reload();
}
catch
{
// 刷新失敗時不執行任何操作
}
}
/// <summary>
/// 取得應用程式設定檔路徑
/// 返回用戶設定檔的路徑(位於 AppData 中)
/// </summary>
/// <returns>設定檔路徑</returns>
//public string GetConfigFilePath()
//{
// return _userConfigPath;
//}
}
}
@@ -0,0 +1,101 @@
using System;
using System.Threading.Tasks;
using .Models;
namespace .Services
{
/// <summary>
/// 登入服務客戶端
/// 呼叫後端 Services/LoginService.asmx
/// </summary>
public class LoginService
{
private const string SERVICE_PATH = "/Services/LoginService.asmx";
/// <summary>
/// 登入
/// </summary>
/// <param name="username">使用者名稱</param>
/// <param name="password">密碼</param>
/// <param name="rememberMe">是否記住密碼</param>
/// <returns>登入結果</returns>
public static BaseResponse Login(string username, string password, bool rememberMe = false)
{
var parameters = new
{
username = username,
password = password,
rememberMe = rememberMe
};
return ApiService.Instance.Post(SERVICE_PATH, "Login", parameters);
}
/// <summary>
/// 登入(非同步版本)
/// </summary>
public static async Task<BaseResponse> LoginAsync(string username, string password, bool rememberMe = false)
{
var parameters = new
{
username = username,
password = password,
rememberMe = rememberMe
};
return await ApiService.Instance.PostAsync(SERVICE_PATH, "Login", parameters);
}
/// <summary>
/// 登出
/// </summary>
/// <returns>登出結果</returns>
public static BaseResponse Logout()
{
return ApiService.Instance.Post(SERVICE_PATH, "Logout", null);
}
/// <summary>
/// 登出(非同步版本)
/// </summary>
//public static async Task<BaseResponse> LogoutAsync()
//{
// return await ApiService.Instance.PostAsync(SERVICE_PATH, "Logout", null);
//}
/// <summary>
/// 檢查 Session(當作心跳使用)
/// </summary>
/// <returns>檢查結果</returns>
public static BaseResponse CheckSession()
{
return ApiService.Instance.Post(SERVICE_PATH, "CheckSession", null);
}
/// <summary>
/// 檢查 Session(非同步版本)
/// </summary>
//public static async Task<BaseResponse> CheckSessionAsync()
//{
// return await ApiService.Instance.PostAsync(SERVICE_PATH, "CheckSession", null);
//}
/// <summary>
/// 清除 Session
/// </summary>
public static void ClearSession()
{
ApiService.Instance.ClearSession();
}
/// <summary>
/// 測試是否能連線
/// </summary>
/// <returns></returns>
public static bool Ping()
{
var pingResult = ApiService.Instance.Post(SERVICE_PATH, "Ping", null);
return pingResult != null;
}
}
}
@@ -0,0 +1,57 @@
using System;
using System.IO;
using System.Media;
namespace .Services
{
/// <summary>
/// 通知音效服務,密集呼叫時會中斷先前的播放再重新播放。
/// </summary>
public static class NotificationSoundService
{
private static readonly string _dingWavePath =
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sound\\ding.wav");
private static readonly string _checkinWavePath =
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sound\\CheckinOK.wav");
private static readonly string _checkoutWavePath =
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sound\\CheckoutOK.wav");
private static SoundPlayer _dingPlayer;
private static SoundPlayer _checkinPlayer;
private static SoundPlayer _checkoutPlayer;
private static void StopAll()
{
_dingPlayer?.Stop();
_checkinPlayer?.Stop();
_checkoutPlayer?.Stop();
}
private static void PlaySound(string path, ref SoundPlayer player)
{
if (!File.Exists(path))
return;
if (player == null)
player = new SoundPlayer(path);
StopAll();
player.Play();
}
/// <summary>
/// 播放 ding.wav;若有其他音效正在播放,先全部停止再播放。
/// </summary>
public static void Ding() => PlaySound(_dingWavePath, ref _dingPlayer);
/// <summary>
/// 播放 CheckinOK.wav;若有其他音效正在播放,先全部停止再播放。
/// </summary>
public static void CheckinOK() => PlaySound(_checkinWavePath, ref _checkinPlayer);
/// <summary>
/// 播放 CheckoutOK.wav;若有其他音效正在播放,先全部停止再播放。
/// </summary>
public static void CheckoutOK() => PlaySound(_checkoutWavePath, ref _checkoutPlayer);
}
}
@@ -0,0 +1,298 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using .Models;
namespace .Services
{
/// <summary>
/// 報名服務客戶端
/// 呼叫後端 Services/SignUpService.asmx
/// </summary>
public class SignUpService
{
private const string SERVICE_PATH = "/Services/SignUpService.asmx";
/// <summary>
/// 搜尋活動參加人員清單
/// </summary>
/// <param name="pageIndex">頁碼(從 1 開始)</param>
/// <param name="pageSize">每頁筆數</param>
/// <param name="courseID">活動編號</param>
/// <param name="name">學員姓名</param>
/// <param name="mobile">手機號</param>
/// <returns>搜尋結果</returns>
public static BaseResponse SearchSignUps(
int pageIndex,
int pageSize,
int courseID,
string name = "",
string mobile = "")
{
var parameters = new
{
pageIndex = pageIndex,
pageSize = pageSize,
courseID = courseID,
name = name,
mobile = mobile
};
return ApiService.Instance.Post(SERVICE_PATH, "SearchSignUps", parameters);
}
///// <summary>
///// 搜尋活動參加人員清單(非同步版本)
///// </summary>
//public static async Task<BaseResponse> SearchSignUpsAsync(
// int pageIndex,
// int pageSize,
// int courseID,
// string name = "",
// string mobile = "")
//{
// var parameters = new
// {
// pageIndex = pageIndex,
// pageSize = pageSize,
// courseID = courseID,
// name = name,
// mobile = mobile
// };
// return await ApiService.Instance.PostAsync(SERVICE_PATH, "SearchSignUps", parameters);
//}
/// <summary>
/// 上傳活動參加人員簽到簽退資料
/// 由 WinForm 客戶端上傳簽到簽退資料
/// </summary>
/// <param name="courseID">活動編號</param>
/// <param name="items">報名人員列表</param>
/// <returns>上傳結果</returns>
//public static BaseResponse UploadSignUps(int courseID, List<SignUpItem> items)
//{
// var parameters = new
// {
// courseID = courseID,
// items = items
// };
// return ApiService.Instance.Post(SERVICE_PATH, "UploadSignUps", parameters);
//}
/// <summary>
/// 上傳活動參加人員簽到簽退資料(非同步版本)
/// </summary>
//public static async Task<BaseResponse> UploadSignUpsAsync(int courseID, List<SignUpItem> items)
//{
// var parameters = new
// {
// courseID = courseID,
// items = items
// };
// return await ApiService.Instance.PostAsync(SERVICE_PATH, "UploadSignUps", parameters);
//}
/// <summary>
/// 掃碼簽到
/// </summary>
/// <param name="checkType">簽到類型</param>
/// <param name="qrCode">QR Code</param>
/// <param name="checkInTime">簽到時間</param>
/// <returns>簽到結果</returns>
//public static BaseResponse CheckIn(string checkType, string qrCode, DateTime checkInTime)
//{
// var parameters = new
// {
// checkType = checkType,
// qrCode = qrCode,
// checkInTime = checkInTime
// };
// return ApiService.Instance.Post(SERVICE_PATH, "CheckIn", parameters);
//}
/// <summary>
/// 掃碼簽到(非同步版本)
/// </summary>
//public static async Task<BaseResponse> CheckInAsync(string checkType, string qrCode, DateTime checkInTime)
//{
// var parameters = new
// {
// checkType = checkType,
// qrCode = qrCode,
// checkInTime = checkInTime
// };
// return await ApiService.Instance.PostAsync(SERVICE_PATH, "CheckIn", parameters);
//}
/// <summary>
/// 掃碼簽退
/// </summary>
/// <param name="checkType">簽退類型</param>
/// <param name="qrCode">QR Code</param>
/// <param name="checkOutTime">簽退時間</param>
/// <returns>簽退結果</returns>
//public static BaseResponse CheckOut(string checkType, string qrCode, DateTime checkOutTime)
//{
// var parameters = new
// {
// checkType = checkType,
// qrCode = qrCode,
// checkOutTime = checkOutTime
// };
// return ApiService.Instance.Post(SERVICE_PATH, "CheckOut", parameters);
//}
/// <summary>
/// 掃碼簽退(非同步版本)
/// </summary>
//public static async Task<BaseResponse> CheckOutAsync(string checkType, string qrCode, DateTime checkOutTime)
//{
// var parameters = new
// {
// checkType = checkType,
// qrCode = qrCode,
// checkOutTime = checkOutTime
// };
// return await ApiService.Instance.PostAsync(SERVICE_PATH, "CheckOut", parameters);
//}
/// <summary>
/// 現場簽到(未預先登記的參加人員)
/// </summary>
/// <param name="checkType">簽到類型</param>
/// <param name="courseID">活動編號</param>
/// <param name="name">姓名</param>
/// <param name="mobile">手機號</param>
/// <param name="checkInTime">簽到時間</param>
/// <returns>簽到結果</returns>
//public static BaseResponse OnSiteCheckIn(
// string checkType,
// int courseID,
// string name,
// string mobile,
// DateTime checkInTime)
//{
// var parameters = new
// {
// checkType = checkType,
// courseID = courseID,
// name = name,
// mobile = mobile,
// checkInTime = checkInTime
// };
// return ApiService.Instance.Post(SERVICE_PATH, "OnSiteCheckIn", parameters);
//}
/// <summary>
/// 現場簽到(非同步版本)
/// </summary>
//public static async Task<BaseResponse> OnSiteCheckInAsync(
// string checkType,
// int courseID,
// string name,
// string mobile,
// DateTime checkInTime)
//{
// var parameters = new
// {
// checkType = checkType,
// courseID = courseID,
// name = name,
// mobile = mobile,
// checkInTime = checkInTime
// };
// return await ApiService.Instance.PostAsync(SERVICE_PATH, "OnSiteCheckIn", parameters);
//}
/// <summary>
/// 現場簽退(未預先登記的參加人員)
/// </summary>
/// <param name="checkType">簽退類型</param>
/// <param name="courseID">活動編號</param>
/// <param name="name">姓名</param>
/// <param name="mobile">手機號</param>
/// <param name="checkOutTime">簽退時間</param>
/// <returns>簽退結果</returns>
//public static BaseResponse OnSiteCheckOut(
// string checkType,
// int courseID,
// string name,
// string mobile,
// DateTime checkOutTime)
//{
// var parameters = new
// {
// checkType = checkType,
// courseID = courseID,
// name = name,
// mobile = mobile,
// checkOutTime = checkOutTime
// };
// return ApiService.Instance.Post(SERVICE_PATH, "OnSiteCheckOut", parameters);
//}
/// <summary>
/// 現場簽退(非同步版本)
/// </summary>
//public static async Task<BaseResponse> OnSiteCheckOutAsync(
// string checkType,
// int courseID,
// string name,
// string mobile,
// DateTime checkOutTime)
//{
// var parameters = new
// {
// checkType = checkType,
// courseID = courseID,
// name = name,
// mobile = mobile,
// checkOutTime = checkOutTime
// };
// return await ApiService.Instance.PostAsync(SERVICE_PATH, "OnSiteCheckOut", parameters);
//}
/// <summary>
/// 批量上傳簽到簽退資料
/// 由 WinForm 客戶端上傳簽到簽退資料
/// </summary>
/// <param name="courseID">活動編號</param>
/// <param name="items">簽到簽退資料列表</param>
/// <returns>上傳結果</returns>
public static BaseResponse UploadCheckIns(int courseID, List<CheckInItem> items)
{
var parameters = new
{
courseID = courseID,
items = items
};
return ApiService.Instance.Post(SERVICE_PATH, "UploadCheckIns", parameters);
}
/// <summary>
/// 批量上傳簽到簽退資料(非同步版本)
/// </summary>
//public static async Task<BaseResponse> UploadCheckInsAsync(int courseID, List<CheckInItem> items)
//{
// var parameters = new
// {
// courseID = courseID,
// items = items
// };
// return await ApiService.Instance.PostAsync(SERVICE_PATH, "UploadCheckIns", parameters);
//}
}
}
+28
View File
@@ -0,0 +1,28 @@
namespace .Properties {
// 這個類別可以讓您處理設定類別上的特定事件:
// 在設定值變更之前引發 SettingChanging 事件。
// 在設定值變更之後引發 PropertyChanged 事件。
// 在載入設定值之後引發 SettingsLoaded 事件。
// 在儲存設定值之前引發 SettingsSaving 事件。
internal sealed partial class Settings {
public Settings() {
// // 若要加入用於儲存與變更設定的事件處理常式,請取消註解下列程式行:
//
// this.SettingChanging += this.SettingChangingEventHandler;
//
// this.SettingsSaving += this.SettingsSavingEventHandler;
//
}
private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) {
// 在此加入程式碼以處理 SettingChangingEvent 事件。
}
private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) {
// 在此加入程式碼以處理 SettingsSaving 事件。
}
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
+27
View File
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace
{
internal class Utils
{
/// <summary>
/// 手機號轉隱碼輸出(前4後3)
/// </summary>
/// <param name="mobile"></param>
/// <returns></returns>
public static string HideMobile(string mobile)
{
if (string.IsNullOrEmpty(mobile))
return string.Empty;
if (mobile.Length >= 7)
return mobile.Substring(0, 4) + "***" + mobile.Substring(mobile.Length - 3);
return "***";
}
}
}
+77
View File
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC 資訊清單選項
如果要變更 Windows 使用者帳戶控制層級,請將
requestedExecutionLevel 節點以下列其中之一取代。
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
指定 requestedExecutionLevel 項目會停用檔案及登錄虛擬化。
如果您的應用程式需要針對回溯相容性進行這項虛擬化,請移除這個
項目。
-->
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- 此應用程式已通過測試,並設計要搭配使用的
應用程式版本清單。請取消註解適當元素,
Windows 就會自動選取最相容的環境。 -->
<!-- Windows Vista -->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
<!-- Windows 7 -->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
<!-- Windows 8 -->
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
<!-- Windows 8.1 -->
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<!-- 表示應用程式為 DPI 感知,Windows 不會在 DPI 變高時自動
縮放。Windows Presentation Foundation (WPF) 應用程式會自動感知 DPI,因此不需要
加入。以 .NET Framework 4.6 為目標的 Windows Form 應用程式若加入這項設定,也
應該在其 app.config 中將 'EnableWindowsFormsHighDpiAutoResizing' 設定為 'true'。
將應用程式設定為感知長路徑。請參閱 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation-->
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
<!-- 啟用 Windows 通用控制項和對話方塊的佈景主題 (Windows XP 以後版本) -->
<!--
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
-->
</assembly>
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Data.Sqlite" version="6.0.36" targetFramework="net481" />
<package id="Microsoft.Data.Sqlite.Core" version="6.0.36" targetFramework="net481" />
<package id="Newtonsoft.Json" version="13.0.4" targetFramework="net481" />
<package id="SourceGear.sqlite3" version="3.50.4.2" targetFramework="net481" />
<package id="SQLitePCLRaw.bundle_e_sqlite3" version="3.0.2" targetFramework="net481" />
<package id="SQLitePCLRaw.config.e_sqlite3" version="3.0.2" targetFramework="net481" />
<package id="SQLitePCLRaw.core" version="3.0.2" targetFramework="net481" />
<package id="SQLitePCLRaw.provider.dynamic_cdecl" version="2.1.2" targetFramework="net481" />
<package id="SQLitePCLRaw.provider.e_sqlite3" version="3.0.2" targetFramework="net481" />
<package id="System.Buffers" version="4.6.1" targetFramework="net481" />
<package id="System.Memory" version="4.6.3" targetFramework="net481" />
<package id="System.Numerics.Vectors" version="4.6.1" targetFramework="net481" />
<package id="System.Runtime.CompilerServices.Unsafe" version="6.1.2" targetFramework="net481" />
</packages>
@@ -0,0 +1,285 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\報到系統\packages\SourceGear.sqlite3.3.50.4.2\build\net471\SourceGear.sqlite3.props" Condition="Exists('..\報到系統\packages\SourceGear.sqlite3.3.50.4.2\build\net471\SourceGear.sqlite3.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{8B1277B9-7958-4B49-B8E5-3D32A3A0311C}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>報到系統客戶端</RootNamespace>
<AssemblyName>報到系統客戶端</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<TargetFrameworkProfile />
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>x86</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>x86</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Data.Sqlite, Version=6.0.36.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\報到系統\packages\Microsoft.Data.Sqlite.Core.6.0.36\lib\netstandard2.0\Microsoft.Data.Sqlite.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\報到系統\packages\Newtonsoft.Json.13.0.4\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="SQLitePCLRaw.batteries_v2, Version=3.0.2.2801, Culture=neutral, PublicKeyToken=8226ea5df37bcae9, processorArchitecture=MSIL">
<HintPath>..\報到系統\packages\SQLitePCLRaw.config.e_sqlite3.3.0.2\lib\net471\SQLitePCLRaw.batteries_v2.dll</HintPath>
</Reference>
<Reference Include="SQLitePCLRaw.core, Version=3.0.2.2801, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL">
<HintPath>..\報到系統\packages\SQLitePCLRaw.core.3.0.2\lib\netstandard2.0\SQLitePCLRaw.core.dll</HintPath>
</Reference>
<Reference Include="SQLitePCLRaw.provider.dynamic_cdecl, Version=2.1.2.1721, Culture=neutral, PublicKeyToken=b68184102cba0b3b, processorArchitecture=MSIL">
<HintPath>..\報到系統\packages\SQLitePCLRaw.provider.dynamic_cdecl.2.1.2\lib\netstandard2.0\SQLitePCLRaw.provider.dynamic_cdecl.dll</HintPath>
</Reference>
<Reference Include="SQLitePCLRaw.provider.e_sqlite3, Version=3.0.2.2801, Culture=neutral, PublicKeyToken=9c301db686d0bd12, processorArchitecture=MSIL">
<HintPath>..\報到系統\packages\SQLitePCLRaw.provider.e_sqlite3.3.0.2\lib\net471\SQLitePCLRaw.provider.e_sqlite3.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\報到系統\packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.Configuration.Install" />
<Reference Include="System.Core" />
<Reference Include="System.EnterpriseServices" />
<Reference Include="System.Memory, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\報到系統\packages\System.Memory.4.6.3\lib\net462\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\報到系統\packages\System.Numerics.Vectors.4.6.1\lib\net462\System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\報到系統\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Transactions" />
<Reference Include="System.Web.Services" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="AgreementConfirmForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="AgreementConfirmForm.Designer.cs">
<DependentUpon>AgreementConfirmForm.cs</DependentUpon>
</Compile>
<Compile Include="CheckoutOnlyConfirmForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="CheckoutOnlyConfirmForm.Designer.cs">
<DependentUpon>CheckoutOnlyConfirmForm.cs</DependentUpon>
</Compile>
<Compile Include="ClientSession.cs" />
<Compile Include="ExistConfirmForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="ExistConfirmForm.Designer.cs">
<DependentUpon>ExistConfirmForm.cs</DependentUpon>
</Compile>
<Compile Include="Data\DatabaseService.cs" />
<Compile Include="Data\Models\LocalBPSNItem.cs" />
<Compile Include="Data\Models\LocalCheckInItem.cs" />
<Compile Include="Data\Models\LocalClassesModel.cs" />
<Compile Include="Data\Models\LocalSignUpModel.cs" />
<Compile Include="Data\Repositories\BPSNRepository.cs" />
<Compile Include="Data\Repositories\CheckInRepository.cs" />
<Compile Include="Data\Repositories\ClassesRepository.cs" />
<Compile Include="Data\Repositories\SignUpsRepository.cs" />
<Compile Include="LoginForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="LoginForm.Designer.cs">
<DependentUpon>LoginForm.cs</DependentUpon>
</Compile>
<Compile Include="MainForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="MainForm.Designer.cs">
<DependentUpon>MainForm.cs</DependentUpon>
</Compile>
<Compile Include="Models\BaseResponse.cs" />
<Compile Include="Models\BPSNModel.cs" />
<Compile Include="Models\CheckInModel.cs" />
<Compile Include="Models\ClassesModel.cs" />
<Compile Include="Models\SignUpModel.cs" />
<Compile Include="ProcessingForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="ProcessingForm.Designer.cs">
<DependentUpon>ProcessingForm.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\ApiService.cs" />
<Compile Include="Services\BPSNService.cs" />
<Compile Include="Services\ClassesService.cs" />
<Compile Include="Services\ConfigService.cs" />
<Compile Include="Services\LoginService.cs" />
<Compile Include="Services\NotificationSoundService.cs" />
<Compile Include="Services\SignUpService.cs" />
<Compile Include="Settings.cs" />
<Compile Include="Utils.cs" />
<Content Include="Sound\CheckinOK.wav">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Sound\CheckoutOK.wav">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Sound\ding.wav">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<EmbeddedResource Include="AgreementConfirmForm.resx">
<DependentUpon>AgreementConfirmForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="CheckoutOnlyConfirmForm.resx">
<DependentUpon>CheckoutOnlyConfirmForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="ExistConfirmForm.resx">
<DependentUpon>ExistConfirmForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="LoginForm.resx">
<DependentUpon>LoginForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="MainForm.resx">
<DependentUpon>MainForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="ProcessingForm.resx">
<DependentUpon>ProcessingForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile>
<Content Include=".editorconfig" />
<None Include="app.manifest" />
<None Include="CONFIG_PATH_GUIDE.md" />
<None Include="DOC\API服務.md" />
<None Include="DOC\Models模型層改造報告.md" />
<None Include="DOC\簽到簽退.md" />
<None Include="DOC\登入.md" />
<None Include="DOC\資料儲存服務.md" />
<None Include="DOC\資料存取層改造完成報告.md" />
<None Include="packages.config" />
<None Include="Properties\DataSources\報到系統客戶端.Data.Models.LocalSignUpItem.datasource" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<Content Include="ConfigService_Usage_Example.cs.txt" />
</ItemGroup>
<ItemGroup>
<WCFMetadata Include="Connected Services\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\報到系統客戶端安裝自訂動作\報到系統客戶端安裝自訂動作.csproj">
<Project>{870587F6-4E78-447E-9AFE-D9A44DE908F5}</Project>
<Name>報到系統客戶端安裝自訂動作</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.8">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.8 %28x86 和 x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>此專案參考這部電腦上所缺少的 NuGet 套件。請啟用 NuGet 套件還原,以下載該套件。如需詳細資訊,請參閱 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的檔案是 {0}。</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\報到系統\packages\SourceGear.sqlite3.3.50.4.2\build\net471\SourceGear.sqlite3.props')" Text="$([System.String]::Format('$(ErrorText)', '..\報到系統\packages\SourceGear.sqlite3.3.50.4.2\build\net471\SourceGear.sqlite3.props'))" />
<Error Condition="!Exists('..\報到系統\packages\SourceGear.sqlite3.3.50.4.2\build\net471\SourceGear.sqlite3.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\報到系統\packages\SourceGear.sqlite3.3.50.4.2\build\net471\SourceGear.sqlite3.targets'))" />
</Target>
<Import Project="..\報到系統\packages\SourceGear.sqlite3.3.50.4.2\build\net471\SourceGear.sqlite3.targets" Condition="Exists('..\報到系統\packages\SourceGear.sqlite3.3.50.4.2\build\net471\SourceGear.sqlite3.targets')" />
</Project>