# Excel导入功能 - 前端接入指南

## 变更说明

导入功能已从CSV格式升级为Excel格式(.xlsx),解决长数字(如20位ICCID)被Excel自动转为科学记数法导致数据损坏的问题。

## 关键变更

### 1. 文件格式

| 项目 | 旧版本(CSV) | 新版本(Excel) |
|-----|------------|--------------|
| 文件扩展名 | `.csv` | `.xlsx` |
| MIME类型 | `text/csv` | `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` |
| 文件选择器accept | `*` 或 `.csv` | `.xlsx` |

### 2. 上传示例代码

**ICCID导入**:
```javascript
// 1. 获取预签名URL
const response = await api.post('/api/admin/storage/upload-url', {
  file_name: 'cards.xlsx',  // 修改扩展名
  content_type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',  // 修改MIME类型
  purpose: 'iot_import'
});

const { upload_url, file_key } = response.data;

// 2. 上传Excel文件到对象存储
await fetch(upload_url, {
  method: 'PUT',
  headers: { 
    'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'  // 修改MIME类型
  },
  body: file  // File对象来自<input type="file" accept=".xlsx">
});

// 3. 提交导入任务
await api.post('/api/admin/iot-cards/import', {
  carrier_id: 1,
  batch_no: 'BATCH-2025-01',
  file_key: file_key
});
```

**设备导入**: 流程相同,只需调用 `/api/admin/devices/import` 接口。

### 3. 文件选择器组件

**修改前**:
```html
<input type="file" accept="*" />
<!-- 或 -->
<input type="file" accept=".csv" />
```

**修改后**:
```html
<input type="file" accept=".xlsx" />
```

### 4. 文件验证

```javascript
function validateFile(file) {
  // 检查扩展名
  if (!file.name.toLowerCase().endsWith('.xlsx')) {
    throw new Error('仅支持上传Excel文件(.xlsx格式)');
  }
  
  // 检查MIME类型(可选,部分浏览器可能不准确)
  const validTypes = [
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    'application/octet-stream'  // 部分浏览器可能返回此类型
  ];
  
  if (!validTypes.includes(file.type)) {
    console.warn('文件MIME类型不匹配,但根据扩展名判断为有效文件');
  }
  
  return true;
}
```

## Excel模板文件

### ICCID导入模板

**文件名**: `iccid_import_template.xlsx`

**格式**:
| ICCID | MSISDN | virtual_no |
|-------|--------|------------|
| 89860012345678901234 | 13800000001 | VNO-00001 |
| 89860012345678901235 | 13800000002 | VNO-00002 |

**字段说明**:

| 列名 | 是否必填 | 说明 |
|------|---------|------|
| ICCID | **是** | 卡唯一标识，电信19位/其他20位 |
| MSISDN | **是** | 接入号 |
| virtual_no | **是** | 虚拟号，全局唯一；留空将导致该行导入失败 |

**要点**:
- 必须包含表头行(ICCID, MSISDN, virtual_no)
- 所有列必须设置为**文本格式**(重要!)
- Excel中设置文本格式: 选中列 → 右键 → 设置单元格格式 → 文本
- `virtual_no` 为必填列，**缺少该列将导致整批导入失败**

### 设备导入模板

**文件名**: `device_import_template.xlsx`

**格式**:
| virtual_no | sn | device_name | device_model | device_type | imei | manufacturer | max_sim_slots | iccid_1 | iccid_2 | iccid_3 | iccid_4 |
|------------|----|-------------|--------------|-------------|------|--------------|---------------|---------|---------|---------|---------|
| DEV-001 | SN-001 | GPS追踪器A | GT06N | GPS Tracker | 123456789012345 | Concox | 4 | 89860012345678901234 | 89860012345678901235 | | |
| DEV-002 | SN-002 | GPS追踪器B | GT06N | GPS Tracker | 123456789012346 | Concox | 3 | | 89860012345678901236 | 89860012345678901237 | |

**字段说明**:

