Lyyyi 10 小时之前
父节点
当前提交
51c3d57d1d

+ 306 - 28
OASystem/OASystem.Api/Controllers/GroupsController.cs

@@ -4171,7 +4171,7 @@ FROM
                         return Ok(JsonView(false, "公司信息添加失败!"));
                     }
                 }
-                else 
+                else
                 {
                     // 存在信息为空则更新公司地址信息
                     if (string.IsNullOrEmpty(compnayInfo.Address))
@@ -13777,20 +13777,44 @@ FROM
                 return Ok(jw);
             }
 
-            var clients = await _sqlSugar.Queryable<Grp_TourClientList>()
-                           .Where(x => x.IsDel == 0 && x.DiId == dto.Diid)
-                           .ToListAsync();
+            var DeleClientList = _sqlSugar.Queryable<Grp_TourClientList>()
+                     .LeftJoin<Crm_DeleClient>((tcl, dc) => tcl.ClientId == dc.Id && dc.IsDel == 0)
+                     .LeftJoin<Crm_CustomerCompany>((tcl, dc, cc) => dc.CrmCompanyId == cc.Id && dc.IsDel == 0)
+                     .Where((tcl, dc, cc) => tcl.IsDel == 0 && tcl.DiId == dto.Diid)
+                     .Select((tcl, dc, cc) => new ClientInfo
+                     {
+                         LastName = dc.LastName,
+                         FirstName = dc.FirstName,
+                         Sex = dc.Sex,
+                         Birthday = dc.BirthDay,
+                         Company = cc.CompanyFullName,
+                         Job = dc.Job
+                     })
+                     .ToList();
+            foreach (var item in DeleClientList)
+            {
+                EncryptionProcessor.DecryptProperties(item);
+            }
 
-            if (!clients.Any())
+            if (!DeleClientList.Any())
             {
                 jw.Msg = "团组客户信息不存在!";
                 return Ok(jw);
             }
 
-            var TravelList = await _sqlSugar.Queryable<Grp_ApprovalTravel>()
-            .Where(x => x.IsDel == 0 && x.Diid == dto.Diid).ToListAsync();
+            var travelList = await _sqlSugar.Queryable<Grp_ApprovalTravel>()
+              .LeftJoin<Grp_ApprovalTravelDetails>((x, a) => x.Id == a.ParentId && a.IsDel == 0)
+              .Where((x, a) => x.IsDel == 0 && x.Diid == dto.Diid)
+              .Select((x, a) => new
+              {
+                  日期 = x.Date,
+                  具体时间 = a.Time,
+                  行程安排 = a.Details,
+                  数据id = a.Id,
+              })
+              .ToListAsync();
 
-            if (!TravelList.Any())
+            if (!travelList.Any())
             {
                 jw.Msg = "团组行程信息不存在!";
                 return Ok(jw);
@@ -13805,8 +13829,8 @@ FROM
                 {
                     FormatTemplate = templateSetting.Remark,
                     Parameters = new List<string> {
-                            JsonConvert.SerializeObject(clients),
-                             JsonConvert.SerializeObject(TravelList)
+                            JsonConvert.SerializeObject(DeleClientList),
+                             JsonConvert.SerializeObject(travelList)
                         }
                 });
 
@@ -13826,9 +13850,10 @@ FROM
                 return Ok(jw);
             }
 
+            Console.WriteLine(chat);
 
             // var chatResult = await _doubaoService.CompleteChatAsync(new List<DouBaoChatMessage> { new DouBaoChatMessage { Role = DouBaoRole.user, Content = chat } });
-            var apiResp = await _deepSeekService.ChatAsync(chat);
+            var apiResp = await _deepSeekService.ChatAsync(chat, false, "deepseek-reasoner", 0.7f, 60000);
             var chatResult = apiResp.Answer;
 
             if (string.IsNullOrEmpty(chatResult))
@@ -13837,9 +13862,30 @@ FROM
                 return Ok(jw);
             }
 
-            return Ok(JsonView(true, "请示文件生成成功!", chatResult));
+            string outputPath = $"{AppSettingsHelper.Get("GrpFileBasePath")}/商邀相关文件/{groupInfo.TeamName}请示文件.docx";
+
+            string json = chatResult;
+            byte[] wordBytes = JsonToWordHelper.ConvertJsonToWord(json);
+            System.IO.File.WriteAllBytes(outputPath, wordBytes);
+
+            //WordExporter.MarkdownToWord(chatResult, outputPath);
+            string url = outputPath.Replace(AppSettingsHelper.Get("GrpFileBasePath"), AppSettingsHelper.Get("GrpFileBaseUrl") + AppSettingsHelper.Get("GrpFileFtpPath"));
+
+            return Ok(JsonView(true, "请示文件生成成功!", url));
         }
 
