6.2 KiB
6.2 KiB
SignUpImport - 新增「重複檢查」功能
功能概述
匯入參加人員時,若「姓名+手機號」的組合在同一活動中已經存在,則自動跳過該記錄,避免重複匯入。
實現細節
1. 後端修改 (SignUpService.asmx.cs)
ConfirmImport 方法增強
public BaseResponse ConfirmImport(int courseID, List<SignUpItem> items)
{
// 1. 查詢現有參加人員(同一活動)
SignUpList existingSignups = dac.Select(
noInputReturnAll: true,
CourseID: courseID,
...
);
// 2. 建立重複檢查集合
HashSet<string> existingCombinations = new HashSet<string>();
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); // 加入集合,避免批次內重複
}
}
複合鍵生成
private string CreateCompositeKey(string name, string mobile)
{
return (name ?? "").Trim() + "|" + (mobile ?? "").Trim();
}
2. 前端修改 (SignUpImport.aspx)
匯入結果顯示
<!-- 顯示成功、跳過、失敗的統計 -->
<span>成功:{{ importResult.details.SuccessCount }} 筆</span>
<span v-if="importResult.details.SkippedCount > 0">跳過:{{ importResult.details.SkippedCount }} 筆</span>
<span v-if="importResult.details.ErrorCount > 0">失敗:{{ importResult.details.ErrorCount }} 筆</span>
<!-- 跳過記錄詳情 -->
<div class="skipped-records" v-if="importResult.details.SkippedRecords && importResult.details.SkippedRecords.length > 0">
<div class="skipped-title">❌ 跳過的重複記錄:</div>
<div class="skipped-list">
<div class="skipped-item" v-for="record in importResult.details.SkippedRecords.slice(0, 10)">
{{ record }}
</div>
<div v-if="importResult.details.SkippedRecords.length > 10">
... 還有 {{ importResult.details.SkippedRecords.length - 10 }} 筆
</div>
</div>
</div>
3. 樣式增強 (SignUpImport.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
{
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
優化點
- 使用
HashSet<T>而非 List,O(1) 查詢而非 O(n) - 同時檢查「現有記錄」和「同批次內記錄」,避免同批次重複
測試清單
- 匯入無重複資料 → 全部成功
- 匯入有部分重複資料 → 成功 + 跳過
- 匯入全部重複資料 → 全部跳過
- 同批次內有重複 → 跳過後續重複
- 驗證錯誤 + 重複混合 → 正確統計每種情況
- 驗證跳過記錄列表正確顯示
- 驗證成功/跳過/失敗的計數準確