Commit bff07b1f020dda8518e3f1d38ad8b038f3fa053d

Authored by “wangming”
1 parent c6d1116d

Refactor FileVariable and LqEventService to include UserSignatureFilePath; enhan…

…ce LqEventUser DTO with StoreId; implement Excel import functionality for LqEvent users; add UploadBase64Image method in FileService for user signature uploads.
netcore/src/Modularity/Common/NCC.Common/Configuration/FileVariable.cs
1 -using NCC.Dependency;  
2 -using System.IO; 1 +using System.IO;
  2 +using NCC.Dependency;
3 3
4 namespace NCC.Common.Configuration 4 namespace NCC.Common.Configuration
5 { 5 {
@@ -73,5 +73,10 @@ namespace NCC.Common.Configuration @@ -73,5 +73,10 @@ namespace NCC.Common.Configuration
73 /// 模板路径 73 /// 模板路径
74 /// </summary> 74 /// </summary>
75 public static string TemplateFilePath = SystemPath + "TemplateFile/"; 75 public static string TemplateFilePath = SystemPath + "TemplateFile/";
  76 +
  77 + /// <summary>
  78 + /// 用户签名存储路径
  79 + /// </summary>
  80 + public static string UserSignatureFilePath = SystemPath + "UserSignature/";
76 } 81 }
77 } 82 }
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqEvent/LqEventImportResult.cs 0 → 100644
  1 +using System.Collections.Generic;
  2 +using NCC.Extend.Entitys.Dto.LqEventUser;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqEvent
  5 +{
  6 + /// <summary>
  7 + /// 拓客活动Excel导入结果
  8 + /// </summary>
  9 + public class LqEventImportResult
  10 + {
  11 + /// <summary>
  12 + /// 成功数量
  13 + /// </summary>
  14 + public int SuccessCount { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 失败数量
  18 + /// </summary>
  19 + public int FailCount { get; set; }
  20 +
  21 + /// <summary>
  22 + /// 总数量
  23 + /// </summary>
  24 + public int TotalCount { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 成功的数据列表
  28 + /// </summary>
  29 + public List<LqEventUserCrInput> SuccessData { get; set; } = new List<LqEventUserCrInput>();
  30 +
  31 + /// <summary>
  32 + /// 失败的数据列表
  33 + /// </summary>
  34 + public List<LqEventImportError> FailData { get; set; } = new List<LqEventImportError>();
  35 + }
  36 +
  37 + /// <summary>
  38 + /// 拓客活动导入错误信息
  39 + /// </summary>
  40 + public class LqEventImportError
  41 + {
  42 + /// <summary>
  43 + /// 行号
  44 + /// </summary>
  45 + public int RowNumber { get; set; }
  46 +
  47 + /// <summary>
  48 + /// 员工手机号
  49 + /// </summary>
  50 + public string MobilePhone { get; set; }
  51 +
  52 + /// <summary>
  53 + /// 姓名
  54 + /// </summary>
  55 + public string Name { get; set; }
  56 +
  57 + /// <summary>
  58 + /// 战队
  59 + /// </summary>
  60 + public string TeamName { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 门店
  64 + /// </summary>
  65 + public string StoreName { get; set; }
  66 +
  67 + /// <summary>
  68 + /// 目标张数
  69 + /// </summary>
  70 + public int? TargetCount { get; set; }
  71 +
  72 + /// <summary>
  73 + /// 错误信息
  74 + /// </summary>
  75 + public string ErrorMessage { get; set; }
  76 + }
  77 +}
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqEventUser/LqEventUserCrInput.cs
@@ -33,5 +33,10 @@ namespace NCC.Extend.Entitys.Dto.LqEventUser @@ -33,5 +33,10 @@ namespace NCC.Extend.Entitys.Dto.LqEventUser
33 /// 战队名称 33 /// 战队名称
34 /// </summary> 34 /// </summary>
35 public string TeamName { get; set; } 35 public string TeamName { get; set; }
  36 +
  37 + /// <summary>
  38 + /// 门店id
  39 + /// </summary>
  40 + public string StoreId { get; set; }
36 } 41 }
37 } 42 }
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdPxmx/LqKdPxmxInfoOutput.cs
@@ -12,62 +12,67 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb @@ -12,62 +12,67 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
12 /// 明细编号 12 /// 明细编号
13 /// </summary> 13 /// </summary>
14 public string id { get; set; } 14 public string id { get; set; }
15 - 15 +
16 /// <summary> 16 /// <summary>
17 /// 关联开单编号 17 /// 关联开单编号
18 /// </summary> 18 /// </summary>
19 public string glkdbh { get; set; } 19 public string glkdbh { get; set; }
20 - 20 +
21 /// <summary> 21 /// <summary>
22 /// 品项编号 22 /// 品项编号
23 /// </summary> 23 /// </summary>
24 public string px { get; set; } 24 public string px { get; set; }
25 - 25 +
26 /// <summary> 26 /// <summary>
27 /// 品项名称 27 /// 品项名称
28 /// </summary> 28 /// </summary>
29 public string pxmc { get; set; } 29 public string pxmc { get; set; }
30 - 30 +
31 /// <summary> 31 /// <summary>
32 /// 品项价格 32 /// 品项价格
33 /// </summary> 33 /// </summary>
34 public decimal pxjg { get; set; } 34 public decimal pxjg { get; set; }
35 - 35 +
36 /// <summary> 36 /// <summary>
37 /// 项目次数 37 /// 项目次数
38 /// </summary> 38 /// </summary>
39 public int? projectNumber { get; set; } 39 public int? projectNumber { get; set; }
40 - 40 +
41 /// <summary> 41 /// <summary>
42 /// 是否有效 42 /// 是否有效
43 /// </summary> 43 /// </summary>
44 public int? isEnabled { get; set; } 44 public int? isEnabled { get; set; }
45 - 45 +
46 /// <summary> 46 /// <summary>
47 /// 来源类型(开卡/赠送/其他) 47 /// 来源类型(开卡/赠送/其他)
48 /// </summary> 48 /// </summary>
49 public string sourceType { get; set; } 49 public string sourceType { get; set; }
50 - 50 +
51 /// <summary> 51 /// <summary>
52 /// 会员ID 52 /// 会员ID
53 /// </summary> 53 /// </summary>
54 public string memberId { get; set; } 54 public string memberId { get; set; }
55 - 55 +
56 /// <summary> 56 /// <summary>
57 /// 创建时间 57 /// 创建时间
58 /// </summary> 58 /// </summary>
59 public DateTime? createTime { get; set; } 59 public DateTime? createTime { get; set; }
60 - 60 +
61 /// <summary> 61 /// <summary>
62 /// 总价格(品项价格 × 项目次数) 62 /// 总价格(品项价格 × 项目次数)
63 /// </summary> 63 /// </summary>
64 public decimal totalPrice { get; set; } 64 public decimal totalPrice { get; set; }
65 - 65 +
  66 + /// <summary>
  67 + /// 实际价格
  68 + /// </summary>
  69 + public decimal actualPrice { get; set; }
  70 +