+
+        // [HttpPost("export-word")]
+        // public IActionResult ExportWord([FromBody] string json)
+        // {
+        //     var bytes = JsonToWordHelper.ConvertJsonToWord(json);
+
+        //     return File(
+        //         bytes,
+        //         "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+        //         "出访请示.docx");
+        // }
+
         /// <summary>
         /// 企业拜访内容续写
         /// </summary>
@@ -13851,33 +13897,101 @@ FROM
         {
             var jw = JsonView(false);
 
-            if (string.IsNullOrEmpty(dto.DepartEnterpriseName))
+            var di = await _sqlSugar.Queryable<Grp_DelegationInfo>()
+            .Where(x => x.IsDel == 0 && x.Id == dto.Diid)
+            .FirstAsync();
+
+            var resultArr = new ArrayList();
+            var content = _sqlSugar.Queryable<Grp_ApprovalTravel>().Where(x => x.Diid == dto.Diid && x.IsDel == 0).ToList();
+            const int chiNumber = 5;
+
+            if (!content.Any())
             {
-                jw.Msg = "出访企业名称不能为空!";
+                jw.Msg = "团组行程信息不存在!";
                 return Ok(jw);
             }
 
-            if (dto.EnterpriseNames == null || dto.EnterpriseNames.Count == 0)
+            foreach (var x in content)
             {
-                jw.Msg = "企业名称列表不能为空!";
-                return Ok(jw);
-            }
+                var chiList = _sqlSugar.Queryable<Grp_ApprovalTravelDetails>().Where(x1 => x1.ParentId == x.Id && x1.IsDel == 0).ToList();
+                if (chiList.Count < chiNumber)
+                {
+                    for (int i = chiList.Count; i < chiNumber; i++)
+                    {
+                        chiList.Add(new Grp_ApprovalTravelDetails());
+                    }
+                }
 
-            ArrayList result = new ArrayList();
+                resultArr.Add(new
+                {
+                    x.Id,
+                    x.Date,
+                    x.Diid,
+                    chiList = chiList.Select(x1 => new
+                    {
+                        timeInterval = string.IsNullOrWhiteSpace(x1.Time) ? new string[1] : x1.Time.Split('-'),
+                        x1.Details,
+                        x1.ParentId,
+                        x1.Id
+                    })
+                });
+            }
 
-            foreach (var enterpriseName in dto.EnterpriseNames)
+            string chat = string.Empty;
+            var templateSetting = await _sqlSugar.Queryable<Sys_SetData>().FirstAsync(x => x.Id == 1554 && x.IsDel == 0);
+            if (templateSetting != null && !string.IsNullOrEmpty(templateSetting.Remark))
             {
-                var kimiResult = await this.ApprovalJourneyAiWrite(new ApprovalJourneyAiWriteDto { ClientName = dto.DepartEnterpriseName, ClientPurpose = enterpriseName });
-                result.Add(new
+                var stringFormatResp = await GeneralMethod.StringFormatAsync(new StringFormatDto
                 {
-                    EnterpriseName = enterpriseName,
-                    Content = kimiResult
+                    FormatTemplate = templateSetting.Remark,
+                    Parameters = new List<string> {
+                            JsonConvert.SerializeObject(resultArr),
+                            di.ClientName
+                        }
                 });
+
+                if (stringFormatResp.Success)
+                {
+                    chat = stringFormatResp.FormattedResult;
+                }
+                else
+                {
+                    jw.Msg = "对话内容模板格式化失败! templateSetting.Id: " + templateSetting.Id + ",错误:" + stringFormatResp.Message;
+                    return Ok(jw);
+                }
+            }
+            else
+            {
+                jw.Msg = "获取对话内容模板失败! templateSetting.Id: " + templateSetting.Id;
+                return Ok(jw);
+            }
+
+
+            var apiResult = await _deepSeekService.ChatAsync(chat, false, "deepseek-reasoner", 0.7f, 60000);
+            var chatResult = apiResult.Answer;
+
+            if (string.IsNullOrEmpty(chatResult))
+            {
+                jw.Msg = "企业拜访内容续写失败!";
+                return Ok(jw);
             }
 
-            return Ok(JsonView(true, "企业拜访内容续写成功!", result));
+            chatResult = GeneralMethod.ExtractJson(chatResult);
+            var JsonResult = JsonDocument.Parse(chatResult);
+
+            return Ok(JsonView(true, "企业拜访内容续写成功!", new
+            {
+                chatResult,
+                JsonResult
+            }));
         }
 
+        // [HttpPost]
+        // public object ToOjbect(ToOjbectDto dto)
+        // {
+        //     return JsonDocument.Parse(dto.Value);
+        // }
+
         /// <summary>
         /// 行程单位安排
         /// </summary>
