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 4 namespace NCC.Common.Configuration
5 5 {
... ... @@ -73,5 +73,10 @@ namespace NCC.Common.Configuration
73 73 /// 模板路径
74 74 /// </summary>
75 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 33 /// 战队名称
34 34 /// </summary>
35 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 12 /// 明细编号
13 13 /// </summary>
14 14 public string id { get; set; }
15   -
  15 +
16 16 /// <summary>
17 17 /// 关联开单编号
18 18 /// </summary>
19 19 public string glkdbh { get; set; }
20   -
  20 +
21 21 /// <summary>
22 22 /// 品项编号
23 23 /// </summary>
24 24 public string px { get; set; }
25   -
  25 +
26 26 /// <summary>
27 27 /// 品项名称
28 28 /// </summary>
29 29 public string pxmc { get; set; }
30   -
  30 +
31 31 /// <summary>
32 32 /// 品项价格
33 33 /// </summary>
34 34 public decimal pxjg { get; set; }
35   -
  35 +
36 36 /// <summary>
37 37 /// 项目次数
38 38 /// </summary>
39 39 public int? projectNumber { get; set; }
40   -
  40 +
41 41 /// <summary>
42 42 /// 是否有效
43 43 /// </summary>
44 44 public int? isEnabled { get; set; }
45   -
  45 +
46 46 /// <summary>
47 47 /// 来源类型(开卡/赠送/其他)
48 48 /// </summary>
49 49 public string sourceType { get; set; }
50   -
  50 +
51 51 /// <summary>
52 52 /// 会员ID
53 53 /// </summary>
54 54 public string memberId { get; set; }
55   -
  55 +
56 56 /// <summary>
57 57 /// 创建时间
58 58 /// </summary>
59 59 public DateTime? createTime { get; set; }
60   -
  60 +
61 61 /// <summary>
62 62 /// 总价格(品项价格 × 项目次数)
63 63 /// </summary>
64 64 public decimal totalPrice { get; set; }
65   -
  65 +
  66 + /// <summary>
  67 + /// 实际价格
  68 + /// </summary>
  69 + public decimal actualPrice { get; set; }
  70 +
66 71 /// <summary>
67 72 /// 健康师业绩列表
68 73 /// </summary>
69 74 public List<LqKdJksyjInfoOutput> lqKdJksyjList { get; set; }
70   -
  75 +
71 76 /// <summary>
72 77 /// 科技部老师业绩列表
73 78 /// </summary>
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxCrInput.cs
... ... @@ -81,7 +81,7 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
81 81 /// <summary>
82 82 /// 客户消费
83 83 /// </summary>
84   - public string khxf { get; set; }
  84 + public List<string> khxf { get; set; }
85 85  
86 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 56 /// </summary>
57 57 [SugarColumn(ColumnName = "F_EventTarget")]
58 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 1 using System;
2 2 using System.Collections.Generic;
  3 +using System.IO;
3 4 using System.Linq;
4 5 using System.Threading.Tasks;
5 6 using Mapster;
  7 +using Microsoft.AspNetCore.Http;
6 8 using Microsoft.AspNetCore.Mvc;
7 9 using NCC.Common.Core.Manager;
8 10 using NCC.Common.Filter;
  11 +using NCC.Common.Helper;
9 12 using NCC.Dependency;
10 13 using NCC.DynamicApiController;
11 14 using NCC.Extend.Entitys.Dto.LqEvent;
12 15 using NCC.Extend.Entitys.Dto.LqEventUser;
13 16 using NCC.Extend.Entitys.lq_event;
14 17 using NCC.Extend.Entitys.lq_eventuser;
  18 +using NCC.Extend.Entitys.lq_mdxx;
15 19 using NCC.Extend.Interfaces.LqEvent;
16 20 using NCC.FriendlyException;
  21 +using NCC.System.Entitys.Permission;
17 22 using SqlSugar;
18 23 using Yitter.IdGenerator;
19 24  
... ... @@ -219,6 +224,7 @@ namespace NCC.Extend.LqEvent
219 224 TeamName = member.TeamName,
220 225 CreationTime = DateTime.Now,
221 226 CreationUser = userInfo?.userName,
  227 + StoreId = member.StoreId,
222 228 }
223 229 );
224 230 }
... ... @@ -306,6 +312,7 @@ namespace NCC.Extend.LqEvent
306 312 TeamName = member.TeamName,
307 313 CreationTime = DateTime.Now,
308 314 CreationUser = userInfo?.userName,
  315 + StoreId = member.StoreId,