| 列名 | 是否必填 | 说明 |
|------|---------|------|
| virtual_no | **是** | 设备虚拟号，全局唯一；**留空不再跳过，将记录为失败行** |
| sn | 否 | 设备 SN，建议填写厂商序列号 |
| device_name | 否 | 设备名称 |
| device_model | 否 | 设备型号 |
| device_type | 否 | 设备类型 |
| imei | 否 | 设备IMEI |
| manufacturer | 否 | 制造商 |
| max_sim_slots | 否 | 最大SIM卡槽数，默认4，范围1-4 |
| iccid_1 ~ iccid_4 | 否 | 固定槽位的 IoT 卡 ICCID：`iccid_1`=槽1，`iccid_2`=槽2，`iccid_3`=槽3，`iccid_4`=槽4；中间留空不会触发“前移补位” |

**要点**:
- 所有列都必须设置为**文本格式**
- `virtual_no` 为必填项，留空将记录为失败行（而非静默跳过）
- 设备导入按**固定列顺序**读取：`virtual_no`、`sn`、`device_name`、`device_model`、`device_type`、`imei`、`manufacturer`、`max_sim_slots`、`iccid_1`、`iccid_2`、`iccid_3`、`iccid_4`
- `iccid_1 ~ iccid_4` 为可选项，填写时对应的 ICCID 必须已存在于系统中
- `iccid_1 ~ iccid_4` 表示**固定槽位**而非“按非空顺序绑定”
  例如只填写 `iccid_2` 和 `iccid_3` 时，导入后仍应绑定到槽2和槽3，而不是槽1和槽2
- 如果 `max_sim_slots=2`，则 `iccid_3`、`iccid_4` 必须留空；否则该行应导入失败

## 模板下载功能实现

```javascript
// 方案1: 后端提供静态文件下载
<a href="/api/admin/storage/templates/iccid_import_template.xlsx" download>
  下载ICCID导入模板
</a>

// 方案2: 前端本地存放模板文件
<a href="/assets/templates/iccid_import_template.xlsx" download>
  下载ICCID导入模板
</a>
```

建议使用方案2(前端本地存放),减轻后端负担。

## 错误处理

### 服务端错误示例

```json
{
  "code": 1,
  "msg": "不支持的文件格式 .csv,请上传Excel文件(.xlsx)",
  "timestamp": "2025-01-31T13:00:00Z"
}
```

### 前端错误提示

```javascript
try {
  await uploadAndImport(file);
} catch (error) {
  if (error.response?.data?.msg) {
    // 显示服务端返回的错误消息
    showError(error.response.data.msg);
  } else {
    showError('上传失败,请重试');
  }
}
```

## 迁移检查清单

- [ ] 修改文件选择器accept属性为 `.xlsx`
- [ ] 更新上传时的MIME类型为Excel格式
- [ ] 添加前端文件格式验证(扩展名检查)
- [ ] 准备Excel模板文件并放置到前端资源目录
- [ ] 添加"下载模板"按钮/链接
- [ ] 更新相关提示文案(CSV → Excel)
- [ ] 测试完整的上传流程
- [ ] 验证错误场景(上传CSV文件时的提示)

## 注意事项

1. **向后兼容**: 本次变更不向后兼容,旧的CSV文件无法使用,前端需同步更新
2. **用户通知**: 建议在界面上添加醒目提示,告知用户格式变更
3. **模板文件**: 模板文件中的ICCID列**必须**设置为文本格式,否则长数字会被Excel自动转为科学记数法
4. **文件大小**: Excel文件比CSV大3-5倍,但对1万行数据影响不大(约3-5MB)

## 常见问题

**Q: 为什么要从CSV改为Excel?**  
A: Excel编辑CSV时会将超过15位的长数字(如20位ICCID)自动转为科学记数法,导致数据损坏。使用Excel格式并设置为文本格式可彻底解决此问题。

**Q: 用户已经准备好的CSV文件怎么办?**  
A: 用户可以在Excel中打开CSV,将ICCID/MSISDN列设置为文本格式,然后另存为.xlsx格式即可。

**Q: 是否支持.xls(旧版Excel)?**  
A: 不支持。仅支持.xlsx (Excel 2007+),建议在文档中明确说明。

## 联系方式

如有问题,请联系后端开发团队。
