From 0cd0fce8c2c17c158bb695c19d7078c8df058b0c Mon Sep 17 00:00:00 2001 From: 18631081161 <2088094923@qq.com> Date: Tue, 20 Jan 2026 14:47:25 +0800 Subject: [PATCH] =?UTF-8?q?bug=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/PersonnelController.cs | 56 ++++++++++++++++++- .../Implementations/PersonnelService.cs | 32 +++++++++-- .../Services/Interfaces/IPersonnelService.cs | 1 + src/frontend/src/views/Dashboard.vue | 15 +++-- .../src/views/personnel/PersonnelList.vue | 33 ++++++++++- 5 files changed, 122 insertions(+), 15 deletions(-) diff --git a/src/MilitaryTrainingManagement/Controllers/PersonnelController.cs b/src/MilitaryTrainingManagement/Controllers/PersonnelController.cs index f65a333..bfc6949 100644 --- a/src/MilitaryTrainingManagement/Controllers/PersonnelController.cs +++ b/src/MilitaryTrainingManagement/Controllers/PersonnelController.cs @@ -1177,14 +1177,53 @@ public class PersonnelController : BaseApiController cell.Style.Border.OutsideBorder = ClosedXML.Excel.XLBorderStyleValues.Thin; } + // 设置照片列宽度 + worksheet.Column(3).Width = 15; + // 填充数据(从第4行开始) int row = 4; int seq = 1; + var webRootPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot"); + foreach (var p in personnelList) { worksheet.Cell(row, 1).Value = seq++; worksheet.Cell(row, 2).Value = p.Name; - worksheet.Cell(row, 3).Value = ""; // 照片列留空 + + // 处理照片 - 2寸照片比例 35mm x 49mm (约 3:4) + worksheet.Row(row).Height = 70; // 设置行高 + if (!string.IsNullOrEmpty(p.PhotoPath)) + { + try + { + // PhotoPath 格式如 "/uploads/xxx.jpg",需要转换为实际文件路径 + var photoRelativePath = p.PhotoPath.TrimStart('/').Replace("/", Path.DirectorySeparatorChar.ToString()); + var photoFullPath = Path.Combine(webRootPath, photoRelativePath); + + if (System.IO.File.Exists(photoFullPath)) + { + var picture = worksheet.AddPicture(photoFullPath); + // 先移动到目标单元格 + var targetCell = worksheet.Cell(row, 3); + picture.MoveTo(targetCell, 18, 3); // 偏移实现居中 + // 2寸照片比例:宽35mm 高49mm,按比例缩放 (38 x 53 像素) + picture.WithSize(38, 53); + } + else + { + worksheet.Cell(row, 3).Value = "图片不存在"; + } + } + catch + { + worksheet.Cell(row, 3).Value = "图片加载失败"; + } + } + else + { + worksheet.Cell(row, 3).Value = ""; + } + worksheet.Cell(row, 4).Value = p.Unit ?? p.SubmittedByUnit?.Name ?? ""; worksheet.Cell(row, 5).Value = p.Position; worksheet.Cell(row, 6).Value = p.Rank; @@ -1381,6 +1420,19 @@ public class PersonnelController : BaseApiController continue; } + // 检查士兵证号是否重复 + var idNumber = GetNullIfEmpty(row.Cell(7).GetString()); + if (!string.IsNullOrEmpty(idNumber)) + { + var existingPersonnel = await _personnelService.GetByIdNumberAsync(idNumber); + if (existingPersonnel != null) + { + failCount++; + importResults.Add(new { row = row.RowNumber(), success = false, message = $"士兵证号 {idNumber} 已存在" }); + continue; + } + } + var birthDate = GetNullIfEmpty(row.Cell(13).GetString()); var age = CalculateAge(birthDate); @@ -1389,7 +1441,7 @@ public class PersonnelController : BaseApiController Name = name, Gender = "男", // 默认性别,模板中没有性别列 Age = age, - IdNumber = GetNullIfEmpty(row.Cell(7).GetString()) ?? "", // 士兵证号 + IdNumber = idNumber ?? "", // 士兵证号 Unit = unit, Position = position, Rank = rank, diff --git a/src/MilitaryTrainingManagement/Services/Implementations/PersonnelService.cs b/src/MilitaryTrainingManagement/Services/Implementations/PersonnelService.cs index f60bfa5..d161c42 100644 --- a/src/MilitaryTrainingManagement/Services/Implementations/PersonnelService.cs +++ b/src/MilitaryTrainingManagement/Services/Implementations/PersonnelService.cs @@ -40,6 +40,13 @@ public class PersonnelService : IPersonnelService .FirstOrDefaultAsync(p => p.Id == id); } + public async Task GetByIdNumberAsync(string idNumber) + { + if (string.IsNullOrEmpty(idNumber)) return null; + return await _context.Personnel + .FirstOrDefaultAsync(p => p.IdNumber == idNumber); + } + public async Task> GetByUnitAsync(int unitId, bool includeSubordinates = false) { if (includeSubordinates) @@ -111,11 +118,20 @@ public class PersonnelService : IPersonnelService // 例如:营级单位提交的人才,团级审批后应为"营级人才" actualLevel = (PersonnelLevel)(int)personnel.SubmittedByUnit!.Level; - // 验证审批单位层级必须高于提交单位层级(数值越小层级越高) - var unitLevelValue = (int)approvedByUnit.Level; - var submittedUnitLevelValue = (int)personnel.SubmittedByUnit!.Level; - if (unitLevelValue >= submittedUnitLevelValue) - throw new ArgumentException("审批单位层级必须高于提交单位层级"); + // 师部(最高级别)可以审批自己提交的人才,此时人才等级为师级 + if (approvedByUnit.Level == OrganizationalLevel.Division && + personnel.SubmittedByUnitId == approvedByUnitId) + { + actualLevel = PersonnelLevel.Division; + } + else + { + // 验证审批单位层级必须高于提交单位层级(数值越小层级越高) + var unitLevelValue = (int)approvedByUnit.Level; + var submittedUnitLevelValue = (int)personnel.SubmittedByUnit!.Level; + if (unitLevelValue >= submittedUnitLevelValue) + throw new ArgumentException("审批单位层级必须高于提交单位层级"); + } } else if (previousStatus == PersonnelStatus.Approved) { @@ -584,7 +600,11 @@ public class PersonnelService : IPersonnelService if (personnel.Status == PersonnelStatus.Rejected) return false; - // 本单位不能审批自己提交的人员,必须由上级单位审批 + // 师部(最高级别)可以审批所有人员,包括自己提交的 + if (user.OrganizationalUnit!.Level == OrganizationalLevel.Division) + return true; + + // 非师部:本单位不能审批自己提交的人员,必须由上级单位审批 if (user.OrganizationalUnitId == personnel.SubmittedByUnitId) return false; diff --git a/src/MilitaryTrainingManagement/Services/Interfaces/IPersonnelService.cs b/src/MilitaryTrainingManagement/Services/Interfaces/IPersonnelService.cs index 9fb32c9..038365e 100644 --- a/src/MilitaryTrainingManagement/Services/Interfaces/IPersonnelService.cs +++ b/src/MilitaryTrainingManagement/Services/Interfaces/IPersonnelService.cs @@ -9,6 +9,7 @@ namespace MilitaryTrainingManagement.Services.Interfaces; public interface IPersonnelService { Task GetByIdAsync(int id); + Task GetByIdNumberAsync(string idNumber); Task> GetByUnitAsync(int unitId, bool includeSubordinates = false); Task CreateAsync(Personnel personnel); Task UpdateAsync(Personnel personnel); diff --git a/src/frontend/src/views/Dashboard.vue b/src/frontend/src/views/Dashboard.vue index 3299246..e56d5b9 100644 --- a/src/frontend/src/views/Dashboard.vue +++ b/src/frontend/src/views/Dashboard.vue @@ -102,6 +102,9 @@ function getPieOption(regiment: RegimentAllocationStats) { const consumed = regiment.totalConsumed const remaining = regiment.totalQuota - consumed + // 当配额为0时,显示全灰色 + const hasData = regiment.totalQuota > 0 + return { tooltip: { trigger: 'item', @@ -130,10 +133,14 @@ function getPieOption(regiment: RegimentAllocationStats) { labelLine: { show: false }, - data: [ - { value: consumed, name: '已消耗', itemStyle: { color: '#67C23A' } }, - { value: remaining > 0 ? remaining : 0, name: '剩余', itemStyle: { color: '#E4E7ED' } } - ] + data: hasData + ? [ + { value: consumed, name: '已消耗', itemStyle: { color: '#67C23A' } }, + { value: remaining > 0 ? remaining : 0, name: '剩余', itemStyle: { color: '#E4E7ED' } } + ] + : [ + { value: 1, name: '无配额', itemStyle: { color: '#E4E7ED' } } + ] } ] } diff --git a/src/frontend/src/views/personnel/PersonnelList.vue b/src/frontend/src/views/personnel/PersonnelList.vue index 8615976..2c99a8b 100644 --- a/src/frontend/src/views/personnel/PersonnelList.vue +++ b/src/frontend/src/views/personnel/PersonnelList.vue @@ -669,11 +669,27 @@ async function handleFileChange(event: Event) { } if (result.failCount > 0) { - ElMessage.warning(`${result.failCount}条记录导入失败`) - console.log('导入失败详情:', result.results.filter(r => !r.success)) + // 获取失败记录的详细信息 + const failedResults = result.results.filter((r: any) => !r.success) + const failMessages = failedResults.slice(0, 3).map((r: any) => `第${r.row}行: ${r.message}`).join('\n') + const moreCount = failedResults.length > 3 ? `\n...还有${failedResults.length - 3}条失败` : '' + + ElMessageBox.alert( + `${result.failCount}条记录导入失败:\n\n${failMessages}${moreCount}`, + '导入失败详情', + { + type: 'warning', + confirmButtonText: '知道了' + } + ) + console.log('导入失败详情:', failedResults) } } catch (error: any) { - ElMessage.error(error.response?.data?.message || '导入失败') + // 解析后端返回的错误信息 + const errorMsg = error.response?.data?.message || '导入失败' + // 转换常见的英文错误为中文 + const chineseMsg = translateErrorMessage(errorMsg) + ElMessage.error(chineseMsg) } finally { importing.value = false // 清空文件选择,允许重复选择同一文件 @@ -681,6 +697,17 @@ async function handleFileChange(event: Event) { } } +// 将英文错误信息转换为中文 +function translateErrorMessage(msg: string): string { + if (msg.includes('duplicate key') || msg.includes('unique index') || msg.includes('IX_Personnel_IdNumber')) { + return '导入失败:存在重复的士兵证号' + } + if (msg.includes('entity changes')) { + return '导入失败:数据保存出错,请检查数据格式' + } + return msg +} + function handleView(person: Personnel) { router.push(`/personnel/${person.id}`) }