修复当前卡不对的问题

This commit is contained in:
2026-05-06 10:19:21 +08:00
parent 21b73a750d
commit 001f8caf35
3 changed files with 34 additions and 8 deletions

View File

@@ -658,6 +658,16 @@ components:
current_package: current_package:
description: 当前套餐名称(无套餐时为空) description: 当前套餐名称(无套餐时为空)
type: string type: string
current_package_activated_at:
description: 当前主套餐开始时间
format: date-time
nullable: true
type: string
current_package_expires_at:
description: 当前主套餐过期时间
format: date-time
nullable: true
type: string
current_package_usage_id: current_package_usage_id:
description: 当前主套餐的套餐使用记录ID无主套餐时为 null description: 当前主套餐的套餐使用记录ID无主套餐时为 null
minimum: 0 minimum: 0
@@ -1179,6 +1189,16 @@ components:
current_package: current_package:
description: 当前套餐名称(无套餐时为空) description: 当前套餐名称(无套餐时为空)
type: string type: string
current_package_activated_at:
description: 当前主套餐开始时间
format: date-time
nullable: true
type: string
current_package_expires_at:
description: 当前主套餐过期时间
format: date-time
nullable: true
type: string
current_package_usage_id: current_package_usage_id:
description: 当前主套餐的套餐使用记录ID无主套餐时为 null description: 当前主套餐的套餐使用记录ID无主套餐时为 null
minimum: 0 minimum: 0
@@ -13591,6 +13611,7 @@ paths:
- 文件格式:仅支持 .xlsx (Excel 2007+) - 文件格式:仅支持 .xlsx (Excel 2007+)
- 必须包含列(首行为表头): - 必须包含列(首行为表头):
- `virtual_no`: 设备虚拟号(**必填**,全局唯一;**为空的行记为失败,不再静默跳过** - `virtual_no`: 设备虚拟号(**必填**,全局唯一;**为空的行记为失败,不再静默跳过**
- `sn`: 设备 SN
- `device_name`: 设备名称 - `device_name`: 设备名称
- `device_model`: 设备型号 - `device_model`: 设备型号
- `device_type`: 设备类型 - `device_type`: 设备类型
@@ -13599,6 +13620,7 @@ paths:
- `max_sim_slots`: 最大插槽数默认4范围1-4 - `max_sim_slots`: 最大插槽数默认4范围1-4
- `iccid_1` ~ `iccid_4`: 固定槽位的卡 ICCID`iccid_1`=槽1`iccid_2`=槽2`iccid_3`=槽3`iccid_4`=槽4卡必须已存在且未绑定 - `iccid_1` ~ `iccid_4`: 固定槽位的卡 ICCID`iccid_1`=槽1`iccid_2`=槽2`iccid_3`=槽3`iccid_4`=槽4卡必须已存在且未绑定
- 槽位规则:中间留空不会触发前移补位;如果 `max_sim_slots=2`,则 `iccid_3`、`iccid_4` 必须留空,否则该行失败 - 槽位规则:中间留空不会触发前移补位;如果 `max_sim_slots=2`,则 `iccid_3`、`iccid_4` 必须留空,否则该行失败
- 固定列顺序:`virtual_no`、`sn`、`device_name`、`device_model`、`device_type`、`imei`、`manufacturer`、`max_sim_slots`、`iccid_1`、`iccid_2`、`iccid_3`、`iccid_4`
- 列格式:设置为文本格式(避免长数字被转为科学记数法) - 列格式:设置为文本格式(避免长数字被转为科学记数法)
requestBody: requestBody:
content: content:

View File

@@ -114,16 +114,17 @@ function validateFile(file) {
**文件名**: `device_import_template.xlsx` **文件名**: `device_import_template.xlsx`
**格式**: **格式**:
| virtual_no | device_name | device_model | device_type | imei | manufacturer | max_sim_slots | iccid_1 | iccid_2 | iccid_3 | iccid_4 | | virtual_no | sn | device_name | device_model | device_type | imei | manufacturer | max_sim_slots | iccid_1 | iccid_2 | iccid_3 | iccid_4 |
|------------|-------------|--------------|-------------|------|--------------|---------------|---------|---------|---------|---------| |------------|----|-------------|--------------|-------------|------|--------------|---------------|---------|---------|---------|---------|
| DEV-001 | GPS追踪器A | GT06N | GPS Tracker | 123456789012345 | Concox | 4 | 89860012345678901234 | 89860012345678901235 | | | | DEV-001 | SN-001 | GPS追踪器A | GT06N | GPS Tracker | 123456789012345 | Concox | 4 | 89860012345678901234 | 89860012345678901235 | | |
| DEV-002 | GPS追踪器B | GT06N | GPS Tracker | 123456789012346 | Concox | 2 | | 89860012345678901236 | 89860012345678901237 | | | DEV-002 | SN-002 | GPS追踪器B | GT06N | GPS Tracker | 123456789012346 | Concox | 3 | | 89860012345678901236 | 89860012345678901237 | |
**字段说明**: **字段说明**:
| 列名 | 是否必填 | 说明 | | 列名 | 是否必填 | 说明 |
|------|---------|------| |------|---------|------|
| virtual_no | **是** | 设备虚拟号,全局唯一;**留空不再跳过,将记录为失败行** | | virtual_no | **是** | 设备虚拟号,全局唯一;**留空不再跳过,将记录为失败行** |
| sn | 否 | 设备 SN建议填写厂商序列号 |
| device_name | 否 | 设备名称 | | device_name | 否 | 设备名称 |
| device_model | 否 | 设备型号 | | device_model | 否 | 设备型号 |
| device_type | 否 | 设备类型 | | device_type | 否 | 设备类型 |
@@ -135,7 +136,7 @@ function validateFile(file) {
**要点**: **要点**:
- 所有列都必须设置为**文本格式** - 所有列都必须设置为**文本格式**
- `virtual_no` 为必填项,留空将记录为失败行(而非静默跳过) - `virtual_no` 为必填项,留空将记录为失败行(而非静默跳过)
- 设备导入按**固定列顺序**读取:`virtual_no``device_name``device_model``device_type``imei``manufacturer``max_sim_slots``iccid_1``iccid_2``iccid_3``iccid_4` - 设备导入按**固定列顺序**读取:`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 必须已存在于系统中
- `iccid_1 ~ iccid_4` 表示**固定槽位**而非“按非空顺序绑定” - `iccid_1 ~ iccid_4` 表示**固定槽位**而非“按非空顺序绑定”
例如只填写 `iccid_2``iccid_3`导入后仍应绑定到槽2和槽3而不是槽1和槽2 例如只填写 `iccid_2``iccid_3`导入后仍应绑定到槽2和槽3而不是槽1和槽2

View File

@@ -136,12 +136,14 @@ func (h *ClientDeviceHandler) GetDeviceCards(c *fiber.Ctx) error {
return response.Success(c, &dto.DeviceCardListResponse{Cards: []dto.DeviceCardItem{}}) return response.Success(c, &dto.DeviceCardListResponse{Cards: []dto.DeviceCardItem{}})
} }
// 收集卡 ID 并记录插槽位置映射 // 收集卡 ID并记录插槽位置与当前在用标识
cardIDs := make([]uint, 0, len(bindings)) cardIDs := make([]uint, 0, len(bindings))
slotMap := make(map[uint]int, len(bindings)) slotMap := make(map[uint]int, len(bindings))
isCurrentMap := make(map[uint]bool, len(bindings))
for _, b := range bindings { for _, b := range bindings {
cardIDs = append(cardIDs, b.IotCardID) cardIDs = append(cardIDs, b.IotCardID)
slotMap[b.IotCardID] = b.SlotPosition slotMap[b.IotCardID] = b.SlotPosition
isCurrentMap[b.IotCardID] = b.IsCurrent
} }
// 批量查询卡详情 // 批量查询卡详情
@@ -153,7 +155,7 @@ func (h *ClientDeviceHandler) GetDeviceCards(c *fiber.Ctx) error {
return errors.New(errors.CodeInternalError) return errors.New(errors.CodeInternalError)
} }
// 组装响应,slot_position == 1 视为当前激活卡 // 组装响应,当前激活卡以绑定表 is_current 为准
items := make([]dto.DeviceCardItem, 0, len(cards)) items := make([]dto.DeviceCardItem, 0, len(cards))
for _, card := range cards { for _, card := range cards {
slot := slotMap[card.ID] slot := slotMap[card.ID]
@@ -166,7 +168,8 @@ func (h *ClientDeviceHandler) GetDeviceCards(c *fiber.Ctx) error {
NetworkStatus: networkStatusText(card.NetworkStatus), NetworkStatus: networkStatusText(card.NetworkStatus),
RealNameStatus: card.RealNameStatus, RealNameStatus: card.RealNameStatus,
SlotPosition: slot, SlotPosition: slot,
IsActive: slot == 1, IsActive: isCurrentMap[card.ID],
RealnamePolicy: card.RealnamePolicy,
}) })
} }