66 /// <summary> 71 /// <summary>
67 /// 健康师业绩列表 72 /// 健康师业绩列表
68 /// </summary> 73 /// </summary>
69 public List<LqKdJksyjInfoOutput> lqKdJksyjList { get; set; } 74 public List<LqKdJksyjInfoOutput> lqKdJksyjList { get; set; }
70 - 75 +
71 /// <summary> 76 /// <summary>
72 /// 科技部老师业绩列表 77 /// 科技部老师业绩列表
73 /// </summary> 78 /// </summary>
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxCrInput.cs
@@ -81,7 +81,7 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx @@ -81,7 +81,7 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
81 /// <summary> 81 /// <summary>
82 /// 客户消费 82 /// 客户消费
83 /// </summary> 83 /// </summary>
84 - public string khxf { get; set; } 84 + public List<string> khxf { get; set; }
85 85
86 /// <summary> 86 /// <summary>
87 /// 消费频次 87 /// 消费频次
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_eventuser/LqEventUserEntity.cs
@@ -56,5 +56,11 @@ namespace NCC.Extend.Entitys.lq_eventuser @@ -56,5 +56,11 @@ namespace NCC.Extend.Entitys.lq_eventuser
56 /// </summary> 56 /// </summary>
57 [SugarColumn(ColumnName = "F_EventTarget")] 57 [SugarColumn(ColumnName = "F_EventTarget")]
58 public int EventTarget { get; set; } 58 public int EventTarget { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 门店id
  62 + /// </summary>
  63 + [SugarColumn(ColumnName = "F_StoreId")]
  64 + public string StoreId { get; set; }
59 } 65 }
60 } 66 }
netcore/src/Modularity/Extend/NCC.Extend/LqEventService.cs
1 using System; 1 using System;
2 using System.Collections.Generic; 2 using System.Collections.Generic;
  3 +using System.IO;
3 using System.Linq; 4 using System.Linq;
4 using System.Threading.Tasks; 5 using System.Threading.Tasks;
5 using Mapster; 6 using Mapster;
  7 +using Microsoft.AspNetCore.Http;
6 using Microsoft.AspNetCore.Mvc; 8 using Microsoft.AspNetCore.Mvc;
7 using NCC.Common.Core.Manager; 9 using NCC.Common.Core.Manager;
8 using NCC.Common.Filter; 10 using NCC.Common.Filter;
  11 +using NCC.Common.Helper;
9 using NCC.Dependency; 12 using NCC.Dependency;
10 using NCC.DynamicApiController; 13 using NCC.DynamicApiController;
11 using NCC.Extend.Entitys.Dto.LqEvent; 14 using NCC.Extend.Entitys.Dto.LqEvent;
12 using NCC.Extend.Entitys.Dto.LqEventUser; 15 using NCC.Extend.Entitys.Dto.LqEventUser;
13 using NCC.Extend.Entitys.lq_event; 16 using NCC.Extend.Entitys.lq_event;
14 using NCC.Extend.Entitys.lq_eventuser; 17 using NCC.Extend.Entitys.lq_eventuser;
  18 +using NCC.Extend.Entitys.lq_mdxx;