@@ -13943,7 +14057,7 @@ FROM
             }
 
             //var chatResult = await _doubaoService.CompleteChatAsync(new List<DouBaoChatMessage> { new DouBaoChatMessage { Role = DouBaoRole.user, Content = chat } });
-            var apiResp = await _deepSeekService.ChatAsync(chat);
+            var apiResp = await _deepSeekService.ChatAsync(chat, false, "deepseek-reasoner", 0.7f, 20000);
             var chatResult = apiResp.Answer;
 
             if (string.IsNullOrEmpty(chatResult))
@@ -13981,6 +14095,13 @@ FROM
         {
             var jw = JsonView(false);
 
+            var Find = _sqlSugar.Queryable<Grp_DelegationInfo>().First(x => x.Id == dto.Diid);
+            if (Find == null)
+            {
+                jw.Msg = "请选择正确的团组!";
+                return Ok(jw);
+            }
+
             var travelList = await _sqlSugar.Queryable<Grp_ApprovalTravel>()
             .LeftJoin<Grp_ApprovalTravelDetails>((x, a) => x.Id == a.ParentId && a.IsDel == 0)
             .Where((x, a) => x.IsDel == 0 && x.Diid == dto.Diid)
@@ -14059,7 +14180,7 @@ FROM
             }
 
             // var chatResult = await _doubaoService.CompleteChatAsync(new List<DouBaoChatMessage> { new DouBaoChatMessage { Role = DouBaoRole.user, Content = chat } });
-            var apiResp = await _deepSeekService.ChatAsync(chat);
+            var apiResp = await _deepSeekService.ChatAsync(chat, false, "deepseek-reasoner", 0.7f, 40000);
             var chatResult = apiResp.Answer;
 
             if (string.IsNullOrEmpty(chatResult))
@@ -14068,7 +14189,164 @@ FROM
                 return Ok(jw);
             }
 
