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 | 2 | using System.Collections.Generic; |
| 3 | 3 | using System.IO; |
| 4 | 4 | using System.Linq; |
| 5 | +using System.Text.RegularExpressions; | |
| 5 | 6 | using System.Threading.Tasks; |
| 6 | 7 | using Mapster; |
| 7 | 8 | using Microsoft.AspNetCore.Http; |
| ... | ... | @@ -110,20 +111,87 @@ namespace NCC.Extend |
| 110 | 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 | 146 | // 从第1行开始读取数据(跳过标题行) |
| 114 | 147 | for (int i = 1; i < dataTable.Rows.Count; i++) |
| 115 | 148 | { |
| 116 | 149 | try |
| 117 | 150 | { |
| 118 | 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 | 197 | if (string.IsNullOrEmpty(employeeName) && string.IsNullOrEmpty(employeePhone)) |
| ... | ... | @@ -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 | 309 | decimal.TryParse(workDaysText, out decimal workDays); | ... | ... |