15 using NCC.Extend.Interfaces.LqEvent; 19 using NCC.Extend.Interfaces.LqEvent;
16 using NCC.FriendlyException; 20 using NCC.FriendlyException;
  21 +using NCC.System.Entitys.Permission;
17 using SqlSugar; 22 using SqlSugar;
18 using Yitter.IdGenerator; 23 using Yitter.IdGenerator;
19 24
@@ -219,6 +224,7 @@ namespace NCC.Extend.LqEvent @@ -219,6 +224,7 @@ namespace NCC.Extend.LqEvent
219 TeamName = member.TeamName, 224 TeamName = member.TeamName,
220 CreationTime = DateTime.Now, 225 CreationTime = DateTime.Now,
221 CreationUser = userInfo?.userName, 226 CreationUser = userInfo?.userName,
  227 + StoreId = member.StoreId,
222 } 228 }
223 ); 229 );
224 } 230 }
@@ -306,6 +312,7 @@ namespace NCC.Extend.LqEvent @@ -306,6 +312,7 @@ namespace NCC.Extend.LqEvent
306 TeamName = member.TeamName, 312 TeamName = member.TeamName,
307 CreationTime = DateTime.Now, 313 CreationTime = DateTime.Now,
308 CreationUser = userInfo?.userName, 314 CreationUser = userInfo?.userName,
  315 + StoreId = member.StoreId,