-            return Ok(JsonView(true, "行程框架导出成功!", chatResult));
+            chatResult = GeneralMethod.ExtractJson(chatResult);
+            var _travelList = new List<TravelPlan>();
+
+            try
+            {
+                var jsonResult = JsonConvert.DeserializeObject<List<TravelPlan>>(chatResult);
+                _travelList = jsonResult;
+            }
+            catch (Exception ex)
+            {
+                jw.Msg = "AI行程框架导出失败!(JSON转换失败!)";
+                return Ok(jw);
+            }
+
+            //创建数据源Table
+            var dtSource = new DataTable();
+            dtSource.Columns.Add("Days", typeof(string));
+            dtSource.Columns.Add("Date", typeof(string));
+            dtSource.Columns.Add("Week", typeof(string));
+            dtSource.Columns.Add("Traffic", typeof(string));
+            dtSource.Columns.Add("Trip", typeof(string));
+
+            var days = 1;
+            //获取数据,放到datatable
+            foreach (var item in _travelList)
+            {
+
+                DataRow dr = dtSource.NewRow();
+                dr["Days"] = days;
+                dr["Date"] = item.Date;
+                dr["Week"] = item.WeekDay;
+                dr["Traffic"] = string.Join("\r\n", item.Traffic);
+                dr["Trip"] = item.Trip;
+                dtSource.Rows.Add(dr);
+            }
+
+            var dic = new Dictionary<string, string>
+            {
+                { "Dele", Find.TeamName.ToString() + GetNum(Find.VisitDays.ToString()) },
+                { "City", GeneralMethod.GetGroupCityLine(dto.Diid, "/") },
+                { "Days", Find.VisitDays.ToString() },
+                { "DeleCode", Find.TourCode },
+                { "Pnum", Find.VisitPNumber.ToString() }
+            };
+
+            //模板路径
+            string tempPath = AppSettingsHelper.Get("WordBasePath") + "Travel/日行程3.docx";
+
+            //载入模板
+            Document doc = null;
+            DocumentBuilder builder = null;
+            try
+            {
+                //载入模板
+                doc = new Document(tempPath);
+                builder = new DocumentBuilder(doc);
+            }
+            catch (Exception)
+            {
+                jw.Msg = "模板位置不存在!";
+                return Ok(jw);
+            }
+
+            foreach (var key in dic.Keys)
+            {
+                Bookmark bookmark = doc.Range.Bookmarks[key];
+                if (bookmark != null)
+                {
+                    builder.MoveToBookmark(key);
+                    builder.Write(dic[key]);
+                }
+            }
+
+            //获取word里所有表格
+            NodeCollection allTables = doc.GetChildNodes(NodeType.Table, true);
+            //获取所填表格的序数
+            Aspose.Words.Tables.Table tableOne = allTables[0] as Aspose.Words.Tables.Table;
+
+            try
+            {
+                //循环赋值
+                for (int i = 0; i < dtSource.Rows.Count; i++)
+                {
+                    builder.MoveToCell(0, i + 1, 0, 0);
+                    builder.Write(dtSource.Rows[i]["Days"].ToString());
+
+                    builder.MoveToCell(0, i + 1, 1, 0);
+                    builder.Write(dtSource.Rows[i]["Date"].ToString() + "\r\n" + dtSource.Rows[i]["Week"].ToString());
+
+                    builder.MoveToCell(0, i + 1, 2, 0);
+                    builder.Write(dtSource.Rows[i]["Traffic"].ToString());
+
+                    var trip = dtSource.Rows[i]["Trip"].ToString();
+                    builder.MoveToCell(0, i + 1, 3, 0);
+                    builder.Write(trip);
+
+                    var cell = (Aspose.Words.Tables.Cell)doc.GetChild(NodeType.Cell, ((i + 2) * 4) - 1, true);
+                    var paragraphs = cell.GetChildNodes(NodeType.Paragraph, true);
+
+                    // 获取特定索引的段落
+                    Paragraph paragraph = (Paragraph)paragraphs[0];
+                    Run run = paragraph.Runs[0];
+                    Aspose.Words.Font font = run.Font;
+                    font.Name = "黑体";
+
+                    //设置双休红色
+                    cell = (Aspose.Words.Tables.Cell)doc.GetChild(NodeType.Cell, ((i + 2) * 4) - 3, true);
+                    paragraphs = cell.GetChildNodes(NodeType.Paragraph, true);
+                    paragraph = (Paragraph)paragraphs[1];
+                    if (paragraph.GetText().Contains("星期六") || paragraph.GetText().Contains("星期日"))
+                    {
+                        run = paragraph.Runs[0];
+                        font = run.Font;
+                        font.Color = Color.Red;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+
+            }
+
+            //删除多余行
+            while (tableOne.Rows.Count > 1 + dtSource.Rows.Count)
+            {
+                tableOne.Rows.RemoveAt(1 + dtSource.Rows.Count);//(1+dtSource.Rows.Count + 1)-1
+            }
+
+            string savePath = AppSettingsHelper.Get("WordBasePath") + "Travel/export/";
+            if (!Directory.Exists(savePath))
+            {
+                try
+                {
+                    Directory.CreateDirectory(savePath);
+                }
+                catch
+                {
+
+                }
+            }
+            string path = savePath + Find.TeamName + "出访日程";
+            string ftpPath = AppSettingsHelper.Get("WordBaseUrl") + AppSettingsHelper.Get("WordFtpPath") + "Travel/export/" + Find.TeamName + "出访日程";
+
+            try
+            {
+
+                Aspose.Words.SaveFormat saveFormat = Aspose.Words.SaveFormat.Doc;
+                string postfix = ".docx";
+
+                doc.Save(path + postfix, saveFormat);
+                jw = JsonView(true, "导出成功", ftpPath + postfix);
+            }
+            catch (Exception)
+            {
+                jw = JsonView(false);
+            }
+
+            return Ok(jw);
         }
 
         #endregion

+ 19 - 1
OASystem/OASystem.Api/OAMethodLib/DeepSeekAPI/DeepSeekModels.cs

@@ -1,4 +1,4 @@
-using System.Text.Json.Serialization;
+using System.Text.Json.Serialization;
 
 namespace OASystem.API.OAMethodLib.DeepSeekAPI
 {
@@ -186,6 +186,24 @@ namespace OASystem.API.OAMethodLib.DeepSeekAPI
         /// </summary>
         [JsonPropertyName("max_tokens")]
         public int MaxTokens { get; set; }
+
+        /// <summary>
+        /// 核采样参数,范围一般为 0~1
+        /// </summary>
+        [JsonPropertyName("top_p")]
+        public decimal TopP { get; set; } = 1.0M;
+
+        /// <summary>
+        /// 频率惩罚,降低重复内容
+        /// </summary>
+        [JsonPropertyName("frequency_penalty")]
+        public decimal FrequencyPenalty { get; set; } = 0M;
+
+        /// <summary>
+        /// 存在惩罚,鼓励引入新内容
+        /// </summary>
+        [JsonPropertyName("presence_penalty")]
+        public decimal PresencePenalty { get; set; } = 0M;
     }
 
     /// <summary>

+ 7 - 3
OASystem/OASystem.Api/OAMethodLib/DeepSeekAPI/DeepSeekService.cs

@@ -1,4 +1,4 @@
-using System.Net.Http;
+using System.Net.Http;
 using System.Net.Http.Headers;
 using System.Text.Json;
 using System.Text.Json.Serialization;
@@ -328,7 +328,7 @@ namespace OASystem.API.OAMethodLib.DeepSeekAPI
         /// <param name="temperature"></param>
         /// <param name="maxTokens"></param>
         /// <returns></returns>
-        public async Task<ApiResponse> ChatAsync(string question, bool stream = false, string model = "deepseek-chat",  float temperature = 0.7f, int maxTokens = 4000)
+        public async Task<ApiResponse> ChatAsync(string question, bool stream = false, string model = "deepseek-chat", float temperature = 0.7f, int maxTokens = 4000)
         {
             try
             {
@@ -351,7 +351,10 @@ namespace OASystem.API.OAMethodLib.DeepSeekAPI
                     },
                     Stream = stream,
                     Temperature = temperature,
-                    MaxTokens = maxTokens
+                    MaxTokens = maxTokens,
+                    TopP = 0.9M,
+                    FrequencyPenalty = 0.2M,
+                    PresencePenalty = 0.1M,
                 };
 
                 var jsonContent = JsonSerializer.Serialize(request, new JsonSerializerOptions
@@ -359,6 +362,7 @@ namespace OASystem.API.OAMethodLib.DeepSeekAPI
                     PropertyNamingPolicy = JsonNamingPolicy.CamelCase
                 });
 
