# SignUpImport - 新增「重複檢查」功能 ## 功能概述 匯入參加人員時,若「姓名+手機號」的組合在同一活動中已經存在,則自動跳過該記錄,避免重複匯入。 ## 實現細節 ### 1. 後端修改 (SignUpService.asmx.cs) #### ConfirmImport 方法增強 ```csharp public BaseResponse ConfirmImport(int courseID, List items) { // 1. 查詢現有參加人員(同一活動) SignUpList existingSignups = dac.Select( noInputReturnAll: true, CourseID: courseID, ... ); // 2. 建立重複檢查集合 HashSet existingCombinations = new HashSet(); foreach (var signup in existingSignups) { string key = CreateCompositeKey(signup.Name, signup.Mobile); existingCombinations.Add(key); } // 3. 逐筆檢查並匯入 foreach (var item in items) { string compositeKey = CreateCompositeKey(item.Name, item.Mobile); if (existingCombinations.Contains(compositeKey)) { // 跳過重複記錄 skippedCount++; skippedRecords.Add($"{item.Name} ({item.Mobile})"); continue; } // 插入新記錄 dac.InsertOne(item); existingCombinations.Add(compositeKey); // 加入集合,避免批次內重複 } } ``` #### 複合鍵生成 ```csharp private string CreateCompositeKey(string name, string mobile) { return (name ?? "").Trim() + "|" + (mobile ?? "").Trim(); } ``` ### 2. 前端修改 (SignUpImport.aspx) #### 匯入結果顯示 ```html 成功:{{ importResult.details.SuccessCount }} 筆 跳過:{{ importResult.details.SkippedCount }} 筆 失敗:{{ importResult.details.ErrorCount }} 筆
❌ 跳過的重複記錄:
{{ record }}
... 還有 {{ importResult.details.SkippedRecords.length - 10 }} 筆
``` ### 3. 樣式增強 (SignUpImport.css) #### 跳過記錄樣式 ```css .skipped-records { margin-top: var(--spacing-md); padding-top: var(--spacing-md); border-top: 1px solid #ddd; } .skipped-title { color: #f57c00; font-weight: 600; margin-bottom: 8px; font-size: 14px; } .skipped-item { color: #f57c00; font-size: 13px; margin-bottom: 4px; padding-left: 20px; position: relative; } .skipped-item::before { content: '⊘ '; position: absolute; left: 0; } ``` ## 工作流程 ``` 上傳 Excel 檔案 ↓ 後端驗證(FileUploadHandler.ashx) ├─ 檢查欄位格式 ├─ 驗證必填欄位 └─ 返回預覽 (✓/✗ 狀態) ↓ 前端顯示預覽(可編輯) ├─ 顯示有效列(✓) ├─ 顯示錯誤列(✗)和錯誤訊息 └─ 統計有效筆數 ↓ 使用者確認匯入 ↓ 後端執行匯入(ConfirmImport) ├─ 查詢現有參加人員 (同一活動) ├─ 建立重複檢查集合 (Name|Mobile) ├─ 逐筆檢查: │ ├─ 若重複 → 跳過並記錄 │ └─ 若不重複 → 插入並加入集合 └─ 返回結果統計 ↓ 前端顯示結果 ├─ 成功:X 筆 ├─ 跳過:Y 筆(列出跳過的記錄) └─ 失敗:Z 筆(列出失敗原因) ``` ## 回傳資料結構 ### ConfirmImport Response ```javascript { Success: true, Message: "成功匯入 10 筆資料,跳過 3 筆重複資料,失敗 0 筆", Data: { SuccessCount: 10, // 成功新增筆數 SkippedCount: 3, // 跳過(重複)筆數 ErrorCount: 0, // 失敗筆數 Errors: [], // 失敗詳情 SkippedRecords: [ // 跳過記錄詳情 "王小明 (0912345678)", "李美麗 (0923456789)", "張大衛 (0934567890)" ] } } ``` ## 重複檢查邏輯 ### 檢查範圍 - **範圍**:同一活動內 - **檢查欄位**:姓名 (Name) + 手機號 (Mobile) - **大小寫敏感**:否(轉換為小寫比較) - **空白敏感**:否(.Trim() 處理) ### 複合鍵格式 ``` key = "王小明|0912345678" ``` ### 重複判定規則 ``` if (Name.Trim() == "王小明" AND Mobile.Trim() == "0912345678") { // 認為重複,跳過 } ``` ## 使用者體驗 ### 成功案例 ``` 成功匯入 20 筆資料,跳過 5 筆重複資料 跳過的重複記錄: ⊘ 王小明 (0912345678) ⊘ 李美麗 (0923456789) ⊘ 張大衛 (0934567890) ... 還有 2 筆 ``` ### 視覺反饋 - **成功** → 綠色背景 (✓) - **跳過** → 橙色文字 (⊘) - **失敗** → 紅色文字 (✗) ## 相關檔案修改 | 檔案 | 修改內容 | |-----|--------| | `Services/SignUpService.asmx.cs` | ConfirmImport 方法新增重複檢查邏輯 + CreateCompositeKey 方法 | | `Forms/SignUpImport.aspx` | 匯入結果顯示新增跳過統計和跳過記錄列表 | | `Content/SignUpImport.css` | 新增 .skipped-records 和相關樣式 | | `Scripts/SignUpImport.js` | 無修改(後端自動處理)| ## 性能考量 ### 算法複雜度 - **時間複雜度**:O(n) 其中 n = 現有記錄數 + 匯入記錄數 - **空間複雜度**:O(n) 用於 HashSet ### 優化點 1. 使用 `HashSet` 而非 List,O(1) 查詢而非 O(n) 2. 同時檢查「現有記錄」和「同批次內記錄」,避免同批次重複 ## 測試清單 - [ ] 匯入無重複資料 → 全部成功 - [ ] 匯入有部分重複資料 → 成功 + 跳過 - [ ] 匯入全部重複資料 → 全部跳過 - [ ] 同批次內有重複 → 跳過後續重複 - [ ] 驗證錯誤 + 重複混合 → 正確統計每種情況 - [ ] 驗證跳過記錄列表正確顯示 - [ ] 驗證成功/跳過/失敗的計數準確