309 } 316 }
310 ); 317 );
311 } 318 }
@@ -482,5 +489,235 @@ namespace NCC.Extend.LqEvent @@ -482,5 +489,235 @@ namespace NCC.Extend.LqEvent
482 } 489 }
483 } 490 }
484 #endregion 491 #endregion
  492 +
  493 + #region Excel导入拓客活动用户
  494 +
  495 + /// <summary>
  496 + /// Excel导入拓客活动用户
  497 + /// </summary>
  498 + /// <remarks>
  499 + /// 通过Excel文件导入拓客活动用户数据,支持批量导入。
  500 + /// Excel列名:员工手机号、姓名、战队、门店、目标张数
  501 + ///
  502 + /// 导入流程:
  503 + /// 1. 通过员工手机号查找用户信息,获取用户ID和组织ID
  504 + /// 2. 通过门店中文名称查找门店信息,获取门店ID
  505 + /// 3. 验证数据完整性
  506 + /// 4. 返回成功和失败的数据列表
  507 + ///
  508 + /// 示例请求:
  509 + /// POST /api/Extend/LqEvent/import-users
  510 + /// Content-Type: multipart/form-data
  511 + ///
  512 + /// 参数:
  513 + /// - file: Excel文件
  514 + ///
  515 + /// 返回数据:
  516 + /// - SuccessCount: 成功数量
  517 + /// - FailCount: 失败数量
  518 + /// - SuccessData: 成功的数据列表
  519 + /// - FailData: 失败的数据列表(包含错误信息)
  520 + /// </remarks>
  521 + /// <param name="file">Excel文件</param>
  522 + /// <param name="eventId">拓客活动ID</param>
  523 + /// <returns>导入结果</returns>
  524 + /// <response code="200">导入完成,返回成功和失败的数据统计</response>
  525 + /// <response code="400">请求参数错误或文件格式不正确</response>
  526 + /// <response code="500">服务器内部错误</response>
  527 + [HttpPost("import-users")]
  528 + public async Task<LqEventImportResult> ImportUsers(IFormFile file)
  529 + {
  530 + try
  531 + {
  532 + if (file == null || file.Length == 0)
  533 + {
  534 + throw NCCException.Oh("请选择要导入的Excel文件");
  535 + }
  536 + var result = new LqEventImportResult();
  537 + // 读取Excel文件
  538 + var excelData = await ReadExcelFile(file);
  539 + result.TotalCount = excelData.Count;
  540 +
  541 + foreach (var row in excelData)
  542 + {
  543 + try
  544 + {
  545 + var importError = new LqEventImportError
  546 + {
  547 + RowNumber = row.RowNumber,
  548 + MobilePhone = row.MobilePhone,
  549 + Name = row.Name,
  550 + TeamName = row.TeamName,
  551 + StoreName = row.StoreName,
  552 + TargetCount = row.TargetCount,
  553 + };
  554 +
  555 + // 验证必填字段
  556 + if (string.IsNullOrEmpty(row.MobilePhone))
  557 + {
  558 + importError.ErrorMessage = "员工手机号不能为空";
  559 + result.FailData.Add(importError);
  560 + result.FailCount++;
  561 + continue;
  562 + }
  563 +
  564 + // 通过手机号查找用户
  565 + var user = await _db.Queryable<UserEntity>().Where(u => u.MobilePhone == row.MobilePhone).FirstAsync();
  566 +
  567 + if (user == null)
  568 + {
  569 + importError.ErrorMessage = $"未找到手机号为 {row.MobilePhone} 的用户";
  570 + result.FailData.Add(importError);
  571 + result.FailCount++;
  572 + continue;
  573 + }
  574 +
  575 + // 获取用户组织ID(部门ID)
  576 + if (string.IsNullOrEmpty(user.OrganizeId))
  577 + {
  578 + importError.ErrorMessage = $"用户 {user.RealName} 没有关联的组织信息";
  579 + result.FailData.Add(importError);
  580 + result.FailCount++;
  581 + continue;
  582 + }
  583 +
  584 + // 查找门店信息
  585 + string storeId = null;
  586 + if (!string.IsNullOrEmpty(row.StoreName))
  587 + {
  588 + var store = await _db.Queryable<LqMdxxEntity>().Where(s => s.Dm == row.StoreName).FirstAsync();
  589 +
  590 + if (store == null)
  591 + {
  592 + importError.ErrorMessage = $"未找到名称为 {row.StoreName} 的门店";
  593 + result.FailData.Add(importError);
  594 + result.FailCount++;
  595 + continue;
  596 + }
  597 + storeId = store.Id;
  598 + }
  599 +
  600 + // 创建成功的数据
  601 + var successData = new NCC.Extend.Entitys.Dto.LqEventUser.LqEventUserCrInput
  602 + {
  603 + Id = YitIdHelper.NextId().ToString(),
  604 + EventId = eventId,
  605 + UserId = user.Id,
  606 + DepId = user.OrganizeId,
  607 + TeamName = row.TeamName,
  608 + StoreId = storeId,
  609 + };
  610 +
  611 + result.SuccessData.Add(successData);
  612 + result.SuccessCount++;
  613 + }
  614 + catch (Exception ex)
  615 + {
  616 + var importError = new LqEventImportError
  617 + {
  618 + RowNumber = row.RowNumber,
  619 + MobilePhone = row.MobilePhone,
  620 + Name = row.Name,
  621 + TeamName = row.TeamName,
  622 + StoreName = row.StoreName,
  623 + TargetCount = row.TargetCount,
  624 + ErrorMessage = $"处理数据时发生错误: {ex.Message}",
  625 + };
  626 + result.FailData.Add(importError);
  627 + result.FailCount++;
  628 + }
  629 + }
  630 +
  631 + return result;
  632 + }
  633 + catch (Exception ex)
  634 + {
  635 + throw NCCException.Oh($"导入拓客活动用户失败: {ex.Message}", ex);
  636 + }
  637 + }
  638 +
  639 + /// <summary>
  640 + /// 读取Excel文件数据
  641 + /// </summary>
  642 + /// <param name="file">Excel文件</param>
  643 + /// <returns>Excel数据列表</returns>
  644 + [NonAction]
  645 + private async Task<List<ExcelImportRow>> ReadExcelFile(IFormFile file)
  646 + {
  647 + var result = new List<ExcelImportRow>();
  648 +
  649 + // 保存临时文件
  650 + var tempFilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + Path.GetExtension(file.FileName));
  651 + try
  652 + {
  653 + using (var stream = new FileStream(tempFilePath, FileMode.Create))
  654 + {
  655 + await file.CopyToAsync(stream);
  656 + }
  657 +
  658 + // 使用ExcelImportHelper读取Excel文件
  659 + var dataTable = ExcelImportHelper.ToDataTable(tempFilePath, 0, 0);
  660 +
  661 + // 从第2行开始读取数据(第1行是标题)
  662 + for (int i = 1; i < dataTable.Rows.Count; i++)
  663 + {
  664 + var row = dataTable.Rows[i];
  665 + var mobilePhone = row[0]?.ToString()?.Trim();
  666 + var name = row[1]?.ToString()?.Trim();
  667 + var teamName = row[2]?.ToString()?.Trim();
  668 + var storeName = row[3]?.ToString()?.Trim();
  669 + var targetCountStr = row[4]?.ToString()?.Trim();
  670 +
  671 + int? targetCount = null;
  672 + if (!string.IsNullOrEmpty(targetCountStr) && int.TryParse(targetCountStr, out int target))
  673 + {
  674 + targetCount = target;
  675 + }
  676 +
  677 + // 跳过空行
  678 + if (string.IsNullOrEmpty(mobilePhone) && string.IsNullOrEmpty(name))
  679 + {
  680 + continue;
  681 + }
  682 +
  683 + result.Add(
  684 + new ExcelImportRow
  685 + {
  686 + RowNumber = i + 1, // Excel行号从1开始
  687 + MobilePhone = mobilePhone,
  688 + Name = name,
  689 + TeamName = teamName,
  690 + StoreName = storeName,
  691 + TargetCount = targetCount,
  692 + }
  693 + );
  694 + }
  695 + }
  696 + finally
  697 + {
  698 + // 清理临时文件
  699 + if (File.Exists(tempFilePath))
  700 + {
  701 + File.Delete(tempFilePath);
  702 + }
  703 + }
  704 +
  705 + return result;
  706 + }
  707 +
  708 + /// <summary>
  709 + /// Excel导入行数据
  710 + /// </summary>
  711 + private class ExcelImportRow
  712 + {
  713 + public int RowNumber { get; set; }
  714 + public string MobilePhone { get; set; }
  715 + public string Name { get; set; }
  716 + public string TeamName { get; set; }
  717 + public string StoreName { get; set; }
  718 + public int? TargetCount { get; set; }
  719 + }
  720 +
  721 + #endregion