+                _httpClient.Timeout = TimeSpan.FromMinutes(10);
                 var httpContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
                 var response = await _httpClient.PostAsync("chat/completions", httpContent);
                 response.EnsureSuccessStatusCode();

+ 17 - 12
OASystem/OASystem.Api/OAMethodLib/DoubaoAPI/DoubaoService.cs

@@ -1,5 +1,6 @@
 using Aspose.Words;
 using OASystem.API.OAMethodLib.File;
+using System.Net;
 using System.Net.Http.Headers;
 using System.Text;
 
@@ -11,6 +12,17 @@ namespace OASystem.API.OAMethodLib.DoubaoAPI
         private readonly DoubaoSetting _doubaoSetting;
         private readonly ILogger<DoubaoService> _logger;
 
+        private HttpClient CreateDoubaoClient()
+        {
+            // 强制指定 TLS 1.2(进程级设置)
+            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
+
+            var httpClient = _httpClientFactory.CreateClient("Doubao");
+            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _doubaoSetting.ApiKey);
+            httpClient.Timeout = TimeSpan.FromMinutes(10);
+            return httpClient;
+        }
+
         public DoubaoService(IHttpClientFactory httpClientFactory, DoubaoSetting doubaoSetting, ILogger<DoubaoService> logger)
         {
             _httpClientFactory = httpClientFactory;
@@ -38,9 +50,7 @@ namespace OASystem.API.OAMethodLib.DoubaoAPI
             }
 
             options ??= new CompleteChatOptions();
-            var httpClient = _httpClientFactory.CreateClient("Doubao");
-            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _doubaoSetting.ApiKey);
-            httpClient.Timeout = TimeSpan.FromMinutes(10);
+            var httpClient = CreateDoubaoClient();
 
             var body = new Dictionary<string, object>
             {
@@ -90,9 +100,7 @@ namespace OASystem.API.OAMethodLib.DoubaoAPI
             }
 
             options ??= new CompleteMultimodalChatOptions();
-            var httpClient = _httpClientFactory.CreateClient("Doubao");
-            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _doubaoSetting.ApiKey);
-            httpClient.Timeout = TimeSpan.FromMinutes(10); // 超时控制
+            var httpClient = CreateDoubaoClient();
 
             // 2. 构建请求体(核心:修正type值)
             var requestBody = new
@@ -208,8 +216,7 @@ namespace OASystem.API.OAMethodLib.DoubaoAPI
 
         public async Task<DoubaoFileResponse> UploadFileAsync(Stream fileStream, string fileName, string purpose = "user_data")
         {
-            var httpClient = _httpClientFactory.CreateClient("Doubao");
-            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _doubaoSetting.ApiKey);
+            var httpClient = CreateDoubaoClient();
 
             using var content = new MultipartFormDataContent();
 
@@ -236,8 +243,7 @@ namespace OASystem.API.OAMethodLib.DoubaoAPI
 
         public async Task<DoubaoFileListResponse> ListFilesAsync()
         {
-            var httpClient = _httpClientFactory.CreateClient("Doubao");
-            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _doubaoSetting.ApiKey);
+            var httpClient = CreateDoubaoClient();
 
             var response = await httpClient.GetAsync("files");
             response.EnsureSuccessStatusCode();
