Commit 2030a58d60aca87a461237747e803e9bf328ddb5
1 parent
0fc6b403
优化考勤数据导入接口:支持按列名匹配、多种年份月份格式解析
- 修改ImportAttendanceDataFromExcel方法,支持按列名动态匹配字段 - 支持多种年份格式:纯数字、日期格式、中文格式(如:2025年) - 支持多种月份格式:纯数字、日期格式、中文格式(如:11月) - 处理DateTime、double、string等多种数据类型 - 改进错误提示信息,显示实际读取的值和类型 - 修复编译错误:添加System.Text.RegularExpressions命名空间
Showing
1 changed file
with
167 additions
and
12 deletions
netcore/src/Modularity/Extend/NCC.Extend/LqAttendanceSummaryService.cs
| @@ -2,6 +2,7 @@ using System; | @@ -2,6 +2,7 @@ using System; | ||
| 2 | using System.Collections.Generic; | 2 | using System.Collections.Generic; |
| 3 | using System.IO; | 3 | using System.IO; |
| 4 | using System.Linq; | 4 | using System.Linq; |
| 5 | +using System.Text.RegularExpressions; | ||
| 5 | using System.Threading.Tasks; | 6 | using System.Threading.Tasks; |
| 6 | using Mapster; | 7 | using Mapster; |
| 7 | using Microsoft.AspNetCore.Http; | 8 | using Microsoft.AspNetCore.Http; |
| @@ -110,20 +111,87 @@ namespace NCC.Extend | @@ -110,20 +111,87 @@ namespace NCC.Extend | ||
| 110 | throw NCCException.Oh("Excel文件中没有数据行"); | 111 | throw NCCException.Oh("Excel文件中没有数据行"); |
| 111 | } | 112 | } |
| 112 | 113 | ||
| 114 | + // 根据列名查找列索引(支持多种可能的列名) | ||
| 115 | + int GetColumnIndex(string[] possibleNames) | ||
| 116 | + { | ||
| 117 | + foreach (var name in possibleNames) | ||
| 118 | + { | ||
| 119 | + for (int col = 0; col < dataTable.Columns.Count; col++) | ||
| 120 | + { | ||
| 121 | + var columnName = dataTable.Columns[col].ColumnName?.Trim(); | ||
| 122 | + if (columnName == name || columnName?.Contains(name) == true) | ||
| 123 | + { | ||
| 124 | + return col; | ||
| 125 | + } | ||
| 126 | + } | ||
| 127 | + } | ||
| 128 | + return -1; | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + var nameColIndex = GetColumnIndex(new[] { "员工姓名", "姓名", "员工", "姓名" }); | ||
| 132 | + var phoneColIndex = GetColumnIndex(new[] { "员工电话", "电话", "手机", "手机号", "联系电话" }); | ||
| 133 | + var yearColIndex = GetColumnIndex(new[] { "年份", "年" }); | ||
| 134 | + var monthColIndex = GetColumnIndex(new[] { "月份", "月" }); | ||
| 135 | + var workDaysColIndex = GetColumnIndex(new[] { "出勤天数", "出勤", "工作天数", "上班天数" }); | ||
| 136 | + var leaveDaysColIndex = GetColumnIndex(new[] { "请假天数", "请假", "事假天数" }); | ||
| 137 | + var restDaysColIndex = GetColumnIndex(new[] { "休息天数", "休息", "休假天数" }); | ||
| 138 | + var remarkColIndex = GetColumnIndex(new[] { "备注", "说明", "备注信息" }); | ||
| 139 | + | ||
| 140 | + // 验证必需的列是否存在 | ||
| 141 | + if (nameColIndex == -1) throw NCCException.Oh("Excel文件中未找到'员工姓名'列"); | ||
| 142 | + if (phoneColIndex == -1) throw NCCException.Oh("Excel文件中未找到'员工电话'列"); | ||
| 143 | + if (yearColIndex == -1) throw NCCException.Oh("Excel文件中未找到'年份'列"); | ||
| 144 | + if (monthColIndex == -1) throw NCCException.Oh("Excel文件中未找到'月份'列"); | ||
| 145 | + | ||
| 113 | // 从第1行开始读取数据(跳过标题行) | 146 | // 从第1行开始读取数据(跳过标题行) |
| 114 | for (int i = 1; i < dataTable.Rows.Count; i++) | 147 | for (int i = 1; i < dataTable.Rows.Count; i++) |
| 115 | { | 148 | { |
| 116 | try | 149 | try |
| 117 | { | 150 | { |
| 118 | var row = dataTable.Rows[i]; | 151 | var row = dataTable.Rows[i]; |
| 119 | - var employeeName = row[0]?.ToString()?.Trim(); | ||
| 120 | - var employeePhone = row[1]?.ToString()?.Trim(); | ||
| 121 | - var yearText = row[2]?.ToString()?.Trim(); | ||
| 122 | - var monthText = row[3]?.ToString()?.Trim(); | ||
| 123 | - var workDaysText = row[4]?.ToString()?.Trim(); | ||
| 124 | - var leaveDaysText = row[5]?.ToString()?.Trim(); | ||
| 125 | - var restDaysText = row[6]?.ToString()?.Trim(); | ||
| 126 | - var remark = row[7]?.ToString()?.Trim(); | 152 | + var employeeName = row[nameColIndex]?.ToString()?.Trim(); |
| 153 | + var employeePhone = row[phoneColIndex]?.ToString()?.Trim(); | ||
| 154 | + | ||
| 155 | + // 处理年份:可能是数字、日期或字符串 | ||
| 156 | + string yearText = null; | ||
| 157 | + if (yearColIndex >= 0 && row[yearColIndex] != null && row[yearColIndex] != DBNull.Value) | ||
| 158 | + { | ||
| 159 | + if (row[yearColIndex] is DateTime dt) | ||
| 160 | + { | ||
| 161 | + yearText = dt.Year.ToString(); | ||
| 162 | + } | ||
| 163 | + else if (row[yearColIndex] is double d) | ||
| 164 | + { | ||
| 165 | + yearText = ((int)d).ToString(); | ||
| 166 | + } | ||
| 167 | + else | ||
| 168 | + { | ||
| 169 | + yearText = row[yearColIndex].ToString()?.Trim(); | ||
| 170 | + } | ||
| 171 | + } | ||
| 172 | + | ||
| 173 | + // 处理月份:可能是数字、日期或字符串 | ||
| 174 | + string monthText = null; | ||
| 175 | + if (monthColIndex >= 0 && row[monthColIndex] != null && row[monthColIndex] != DBNull.Value) | ||
| 176 | + { | ||
| 177 | + if (row[monthColIndex] is DateTime dt) | ||
| 178 | + { | ||
| 179 | + monthText = dt.Month.ToString(); | ||
| 180 | + } | ||
| 181 | + else if (row[monthColIndex] is double d) | ||
| 182 | + { | ||
| 183 | + monthText = ((int)d).ToString(); | ||
| 184 | + } | ||
| 185 | + else | ||
| 186 | + { | ||
| 187 | + monthText = row[monthColIndex].ToString()?.Trim(); | ||
| 188 | + } | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + var workDaysText = workDaysColIndex >= 0 ? row[workDaysColIndex]?.ToString()?.Trim() : null; | ||
| 192 | + var leaveDaysText = leaveDaysColIndex >= 0 ? row[leaveDaysColIndex]?.ToString()?.Trim() : null; | ||
| 193 | + var restDaysText = restDaysColIndex >= 0 ? row[restDaysColIndex]?.ToString()?.Trim() : null; | ||
| 194 | + var remark = remarkColIndex >= 0 ? row[remarkColIndex]?.ToString()?.Trim() : null; | ||
| 127 | 195 | ||
| 128 | // 跳过空行 | 196 | // 跳过空行 |
| 129 | if (string.IsNullOrEmpty(employeeName) && string.IsNullOrEmpty(employeePhone)) | 197 | if (string.IsNullOrEmpty(employeeName) && string.IsNullOrEmpty(employeePhone)) |
| @@ -142,13 +210,100 @@ namespace NCC.Extend | @@ -142,13 +210,100 @@ namespace NCC.Extend | ||
| 142 | } | 210 | } |
| 143 | 211 | ||
| 144 | // 解析数值字段 | 212 | // 解析数值字段 |
| 145 | - if (!int.TryParse(yearText, out int year)) | 213 | + int year = 0; |
| 214 | + int month = 0; | ||
| 215 | + | ||
| 216 | + // 解析年份:支持纯数字、日期格式、中文格式 | ||
| 217 | + if (string.IsNullOrEmpty(yearText)) | ||
| 218 | + { | ||
| 219 | + throw new Exception($"第{i + 1}行:年份不能为空"); | ||
| 220 | + } | ||
| 221 | + | ||
| 222 | + // 尝试直接解析为整数 | ||
| 223 | + if (int.TryParse(yearText, out year)) | ||
| 224 | + { | ||
| 225 | + // 成功解析 | ||
| 226 | + } | ||
| 227 | + // 尝试解析日期格式(如:2025-11-01 或 2025/11/01) | ||
| 228 | + else if (DateTime.TryParse(yearText, out DateTime yearDate)) | ||
| 229 | + { | ||
| 230 | + year = yearDate.Year; | ||
| 231 | + } | ||
| 232 | + // 尝试解析中文格式(如:2025年) | ||
| 233 | + else if (yearText.Contains("年")) | ||
| 234 | + { | ||
| 235 | + var yearMatch = Regex.Match(yearText, @"(\d{4})年"); | ||
| 236 | + if (yearMatch.Success && int.TryParse(yearMatch.Groups[1].Value, out year)) | ||
| 237 | + { | ||
| 238 | + // 成功解析 | ||
| 239 | + } | ||
| 240 | + else | ||
| 241 | + { | ||
| 242 | + throw new Exception($"第{i + 1}行:年份格式错误,无法解析:{yearText}"); | ||
| 243 | + } | ||
| 244 | + } | ||
| 245 | + else | ||
| 246 | + { | ||
| 247 | + throw new Exception($"第{i + 1}行:年份格式错误,无法解析。实际值:\"{yearText}\"(类型:{yearText?.GetType().Name})"); | ||
| 248 | + } | ||
| 249 | + | ||
| 250 | + // 验证年份范围 | ||
| 251 | + if (year < 2020 || year > 2030) | ||
| 252 | + { | ||
| 253 | + throw new Exception($"第{i + 1}行:年份必须在2020-2030之间"); | ||
| 254 | + } | ||
| 255 | + | ||
| 256 | + // 解析月份:支持纯数字、日期格式、中文格式 | ||
| 257 | + if (string.IsNullOrEmpty(monthText)) | ||
| 258 | + { | ||
| 259 | + throw new Exception($"第{i + 1}行:月份不能为空"); | ||
| 260 | + } | ||
| 261 | + | ||
| 262 | + // 尝试直接解析为整数 | ||
| 263 | + if (int.TryParse(monthText, out month)) | ||
| 264 | + { | ||
| 265 | + // 成功解析 | ||
| 266 | + } | ||
| 267 | + // 尝试解析日期格式(如:2025-11-01 或 2025/11/01) | ||
| 268 | + else if (DateTime.TryParse(monthText, out DateTime monthDate)) | ||
| 269 | + { | ||
| 270 | + month = monthDate.Month; | ||
| 271 | + } | ||
| 272 | + // 尝试解析中文格式(如:11月) | ||
| 273 | + else if (monthText.Contains("月")) | ||
| 274 | + { | ||
| 275 | + var monthMatch = Regex.Match(monthText, @"(\d{1,2})月"); | ||
| 276 | + if (monthMatch.Success && int.TryParse(monthMatch.Groups[1].Value, out month)) | ||
| 277 | + { | ||
| 278 | + // 成功解析 | ||
| 279 | + } | ||
| 280 | + else | ||
| 281 | + { | ||
| 282 | + throw new Exception($"第{i + 1}行:月份格式错误,无法解析:{monthText}"); | ||
| 283 | + } | ||
| 284 | + } | ||
| 285 | + // 尝试解析"年-月"格式(如:2025-11) | ||
| 286 | + else if (monthText.Contains("-") || monthText.Contains("/")) | ||
| 287 | + { | ||
| 288 | + var parts = monthText.Split(new[] { "-", "/" }, StringSplitOptions.RemoveEmptyEntries); | ||
| 289 | + if (parts.Length >= 2 && int.TryParse(parts[1], out month)) | ||
| 290 | + { | ||
| 291 | + // 成功解析 | ||
| 292 | + } | ||
| 293 | + else | ||
| 294 | + { | ||
| 295 | + throw new Exception($"第{i + 1}行:月份格式错误,无法解析:{monthText}"); | ||
| 296 | + } | ||
| 297 | + } | ||
| 298 | + else | ||
| 146 | { | 299 | { |
| 147 | - throw new Exception($"第{i + 1}行:年份格式错误"); | 300 | + throw new Exception($"第{i + 1}行:月份格式错误,无法解析。实际值:\"{monthText}\"(类型:{monthText?.GetType().Name})"); |
| 148 | } | 301 | } |
| 149 | - if (!int.TryParse(monthText, out int month)) | 302 | + |
| 303 | + // 验证月份范围 | ||
| 304 | + if (month < 1 || month > 12) | ||
| 150 | { | 305 | { |
| 151 | - throw new Exception($"第{i + 1}行:月份格式错误"); | 306 | + throw new Exception($"第{i + 1}行:月份必须在1-12之间"); |
| 152 | } | 307 | } |
| 153 | 308 | ||
| 154 | decimal.TryParse(workDaysText, out decimal workDays); | 309 | decimal.TryParse(workDaysText, out decimal workDays); |