485 } 722 }
486 } 723 }
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
@@ -131,6 +131,7 @@ namespace NCC.Extend.LqKdKdjlb @@ -131,6 +131,7 @@ namespace NCC.Extend.LqKdKdjlb
131 memberId = pxmx.MemberId, 131 memberId = pxmx.MemberId,
132 createTime = pxmx.CreateTIme, 132 createTime = pxmx.CreateTIme,
133 totalPrice = pxmx.TotalPrice, 133 totalPrice = pxmx.TotalPrice,
  134 + actualPrice = pxmx.ActualPrice,
134 }; 135 };
135 136
136 // 关联该品项的健康师业绩 137 // 关联该品项的健康师业绩
netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs
@@ -157,6 +157,13 @@ namespace NCC.Extend.LqKhxx @@ -157,6 +157,13 @@ namespace NCC.Extend.LqKhxx
157 } 157 }
158 var entity = input.Adapt<LqKhxxEntity>(); 158 var entity = input.Adapt<LqKhxxEntity>();
159 entity.Id = YitIdHelper.NextId().ToString(); 159 entity.Id = YitIdHelper.NextId().ToString();
  160 +
  161 + // 处理客户消费字段:将数组转换为逗号分隔的字符串
  162 + if (input.khxf != null && input.khxf.Count > 0)
  163 + {
  164 + entity.Khxf = string.Join(",", input.khxf);
  165 + }
  166 +