@@ -248,8 +254,7 @@ namespace OASystem.API.OAMethodLib.DoubaoAPI
 
         public async Task<bool> DeleteFileAsync(string fileId)
         {
-            var httpClient = _httpClientFactory.CreateClient("Doubao");
-            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _doubaoSetting.ApiKey);
+            var httpClient = CreateDoubaoClient();
 
             var response = await httpClient.DeleteAsync($"files/{fileId}");
             return response.IsSuccessStatusCode;

+ 0 - 5
OASystem/OASystem.Api/OAMethodLib/GeneralMethod.cs

@@ -6984,8 +6984,6 @@ namespace OASystem.API.OAMethodLib
 
         #endregion
 
-        #region 商邀 AI 辅助方法
-
         /// <summary>
         /// AI对话结果中提取JSON
         /// </summary>
@@ -7012,9 +7010,6 @@ namespace OASystem.API.OAMethodLib
             // 3) 实在提取不到,按原文返回,让反序列化抛错
             return input.Trim();
         }
-
-        #endregion
-
     }
 }
 

+ 252 - 0
OASystem/OASystem.Api/OAMethodLib/MarkDown/JsonToWordHelper.cs

@@ -0,0 +1,252 @@
+using Aspose.Words;
+using Aspose.Words.Tables;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+public static class JsonToWordHelper
+{
+    public static byte[] ConvertJsonToWord(string json)
+    {
+        if (string.IsNullOrWhiteSpace(json))
+            throw new ArgumentException("JSON不能为空", nameof(json));
+
+        var model = System.Text.Json.JsonSerializer.Deserialize<TripDocumentModel>(json, new JsonSerializerOptions
+        {
+            PropertyNameCaseInsensitive = true
+        });
+
+        if (model == null)
+            throw new InvalidOperationException("JSON反序列化失败");
+
+        model.Sections ??= new List<TripSection>();
+        model.Closing ??= new List<string>();
+
+        var doc = new Document();
+        var builder = new DocumentBuilder(doc);
+
+        SetDefaultFont(builder);
+
+        // 标题
+        builder.ParagraphFormat.ClearFormatting();
+        builder.ParagraphFormat.Alignment = ParagraphAlignment.Center;
+        builder.Font.Bold = true;
+        builder.Font.Size = 16;
+        builder.Writeln(model.Title ?? string.Empty);
+
+        builder.InsertParagraph();
+
+        // 收文单位
+        builder.ParagraphFormat.ClearFormatting();
+        builder.ParagraphFormat.Alignment = ParagraphAlignment.Left;
+        builder.Font.Bold = false;
+        builder.Font.Size = 12;
+        builder.Writeln($"{model.Receiver}:");
+
+        // 引言
+        WriteNormalParagraph(builder, model.Intro);
+
+        // 正文 sections
+        foreach (var section in model.Sections)
+        {
+            if (!string.IsNullOrWhiteSpace(section.Title))
+            {
+                builder.ParagraphFormat.ClearFormatting();
+                builder.ParagraphFormat.Alignment = ParagraphAlignment.Left;
+                builder.Font.Bold = true;
+                builder.Font.Size = 12;
+                builder.Writeln(section.Title);
+            }
+
+            if (string.Equals(section.Type, "table", StringComparison.OrdinalIgnoreCase))
+            {
+                WriteTable(builder, section.Table);
+                builder.InsertParagraph();
+                continue;
+            }
+
+            if (section.Items != null && section.Items.Count > 0)
+            {
+                for (int i = 0; i < section.Items.Count; i++)
+                {
+                    var item = section.Items[i];
+                    WriteNormalParagraph(builder, $"{i + 1}. {item.Title}");
+
+                    if (item.Content != null)
+                    {
+                        foreach (var line in item.Content)
+                        {
+                            WriteNormalParagraph(builder, line);
+                        }
+                    }
+                }
+            }
+            else if (section.Content != null && section.Content.Count > 0)
+            {
+                foreach (var line in section.Content)
+                {
+                    WriteNormalParagraph(builder, line);
+                }
+            }
+
+            builder.InsertParagraph();
+        }
+
+        // 结尾
+        foreach (var line in model.Closing)
+        {
+            WriteNormalParagraph(builder, line);
+        }
+
+        builder.InsertParagraph();
+
+        // 落款
+        builder.ParagraphFormat.ClearFormatting();
+        builder.ParagraphFormat.Alignment = ParagraphAlignment.Right;
+        builder.Font.Bold = false;
+        builder.Font.Size = 12;
+        builder.Writeln(model.Signature ?? string.Empty);
+        builder.Writeln(model.Date ?? string.Empty);
+
+        using var ms = new MemoryStream();
+        doc.Save(ms, SaveFormat.Docx);
+        return ms.ToArray();
+    }
+
+    private static void WriteNormalParagraph(DocumentBuilder builder, string? text)
+    {
+        if (string.IsNullOrWhiteSpace(text))
+            return;
+
+        builder.ParagraphFormat.ClearFormatting();
+        builder.ParagraphFormat.Alignment = ParagraphAlignment.Left;
+        builder.ParagraphFormat.FirstLineIndent = 24; // 首行缩进,约2字符
+        builder.ParagraphFormat.SpaceAfter = 6;
+        builder.Font.Bold = false;
+        builder.Font.Size = 12;
+        builder.Writeln(text.Trim());
+    }
+
+    private static void WriteTable(DocumentBuilder builder, TripTable? table)
+    {
+        if (table?.Headers == null || table.Headers.Count == 0)
+            return;
+
+        var colCount = table.Headers.Count;
+
+        builder.StartTable();
+
+        // 表头
+        foreach (var header in table.Headers)
+        {
+            builder.InsertCell();
+            builder.CellFormat.VerticalAlignment = CellVerticalAlignment.Center;
+            builder.ParagraphFormat.Alignment = ParagraphAlignment.Center;
+            builder.Font.Bold = true;
+            builder.Write(header ?? string.Empty);
+        }
+        builder.EndRow();
+
+        // 数据
+        if (table.Rows != null)
+        {
+            foreach (var row in table.Rows)
+            {
+                var safeRow = row ?? new List<string>();
+
+                for (int i = 0; i < colCount; i++)
+                {
+                    builder.InsertCell();
+                    builder.CellFormat.VerticalAlignment = CellVerticalAlignment.Center;
+                    builder.ParagraphFormat.Alignment = ParagraphAlignment.Center;
+                    builder.Font.Bold = false;
+
+                    var cellText = i < safeRow.Count ? safeRow[i] ?? string.Empty : string.Empty;
+                    builder.Write(cellText);
+                }
+
+                builder.EndRow();
+            }
+        }
+
+        var wordTable = builder.EndTable();
+
+        // 表格样式
+        wordTable.AutoFit(AutoFitBehavior.AutoFitToWindow);
+        foreach (Row row in wordTable.Rows)
+        {
+            foreach (Cell cell in row.Cells)
+            {
+                cell.CellFormat.LeftPadding = 5;
+                cell.CellFormat.RightPadding = 5;
+                cell.CellFormat.TopPadding = 3;
+                cell.CellFormat.BottomPadding = 3;
+            }
+        }
+    }
+
+    private static void SetDefaultFont(DocumentBuilder builder)
+    {
+        builder.Font.Name = "宋体";
+        builder.Font.Size = 12;
+    }
+}
+
+public class TripDocumentModel
+{
+    [JsonPropertyName("title")]
+    public string? Title { get; set; }
+
+    [JsonPropertyName("receiver")]
+    public string? Receiver { get; set; }
+
+    [JsonPropertyName("intro")]
+    public string? Intro { get; set; }
+
+    [JsonPropertyName("sections")]
+    public List<TripSection>? Sections { get; set; }
+
+    [JsonPropertyName("closing")]
+    public List<string>? Closing { get; set; }
+
+    [JsonPropertyName("signature")]
+    public string? Signature { get; set; }
+
+    [JsonPropertyName("date")]
+    public string? Date { get; set; }
+}
+
+public class TripSection
+{
+    [JsonPropertyName("type")]
+    public string? Type { get; set; }
+
+    [JsonPropertyName("title")]
+    public string? Title { get; set; }
+
+    [JsonPropertyName("items")]
+    public List<TripItem>? Items { get; set; }
+
+    [JsonPropertyName("content")]
+    public List<string>? Content { get; set; }
+
+    [JsonPropertyName("table")]
+    public TripTable? Table { get; set; }
+}
+
+public class TripItem
+{
+    [JsonPropertyName("title")]
+    public string? Title { get; set; }
+
+    [JsonPropertyName("content")]
+    public List<string>? Content { get; set; }
+}
+
+public class TripTable
+{
+    [JsonPropertyName("headers")]
+    public List<string>? Headers { get; set; }
+
+    [JsonPropertyName("rows")]
+    public List<List<string>>? Rows { get; set; }
+}

