230 lines
6.2 KiB
Markdown
230 lines
6.2 KiB
Markdown
# SignUpImport - 新增「重複檢查」功能
|
||
|
||
## 功能概述
|
||
匯入參加人員時,若「姓名+手機號」的組合在同一活動中已經存在,則自動跳過該記錄,避免重複匯入。
|
||
|
||
## 實現細節
|
||
|
||
### 1. 後端修改 (SignUpService.asmx.cs)
|
||
|
||
#### ConfirmImport 方法增強
|
||
```csharp
|
||
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); // 加入集合,避免批次內重複
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 複合鍵生成
|
||
```csharp
|
||
private string CreateCompositeKey(string name, string mobile)
|
||
{
|
||
return (name ?? "").Trim() + "|" + (mobile ?? "").Trim();
|
||
}
|
||
```
|
||
|
||
### 2. 前端修改 (SignUpImport.aspx)
|
||
|
||
#### 匯入結果顯示
|
||
```html
|
||
<!-- 顯示成功、跳過、失敗的統計 -->
|
||
<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)
|
||
|
||
#### 跳過記錄樣式
|
||
```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<T>` 而非 List,O(1) 查詢而非 O(n)
|
||
2. 同時檢查「現有記錄」和「同批次內記錄」,避免同批次重複
|
||
|
||
## 測試清單
|
||
- [ ] 匯入無重複資料 → 全部成功
|
||
- [ ] 匯入有部分重複資料 → 成功 + 跳過
|
||
- [ ] 匯入全部重複資料 → 全部跳過
|
||
- [ ] 同批次內有重複 → 跳過後續重複
|
||
- [ ] 驗證錯誤 + 重複混合 → 正確統計每種情況
|
||
- [ ] 驗證跳過記錄列表正確顯示
|
||
- [ ] 驗證成功/跳過/失敗的計數準確
|