309 316 }
310 317 );
311 318 }
... ... @@ -482,5 +489,235 @@ namespace NCC.Extend.LqEvent
482 489 }
483 490 }
484 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 131 memberId = pxmx.MemberId,
132 132 createTime = pxmx.CreateTIme,
133 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 157 }
158 158 var entity = input.Adapt<LqKhxxEntity>();
159 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 167 var isOk = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteCommandAsync();
161 168 if (!(isOk > 0))
162 169 throw NCCException.Oh(ErrorCode.COM1000);
... ... @@ -464,6 +471,13 @@ namespace NCC.Extend.LqKhxx
464 471 public async Task Update(string id, [FromBody] LqKhxxUpInput input)
465 472 {
466 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 481 var isOk = await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync();
468 482 if (!(isOk > 0))
469 483 throw NCCException.Oh(ErrorCode.COM1001);
... ...
netcore/src/Modularity/Extend/NCC.Extend/NCC.Extend.csproj
... ... @@ -17,4 +17,8 @@
17 17 <ProjectReference Include="..\NCC.Extend.Interfaces\NCC.Extend.Interfaces.csproj" />
18 18 </ItemGroup>
19 19  
  20 + <ItemGroup>
  21 + <PackageReference Include="EPPlus" Version="6.2.10" />
  22 + </ItemGroup>
  23 +
20 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 13 using NCC.Common.Core.Captcha.General;
3 14 using NCC.Common.Core.Manager;
4 15 using NCC.Common.Enum;
... ... @@ -10,18 +21,9 @@ using NCC.DynamicApiController;
10 21 using NCC.FriendlyException;
11 22 using NCC.JsonSerialization;
12 23 using NCC.RemoteRequest.Extensions;
  24 +using NCC.System.Entitys.Dto;
13 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 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 27 using Yitter.IdGenerator;
26 28  
27 29 namespace NCC.System.Service.Common
... ... @@ -33,7 +35,7 @@ namespace NCC.System.Service.Common
33 35 [Route("api/[controller]")]
34 36 public class FileService : IFileService, IDynamicApiController, ITransient
35 37 {
36   - private readonly IGeneralCaptcha _captchaHandle;// 验证码服务
  38 + private readonly IGeneralCaptcha _captchaHandle; // 验证码服务
37 39 private readonly IConfiguration _configuration;
38 40 private readonly IUserManager _userManager;
39 41 private readonly IOSSServiceFactory _oSSServiceFactory;
... ... @@ -177,7 +179,8 @@ namespace NCC.System.Service.Common
177 179 var fileDownloadName = exname;
178 180 if (fileDownloadName.IsNullOrWhiteSpace() || fileDownloadName.Split('.').Length < 2)
179 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 184 return await DownloadFileByType(filePath, fileDownloadName);
182 185 }
183 186 else
... ... @@ -282,6 +285,8 @@ namespace NCC.System.Service.Common
282 285 {
283 286 case "userAvatar":
284 287 return FileVariable.UserAvatarFilePath;
  288 + case "userSignature":
  289 + return FileVariable.UserSignatureFilePath;
285 290 case "mail":
286 291 return FileVariable.EmailFilePath;
287 292 case "IM":
... ... @@ -352,10 +357,6 @@ namespace NCC.System.Service.Common
352 357 return false;
353 358 }
354 359  
355   -
356   -
357   -
358   -
359 360 #region 导入导出
360 361  
361 362 /// <summary>
... ... @@ -374,11 +375,7 @@ namespace NCC.System.Service.Common
374 375 var byteList = new UTF8Encoding(true).GetBytes(jsonStr.ToCharArray());
375 376 FileHelper.CreateFile(_filePath + _fileName, byteList);
376 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 379 return output;
383 380 }
384 381  
... ... @@ -422,7 +419,6 @@ namespace NCC.System.Service.Common
422 419 }
423 420 }
424 421  
425   -
426 422 /// <summary>
427 423 /// 上传文件
428 424 /// </summary>
... ... @@ -446,5 +442,192 @@ namespace NCC.System.Service.Common
446 442 }
447 443 }
448 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
... ...