+ 101 - 0
OASystem/OASystem.Api/OAMethodLib/MarkDown/WordExporter.cs

@@ -0,0 +1,101 @@
+using Aspose.Words;
+using Aspose.Words.Loading;
+using Markdig;
+using System.Text;
+using System.Text.RegularExpressions;
+
+public static class WordExporter
+{
+    private static readonly MarkdownPipeline MarkdownPipeline = new MarkdownPipelineBuilder()
+        .UseAdvancedExtensions()
+        .Build();
+
+    public static void MarkdownToWord(string markdown, string outputPath)
+    {
+        if (string.IsNullOrWhiteSpace(markdown))
+            throw new ArgumentException("markdown 不能为空", nameof(markdown));
+
+        // 清理 AI 常带的 ```markdown 和 ```
+        markdown = CleanMarkdown(markdown);
+        // AI 返回内容有时会把表格压成一行,这里做一次轻量修复
+        markdown = NormalizeSingleLineTableMarkdown(markdown);
+
+        // Markdown -> HTML,再由 Aspose 读取 HTML 保留格式
+        var htmlBody = Markdown.ToHtml(markdown, MarkdownPipeline);
+        var htmlContent = BuildHtmlDocument(htmlBody);
+        var htmlBytes = Encoding.UTF8.GetBytes(htmlContent);
+        using var stream = new MemoryStream(htmlBytes);
+        stream.Position = 0;
+
+        var loadOptions = new LoadOptions
+        {
+            LoadFormat = LoadFormat.Html,
+            Encoding = Encoding.UTF8
+        };
+
+        //判断路径是否存在
+        var dir = Path.GetDirectoryName(outputPath);
+        if (!string.IsNullOrWhiteSpace(dir) && !Directory.Exists(dir))
+        {
+            Directory.CreateDirectory(dir);
+        }
+
+        var doc = new Document(stream, loadOptions);
+        doc.Save(outputPath, SaveFormat.Docx);
+    }
+
+    private static string CleanMarkdown(string input)
+    {
+        input = input.Trim();
+
+        if (input.StartsWith("```markdown", StringComparison.OrdinalIgnoreCase))
+            input = input.Substring("```markdown".Length);
+
+        if (input.StartsWith("```"))
+            input = input.Substring(3);
+
+        if (input.EndsWith("```"))
+            input = input.Substring(0, input.Length - 3);
+
+        return input.Trim();
+    }
+
+    private static string NormalizeSingleLineTableMarkdown(string markdown)
+    {
+        if (string.IsNullOrWhiteSpace(markdown))
+            return markdown;
+
+        // 已经是多行内容则不处理
+        if (markdown.Contains('\n') || markdown.Contains('\r'))
+            return markdown;
+
+        // 仅在明显是表格时处理,避免误伤普通文本
+        if (!markdown.Contains("|") || !Regex.IsMatch(markdown, @"\|\s*[-:]{3,}"))
+            return markdown;
+
+        // 把单行中的“行结束 + 下一行开始”从 "| |" 规整为换行
+        var normalized = Regex.Replace(markdown, @"\|\s+\|", "|\n|");
+        return normalized;
+    }
+
+    private static string BuildHtmlDocument(string body)
+    {
+        return $@"<!DOCTYPE html>
+<html>
+<head>
+    <meta charset=""utf-8"" />
+    <style>
+        body {{ font-family: Calibri, 'Microsoft YaHei', sans-serif; line-height: 1.6; }}
+        pre, code {{ font-family: Consolas, 'Courier New', monospace; }}
+        pre {{ background: #f6f8fa; padding: 8px; border-radius: 4px; }}
+        table {{ border-collapse: collapse; width: 100%; }}
+        th, td {{ border: 1px solid #d0d7de; padding: 6px 8px; }}
+        blockquote {{ border-left: 4px solid #d0d7de; margin: 8px 0; padding-left: 12px; color: #57606a; }}
+    </style>
+</head>
+<body>
+{body}
+</body>
+</html>";
+    }
+}

+ 29 - 3
OASystem/OASystem.Domain/Dtos/Groups/InvitationOfficialActivitiesListDto.cs

@@ -234,9 +234,7 @@ namespace OASystem.Domain.Dtos.Groups
 
     public class BusinessEnterpriseContinueWriteDto
     {
-        public string DepartEnterpriseName { get; set; }
-
-        public List<string> EnterpriseNames { get; set; }
+        public int Diid { get; set; }
     }
 
     public class BusinessSynchronizationWriteDto
@@ -246,10 +244,38 @@ namespace OASystem.Domain.Dtos.Groups
         public List<string> EnterpriseNames { get; set; }
     }
 
+    public class ToOjbectDto
+    {
+        public string Value { get; set; }
+    }
+
     public class BusinessJourneyFileDownDto
     {
         public int Diid { get; set; }
 
         public int BlackCodeId { get; set; }
     }
+
+    public class TravelPlan
+    {
+        /// <summary>
+        /// 日期
+        /// </summary>
+        public string Date { get; set; }
+
+        /// <summary>
+        /// 星期(建议用枚举更规范)
+        /// </summary>
+        public string WeekDay { get; set; }
+
+        /// <summary>
+        /// 交通工具(如:["飞机", "汽车"])
+        /// </summary>
+        public List<string> Traffic { get; set; }
+
+        /// <summary>
+        /// 当天行程
+        /// </summary>
+        public string Trip { get; set; }
+    }
 }