160 var isOk = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteCommandAsync(); 167 var isOk = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteCommandAsync();
161 if (!(isOk > 0)) 168 if (!(isOk > 0))
162 throw NCCException.Oh(ErrorCode.COM1000); 169 throw NCCException.Oh(ErrorCode.COM1000);
@@ -464,6 +471,13 @@ namespace NCC.Extend.LqKhxx @@ -464,6 +471,13 @@ namespace NCC.Extend.LqKhxx
464 public async Task Update(string id, [FromBody] LqKhxxUpInput input) 471 public async Task Update(string id, [FromBody] LqKhxxUpInput input)
465 { 472 {
466 var entity = input.Adapt<LqKhxxEntity>(); 473 var entity = input.Adapt<LqKhxxEntity>();
  474 +
  475 + // 处理客户消费字段:将数组转换为逗号分隔的字符串
  476 + if (input.khxf != null && input.khxf.Count > 0)
  477 + {
  478 + entity.Khxf = string.Join(",", input.khxf);
  479 + }
  480 +
467 var isOk = await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync(); 481 var isOk = await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync();
468 if (!(isOk > 0)) 482 if (!(isOk > 0))
469 throw NCCException.Oh(ErrorCode.COM1001); 483 throw NCCException.Oh(ErrorCode.COM1001);
netcore/src/Modularity/Extend/NCC.Extend/NCC.Extend.csproj
@@ -17,4 +17,8 @@ @@ -17,4 +17,8 @@
17 <ProjectReference Include="..\NCC.Extend.Interfaces\NCC.Extend.Interfaces.csproj" /> 17 <ProjectReference Include="..\NCC.Extend.Interfaces\NCC.Extend.Interfaces.csproj" />
18 </ItemGroup> 18 </ItemGroup>
19 19
  20 + <ItemGroup>
  21 + <PackageReference Include="EPPlus" Version="6.2.10" />
  22 + </ItemGroup>
  23 +
20 </Project> 24 </Project>
netcore/src/Modularity/System/NCC.System/Entitys/Dto/Base64ImageUploadInput.cs 0 → 100644
  1 +using System.ComponentModel.DataAnnotations;
  2 +
  3 +namespace NCC.System.Entitys.Dto
  4 +{
  5 + /// <summary>
  6 + /// Base64图片上传输入参数
  7 + /// </summary>
  8 + public class Base64ImageUploadInput
  9 + {
  10 + /// <summary>
  11 + /// Base64编码的图片数据
  12 + /// 支持格式:data:image/xxx;base64,xxxxx 或纯base64字符串
  13 + /// </summary>
  14 + [Required(ErrorMessage = "Base64数据不能为空")]
  15 + public string Base64Data { get; set; }
  16 +
  17 + /// <summary>
  18 + /// 图片类型(可选)
  19 + /// 支持的类型:userAvatar, document, temporary, weixin, workFlow, annex, annexpic, diskdocument, preview, screenShot, banner, bg, border, source, template, codeGenerator
  20 + /// 默认为 temporary
  21 + /// </summary>
  22 + public string ImageType { get; set; }
  23 +
  24 + /// <summary>
  25 + /// 文件名(可选,不包含扩展名)
  26 + /// 如果不提供,系统会自动生成
  27 + /// </summary>
  28 + public string FileName { get; set; }
  29 + }
  30 +}
netcore/src/Modularity/System/NCC.System/Service/Common/FileService.cs
1 -using NCC.Common.Configuration; 1 +using System;
  2 +using System.IO;
  3 +using System.Linq;
  4 +using System.Text;
  5 +using System.Text.RegularExpressions;
  6 +using System.Threading.Tasks;
  7 +using System.Web;
  8 +using Microsoft.AspNetCore.Authorization;
  9 +using Microsoft.AspNetCore.Http;
  10 +using Microsoft.AspNetCore.Mvc;
  11 +using Microsoft.Extensions.Configuration;
  12 +using NCC.Common.Configuration;
2 using NCC.Common.Core.Captcha.General; 13 using NCC.Common.Core.Captcha.General;
3 using NCC.Common.Core.Manager; 14 using NCC.Common.Core.Manager;
4 using NCC.Common.Enum; 15 using NCC.Common.Enum;
@@ -10,18 +21,9 @@ using NCC.DynamicApiController; @@ -10,18 +21,9 @@ using NCC.DynamicApiController;
10 using NCC.FriendlyException; 21 using NCC.FriendlyException;
11 using NCC.JsonSerialization; 22 using NCC.JsonSerialization;
12 using NCC.RemoteRequest.Extensions; 23 using NCC.RemoteRequest.Extensions;
  24 +using NCC.System.Entitys.Dto;
13 using NCC.System.Interfaces.Common; 25 using NCC.System.Interfaces.Common;
14 -using Microsoft.AspNetCore.Authorization;  
15 -using Microsoft.AspNetCore.Http;  
16 -using Microsoft.AspNetCore.Mvc;  
17 -using Microsoft.Extensions.Configuration;  
18 using OnceMi.AspNetCore.OSS; 26 using OnceMi.AspNetCore.OSS;
19 -using System;  
20 -using System.IO;  
21 -using System.Linq;  
22 -using System.Text;  
23 -using System.Threading.Tasks;  
24 -using System.Web;  
25 using Yitter.IdGenerator; 27 using Yitter.IdGenerator;
26 28
27 namespace NCC.System.Service.Common 29 namespace NCC.System.Service.Common
@@ -33,7 +35,7 @@ namespace NCC.System.Service.Common @@ -33,7 +35,7 @@ namespace NCC.System.Service.Common
33 [Route("api/[controller]")] 35 [Route("api/[controller]")]
34 public class FileService : IFileService, IDynamicApiController, ITransient 36 public class FileService : IFileService, IDynamicApiController, ITransient
35 { 37 {
36 - private readonly IGeneralCaptcha _captchaHandle;// 验证码服务 38 + private readonly IGeneralCaptcha _captchaHandle; // 验证码服务
37 private readonly IConfiguration _configuration; 39 private readonly IConfiguration _configuration;
38 private readonly IUserManager _userManager; 40 private readonly IUserManager _userManager;
39 private readonly IOSSServiceFactory _oSSServiceFactory; 41 private readonly IOSSServiceFactory _oSSServiceFactory;
@@ -177,7 +179,8 @@ namespace NCC.System.Service.Common @@ -177,7 +179,8 @@ namespace NCC.System.Service.Common
177 var fileDownloadName = exname; 179 var fileDownloadName = exname;
178 if (fileDownloadName.IsNullOrWhiteSpace() || fileDownloadName.Split('.').Length < 2) 180 if (fileDownloadName.IsNullOrWhiteSpace() || fileDownloadName.Split('.').Length < 2)
179 fileDownloadName = Path.GetFileName(filePath); 181 fileDownloadName = Path.GetFileName(filePath);
180 - if (fileDownloadName.IsNullOrWhiteSpace()) fileDownloadName = fileName.Replace(GetPathByType(type), ""); 182 + if (fileDownloadName.IsNullOrWhiteSpace())
  183 + fileDownloadName = fileName.Replace(GetPathByType(type), "");
181 return await DownloadFileByType(filePath, fileDownloadName); 184 return await DownloadFileByType(filePath, fileDownloadName);
182 } 185 }
183 else 186 else
@@ -282,6 +285,8 @@ namespace NCC.System.Service.Common @@ -282,6 +285,8 @@ namespace NCC.System.Service.Common
282 { 285 {
283 case "userAvatar": 286 case "userAvatar":
284 return FileVariable.UserAvatarFilePath; 287 return FileVariable.UserAvatarFilePath;
  288 + case "userSignature":
  289 + return FileVariable.UserSignatureFilePath;
285 case "mail": 290 case "mail":
286 return FileVariable.EmailFilePath; 291 return FileVariable.EmailFilePath;
287 case "IM": 292 case "IM":
@@ -352,10 +357,6 @@ namespace NCC.System.Service.Common @@ -352,10 +357,6 @@ namespace NCC.System.Service.Common
352 return false; 357 return false;
353 } 358 }
354 359
355 -  
356 -  
357 -  
358 -  
359 #region 导入导出 360 #region 导入导出
360 361
361 /// <summary> 362 /// <summary>
@@ -374,11 +375,7 @@ namespace NCC.System.Service.Common @@ -374,11 +375,7 @@ namespace NCC.System.Service.Common
374 var byteList = new UTF8Encoding(true).GetBytes(jsonStr.ToCharArray()); 375 var byteList = new UTF8Encoding(true).GetBytes(jsonStr.ToCharArray());
375 FileHelper.CreateFile(_filePath + _fileName, byteList); 376 FileHelper.CreateFile(_filePath + _fileName, byteList);
376 var fileName = _userManager.UserId + "|" + _filePath + _fileName + "|json"; 377 var fileName = _userManager.UserId + "|" + _filePath + _fileName + "|json";
377 - var output = new  
378 - {  
379 - name = _fileName,  
380 - url = "/api/file/Download?encryption=" + DESCEncryption.Encrypt(fileName, "NCC")  
381 - }; 378 + var output = new { name = _fileName, url = "/api/file/Download?encryption=" + DESCEncryption.Encrypt(fileName, "NCC") };
382 return output; 379 return output;
383 } 380 }
384 381
@@ -422,7 +419,6 @@ namespace NCC.System.Service.Common @@ -422,7 +419,6 @@ namespace NCC.System.Service.Common
422 } 419 }
423 } 420 }
424 421
425 -  
426 /// <summary> 422 /// <summary>
427 /// 上传文件 423 /// 上传文件
428 /// </summary> 424 /// </summary>
@@ -446,5 +442,192 @@ namespace NCC.System.Service.Common @@ -446,5 +442,192 @@ namespace NCC.System.Service.Common
446 } 442 }
447 } 443 }
448 #endregion 444 #endregion
  445 +
  446 + #region Base64图片上传
  447 +
  448 + /// <summary>
  449 + /// 上传Base64格式图片(主要用于用户签名信息)
  450 + /// </summary>
  451 + /// <remarks>
  452 + /// 接收前端传入的base64格式图片数据,解码后保存到服务器并返回访问路径。
  453 + /// 支持常见的图片格式(JPG、PNG、GIF、BMP等),主要用于保存用户签名信息。
  454 + ///
  455 + /// 示例请求:
  456 + /// POST /api/File/UploadBase64Image
  457 + /// Content-Type: application/json
  458 + ///
  459 + /// ```json
  460 + /// {
  461 + /// "base64Data": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD...",
  462 + /// "imageType": "userSignature",
  463 + /// "fileName": "signature"
  464 + /// }
  465 + /// ```
  466 + ///
  467 + /// 参数说明:
  468 + /// - base64Data: 必填,base64编码的图片数据(支持data:image/xxx;base64,格式)
  469 + /// - imageType: 可选,图片类型(userSignature用于用户签名,默认为temporary)
  470 + /// - fileName: 可选,文件名(不包含扩展名,系统会自动添加)
  471 + ///
  472 + /// 支持的图片类型:
  473 + /// - userSignature: 用户签名(推荐)
  474 + /// - userAvatar: 用户头像
  475 + /// - document: 文档
  476 + /// - temporary: 临时文件(默认)
  477 + /// - 其他系统支持的图片类型
  478 + ///
  479 + /// 返回数据:
  480 + /// - name: 保存的文件名
  481 + /// - url: 图片访问路径
  482 + /// - fileSize: 文件大小(字节)
  483 + /// - imageFormat: 图片格式
  484 + /// - imageType: 图片类型
  485 + /// </remarks>
  486 + /// <param name="input">Base64图片上传参数</param>
  487 + /// <returns>图片上传结果,包含文件名和访问路径</returns>
  488 + /// <response code="200">图片上传成功</response>
  489 + /// <response code="400">请求参数错误或图片格式不支持</response>
  490 + /// <response code="500">服务器内部错误</response>
  491 + [HttpPost("UploadBase64Image")]
  492 + [AllowAnonymous]
  493 + public async Task<dynamic> UploadBase64Image([FromBody] Base64ImageUploadInput input)
  494 + {
  495 + try
  496 + {
  497 + // 验证输入参数
  498 + if (string.IsNullOrEmpty(input.Base64Data))
  499 + {
  500 + throw NCCException.Oh("Base64数据不能为空");
  501 + }
  502 +
  503 + // 解析Base64数据
  504 + var imageData = ParseBase64Data(input.Base64Data, out string imageFormat);
  505 +
  506 + // 验证图片格式
  507 + if (!IsValidImageFormat(imageFormat))
  508 + {
  509 + throw NCCException.Oh($"不支持的图片格式: {imageFormat}");
  510 + }
  511 +
  512 + // 生成文件名
  513 + var fileName = GenerateImageFileName(input.FileName, imageFormat);
  514 +
  515 + // 获取存储路径
  516 + var imageType = string.IsNullOrEmpty(input.ImageType) ? "temporary" : input.ImageType;
  517 + var filePath = GetPathByType(imageType);
  518 +
  519 + // 确保目录存在
  520 + if (!Directory.Exists(filePath))
  521 + {
  522 + Directory.CreateDirectory(filePath);
  523 + }
  524 +
  525 + // 保存图片文件
  526 + var fullPath = Path.Combine(filePath, fileName);
  527 + await File.WriteAllBytesAsync(fullPath, imageData);
  528 +
  529 + // 生成访问URL
  530 + var accessUrl = $"/api/File/Image/{imageType}/{fileName}";
  531 +
  532 + return new
  533 + {
  534 + name = fileName,
  535 + url = accessUrl,
  536 + fileSize = imageData.Length,
  537 + imageFormat = imageFormat.ToUpper(),
  538 + imageType = imageType,
  539 + };
  540 + }
  541 + catch (Exception ex)
  542 + {
  543 + throw NCCException.Oh($"Base64图片上传失败: {ex.Message}", ex);
  544 + }
  545 + }
  546 +
  547 + /// <summary>
  548 + /// 解析Base64数据并提取图片格式
  549 + /// </summary>
  550 + /// <param name="base64Data">Base64数据</param>
  551 + /// <param name="imageFormat">输出图片格式</param>
  552 + /// <returns>解码后的图片字节数组</returns>
  553 + [NonAction]
  554 + private byte[] ParseBase64Data(string base64Data, out string imageFormat)
  555 + {
  556 + try
  557 + {
  558 + // 处理data:image/xxx;base64,格式
  559 + if (base64Data.StartsWith("data:image/"))
  560 + {
  561 + var parts = base64Data.Split(',');
  562 + if (parts.Length != 2)
  563 + {
  564 + throw new ArgumentException("Base64数据格式不正确");
  565 + }
  566 +
  567 + // 提取图片格式
  568 + var header = parts[0];
  569 + var formatMatch = Regex.Match(header, @"data:image/([^;]+)");
  570 + if (formatMatch.Success)
  571 + {
  572 + imageFormat = formatMatch.Groups[1].Value.ToLower();
  573 + }
  574 + else
  575 + {
  576 + imageFormat = "jpeg"; // 默认格式
  577 + }
  578 +
  579 + // 解码Base64数据
  580 + return Convert.FromBase64String(parts[1]);
  581 + }
  582 + else
  583 + {
  584 + // 纯Base64数据,默认为JPEG格式
  585 + imageFormat = "jpeg";
  586 + return Convert.FromBase64String(base64Data);
  587 + }
  588 + }
  589 + catch (Exception ex)
  590 + {
  591 + throw new ArgumentException($"Base64数据解析失败: {ex.Message}", ex);
  592 + }
  593 + }
  594 +
  595 + /// <summary>
  596 + /// 验证图片格式是否支持
  597 + /// </summary>
  598 + /// <param name="imageFormat">图片格式</param>
  599 + /// <returns>是否支持</returns>
  600 + [NonAction]
  601 + private bool IsValidImageFormat(string imageFormat)
  602 + {
  603 + var allowedFormats = new[] { "jpg", "jpeg", "png", "gif", "bmp", "tiff", "webp" };
  604 + return allowedFormats.Contains(imageFormat.ToLower());
  605 + }
  606 +
  607 + /// <summary>
  608 + /// 生成图片文件名
  609 + /// </summary>
  610 + /// <param name="inputFileName">输入的文件名</param>
  611 + /// <param name="imageFormat">图片格式</param>
  612 + /// <returns>生成的文件名</returns>
  613 + [NonAction]
  614 + private string GenerateImageFileName(string inputFileName, string imageFormat)
  615 + {
  616 + var timestamp = DateTime.Now.ToString("yyyyMMddHHmmss");
  617 + var id = YitIdHelper.NextId().ToString();
  618 +
  619 + if (!string.IsNullOrEmpty(inputFileName))
  620 + {
  621 + // 移除可能存在的扩展名
  622 + var nameWithoutExt = Path.GetFileNameWithoutExtension(inputFileName);
  623 + return $"{nameWithoutExt}_{timestamp}_{id}.{imageFormat}";
  624 + }
  625 + else
  626 + {
  627 + return $"image_{timestamp}_{id}.{imageFormat}";
  628 + }
  629 + }
  630 +
  631 + #endregion
449 } 632 }
450 } 633 }
拓客活动用户导入模板.csv 0 → 100644
  1 +员工手机号,姓名,战队,门店,目标张数
  2 +13800138001,张三,第一战队,北京旗舰店,50
  3 +13800138002,李四,第二战队,上海分店,30
  4 +13800138003,王五,,深圳分店,40
  5 +13800138004,赵六,第三战队,,25