diff --git a/client/src/types/api.ts b/client/src/types/api.ts
index 2e9c14a3..359edfba 100644
--- a/client/src/types/api.ts
+++ b/client/src/types/api.ts
@@ -264,12 +264,13 @@ export interface OilCostComparisonRequestDto {
}
export enum CostType {
- Charging = 1,
- Maintenance = 2,
- Insurance = 3,
- Parking = 4,
- Repair = 5,
- Other = 6
+ Charging = 0,
+ Maintenance = 1,
+ Insurance = 2,
+ Other = 3,
+ Toll = 4,
+ Parking = 5,
+ Repair = 6
}
export enum GasolineGrade {
diff --git a/client/src/views/electric-vehicle/costs/index.vue b/client/src/views/electric-vehicle/costs/index.vue
index c6ff65a9..07676c28 100644
--- a/client/src/views/electric-vehicle/costs/index.vue
+++ b/client/src/views/electric-vehicle/costs/index.vue
@@ -118,11 +118,12 @@
placeholder="请选择类型"
style="width: 100%"
>
-
-
-
-
-
+
+
+
+
+
+
@@ -219,12 +220,13 @@ const formRules: FormRules = {
const getCostTypeName = (type: number) => {
const map = {
- 1: "充电",
- 2: "保养",
- 3: "保险",
- 4: "停车",
- 5: "维修",
- 6: "其他"
+ 0: "充电",
+ 1: "保养",
+ 2: "保险",
+ 3: "其他",
+ 4: "过路费",
+ 5: "停车",
+ 6: "维修"
};
return map[type] || "未知";
};
@@ -286,7 +288,7 @@ const handleCreate = () => {
costDate: new Date().toISOString().split("T")[0],
amount: 0,
isBelongToSelf: true,
- costType: 2,
+ costType: 1,
vehicleId: vehicles.value.length === 1 ? vehicles.value[0].id : undefined,
remark: ""
});
diff --git a/sql/18-migrate-electric-vehicle-cost-type.sql b/sql/18-migrate-electric-vehicle-cost-type.sql
new file mode 100644
index 00000000..bbd61f22
--- /dev/null
+++ b/sql/18-migrate-electric-vehicle-cost-type.sql
@@ -0,0 +1,77 @@
+-- ============================================================
+-- 迁移脚本: 18-migrate-electric-vehicle-cost-type.sql
+-- 描述: 统一 AppElectricVehicleCost 表的 CostType 枚举值为 0-based
+-- 日期: 2026-04-28
+-- ============================================================
+--
+-- 背景说明:
+-- 旧前端 CostType 使用 1-based 枚举:
+-- 1=充电, 2=保养, 3=保险, 4=停车, 5=维修, 6=其他
+--
+-- 后端原来使用 0-based 枚举:
+-- 0=充电, 1=保养, 2=保险, 3=其他
+--
+-- 新的统一 0-based 枚举:
+-- 0=充电, 1=保养, 2=保险, 3=其他, 4=过路费, 5=停车, 6=维修
+--
+-- 数据库中可能存在的情况:
+-- 1. 如果后端直接存储了旧前端传来的 1-based 值,则数据库中有 1-6
+-- 2. 如果后端使用自己的 0-based 枚举,则数据库中有 0-3
+--
+-- 迁移策略(安全、幂等):
+-- - 对于旧前端 1-based 值(4→停车, 5→维修, 6→其他),映射到新的 0-based 值
+-- - 对于后端 0-based 值(0-3),保持不变
+-- - 使用 CASE WHEN 确保只迁移需要迁移的值
+-- ============================================================
+
+-- 先备份(如果还没备份的话,建议在执行前手动备份 DFApp.db)
+
+-- 步骤1: 查看当前数据分布(仅用于调试,注释掉以避免影响自动化执行)
+-- SELECT CostType, COUNT(*) as cnt FROM AppElectricVehicleCost GROUP BY CostType ORDER BY CostType;
+
+-- 步骤2: 如果数据库中有旧前端 1-based 的值,需要迁移
+-- 注意:必须先处理值 4/5/6,再处理值 1,避免冲突
+-- 使用临时值 -1/-2/-3 避免迁移过程中的主键冲突
+
+-- 旧 4 (停车) → 新 5 (停车)
+UPDATE AppElectricVehicleCost
+SET CostType = -1
+WHERE CostType = 4
+ AND EXISTS (SELECT 1 FROM AppElectricVehicleCost WHERE CostType = 4);
+
+-- 旧 5 (维修) → 新 6 (维修)
+UPDATE AppElectricVehicleCost
+SET CostType = -2
+WHERE CostType = 5
+ AND EXISTS (SELECT 1 FROM AppElectricVehicleCost WHERE CostType = 5);
+
+-- 旧 6 (其他) → 新 3 (其他)
+UPDATE AppElectricVehicleCost
+SET CostType = -3
+WHERE CostType = 6
+ AND EXISTS (SELECT 1 FROM AppElectricVehicleCost WHERE CostType = 6);
+
+-- 旧 1 (充电, 前端1-based) → 新 0 (充电)
+-- 只有当数据库中同时存在 CostType=0 和 CostType=1 时,
+-- 说明数据库混合了两种编码方式,此时 CostType=1 是旧前端的充电记录
+-- 如果数据库中只有 CostType=1 没有 CostType=0,说明后端直接存了前端值,1=充电
+-- 如果数据库中只有 CostType=0 没有 CostType=1,说明后端用了自己的枚举,无需迁移
+UPDATE AppElectricVehicleCost
+SET CostType = -4
+WHERE CostType = 1
+ AND NOT EXISTS (
+ -- 排除:如果 CostType=1 对应的是后端枚举的"保养"(即后端自己创建的记录)
+ -- 判断依据:如果数据库中存在 CostType=0(后端枚举的充电),说明后端使用了自己的枚举
+ -- 此时 CostType=1 是后端的"保养",不应迁移
+ SELECT 1 FROM AppElectricVehicleCost WHERE CostType = 0
+ );
+
+-- 步骤3: 将临时值更新为最终目标值
+UPDATE AppElectricVehicleCost SET CostType = 5 WHERE CostType = -1;
+UPDATE AppElectricVehicleCost SET CostType = 6 WHERE CostType = -2;
+UPDATE AppElectricVehicleCost SET CostType = 3 WHERE CostType = -3;
+UPDATE AppElectricVehicleCost SET CostType = 0 WHERE CostType = -4;
+
+-- 步骤4: 验证迁移结果
+-- SELECT CostType, COUNT(*) as cnt FROM AppElectricVehicleCost GROUP BY CostType ORDER BY CostType;
+-- 预期结果:CostType 值应全部在 0-6 范围内,不存在负数或超出范围的值
diff --git a/src/DFApp.Web/Background/LotteryResultJob.cs b/src/DFApp.Web/Background/LotteryResultJob.cs
index bd0a31f8..97c209e0 100644
--- a/src/DFApp.Web/Background/LotteryResultJob.cs
+++ b/src/DFApp.Web/Background/LotteryResultJob.cs
@@ -172,7 +172,8 @@ private async Task ProcessResultsIndividually(List
}
LotteryResult entity = _mapper.MapToEntityFromExternalResultItem(item);
- await _lotteryResultRepository.InsertAsync(entity);
+ // 使用 InsertReturnIdAsync 获取自增 Id,InsertAsync 不会回填自增主键
+ entity.Id = await _lotteryResultRepository.InsertReturnIdAsync(entity);
successCount++;
// 同时写入该条结果对应的奖级信息
diff --git a/src/DFApp.Web/Background/RssMirrorFetchJob.cs b/src/DFApp.Web/Background/RssMirrorFetchJob.cs
index 087c6e08..9c575e8d 100644
--- a/src/DFApp.Web/Background/RssMirrorFetchJob.cs
+++ b/src/DFApp.Web/Background/RssMirrorFetchJob.cs
@@ -157,10 +157,10 @@ private async Task FetchRssSource(RssSource source)
}
}
- // 逐个插入镜像条目(确保自增ID回填)
+ // 逐个插入镜像条目,确保自增 Id 回填
foreach (var item in newItems)
{
- await _rssMirrorItemRepository.InsertAsync(item);
+ item.Id = await _rssMirrorItemRepository.InsertReturnIdAsync(item);
}
// 插入分词数据
diff --git a/src/DFApp.Web/Data/ISqlSugarRepository.cs b/src/DFApp.Web/Data/ISqlSugarRepository.cs
index 27a40394..a84b163c 100644
--- a/src/DFApp.Web/Data/ISqlSugarRepository.cs
+++ b/src/DFApp.Web/Data/ISqlSugarRepository.cs
@@ -85,6 +85,13 @@ namespace DFApp.Web.Data;
/// 插入的行数
Task InsertAsync(T entity);
+ ///
+ /// 插入实体并返回自增主键 ID
+ ///
+ /// 实体
+ /// 自增主键 ID
+ Task InsertReturnIdAsync(T entity);
+
///
/// 批量插入实体
///
diff --git a/src/DFApp.Web/Data/SqlSugarRepository.cs b/src/DFApp.Web/Data/SqlSugarRepository.cs
index 8473a831..70e7bf05 100644
--- a/src/DFApp.Web/Data/SqlSugarRepository.cs
+++ b/src/DFApp.Web/Data/SqlSugarRepository.cs
@@ -152,6 +152,16 @@ public async Task InsertAsync(T entity)
return await _db.Insertable(entity).ExecuteCommandAsync();
}
+ ///
+ /// 插入实体并返回自增主键 ID
+ ///
+ /// 实体
+ /// 自增主键 ID
+ public async Task InsertReturnIdAsync(T entity)
+ {
+ return await _db.Insertable(entity).ExecuteReturnBigIdentityAsync();
+ }
+
///
/// 批量插入实体
///
diff --git a/src/DFApp.Web/Domain/ElectricVehicle/ElectricVehicleEnums.cs b/src/DFApp.Web/Domain/ElectricVehicle/ElectricVehicleEnums.cs
index e787f3b6..45fb1dfa 100644
--- a/src/DFApp.Web/Domain/ElectricVehicle/ElectricVehicleEnums.cs
+++ b/src/DFApp.Web/Domain/ElectricVehicle/ElectricVehicleEnums.cs
@@ -23,7 +23,22 @@ public enum CostType
///
/// 其他费用
///
- Other
+ Other = 3,
+
+ ///
+ /// 过路费
+ ///
+ Toll = 4,
+
+ ///
+ /// 停车费
+ ///
+ Parking = 5,
+
+ ///
+ /// 维修费
+ ///
+ Repair = 6
}
///
diff --git a/src/DFApp.Web/Services/Aria2/Aria2Manager.cs b/src/DFApp.Web/Services/Aria2/Aria2Manager.cs
index 0b866265..cd1b08c3 100644
--- a/src/DFApp.Web/Services/Aria2/Aria2Manager.cs
+++ b/src/DFApp.Web/Services/Aria2/Aria2Manager.cs
@@ -182,8 +182,8 @@ private async Task SaveTellStatusResultAsync(ResponseBase response)
var filesItemRepository = scope.ServiceProvider.GetRequiredService>();
var urisItemRepository = scope.ServiceProvider.GetRequiredService>();
- // 保存主记录
- await resultRepository.InsertAsync(tellStatusResult);
+ // 保存主记录,使用 InsertReturnIdAsync 获取自增 Id
+ tellStatusResult.Id = await resultRepository.InsertReturnIdAsync(tellStatusResult);
// 解析并保存文件列表
if (resultElement.TryGetProperty("files", out var filesElement) && filesElement.ValueKind == JsonValueKind.Array)
@@ -201,7 +201,8 @@ private async Task SaveTellStatusResultAsync(ResponseBase response)
Selected = fileElement.TryGetProperty("selected", out var selected) ? GetBoolValue(selected) : null
};
- await filesItemRepository.InsertAsync(filesItem);
+ // 使用 InsertReturnIdAsync 获取自增 Id,用于子表外键关联
+ filesItem.Id = (int)await filesItemRepository.InsertReturnIdAsync(filesItem);
_logger.LogInformation(" 文件[{Index}]: {Path}, 长度: {Length}, 已完成: {CompletedLength}",
filesItem.Index, filesItem.Path, filesItem.Length, filesItem.CompletedLength);
diff --git a/src/DFApp.Web/Services/Lottery/LotteryDataFetchService.cs b/src/DFApp.Web/Services/Lottery/LotteryDataFetchService.cs
index b0f73ced..e70f8041 100644
--- a/src/DFApp.Web/Services/Lottery/LotteryDataFetchService.cs
+++ b/src/DFApp.Web/Services/Lottery/LotteryDataFetchService.cs
@@ -145,8 +145,11 @@ public async Task FetchLotteryData(LotteryDataFetch
results.Add(lotteryResult);
}
- // 先保存 LotteryResult
- await _lotteryResultRepository.InsertAsync(results);
+ // 逐条保存 LotteryResult,确保自增 Id 正确回填
+ foreach (var result in results)
+ {
+ result.Id = await _lotteryResultRepository.InsertReturnIdAsync(result);
+ }
response.SavedCount = results.Count;
// 保存关联的 Prizegrades
diff --git a/src/DFApp.Web/Services/Media/ExternalLinkService.cs b/src/DFApp.Web/Services/Media/ExternalLinkService.cs
index 3c1a3cb7..f9ec1f0a 100644
--- a/src/DFApp.Web/Services/Media/ExternalLinkService.cs
+++ b/src/DFApp.Web/Services/Media/ExternalLinkService.cs
@@ -182,7 +182,8 @@ public Task GetExternalLink()
IsExternalLinkGenerated = true,
IsDownloadCompleted = true,
};
- await mediaInfoRepository.InsertAsync(zipMediaInfo);
+ // 使用 InsertReturnIdAsync 获取自增 Id,InsertAsync 不会回填自增主键
+ zipMediaInfo.Id = await mediaInfoRepository.InsertReturnIdAsync(zipMediaInfo);
temp.Add(zipMediaInfo);
}
@@ -211,7 +212,8 @@ public Task GetExternalLink()
});
}
- await externalLinkRepository.InsertAsync(mediaExternalLink);
+ // 使用 InsertReturnIdAsync 获取自增 Id,InsertAsync 不会回填自增主键
+ mediaExternalLink.Id = await externalLinkRepository.InsertReturnIdAsync(mediaExternalLink);
// 插入后获取外链 ID,为子记录赋值并批量插入
foreach (var item in mediaExternalLinkMediaIds)