|
|
@@ -1,4 +1,5 @@
|
|
|
using Aspose.Cells;
|
|
|
+using Aspose.Words;
|
|
|
using FluentValidation;
|
|
|
using Microsoft.AspNetCore.SignalR;
|
|
|
using OASystem.API.OAMethodLib;
|
|
|
@@ -21,6 +22,8 @@ using System.Data;
|
|
|
using System.Diagnostics;
|
|
|
using System.Globalization;
|
|
|
using static OASystem.API.OAMethodLib.JWTHelper;
|
|
|
+using Markdig;
|
|
|
+using Dm.util;
|
|
|
|
|
|
namespace OASystem.API.Controllers
|
|
|
{
|
|
|
@@ -3119,7 +3122,8 @@ OPTION (MAXRECURSION 0); -- 允许无限递归 ";
|
|
|
#region Ai绩效分析
|
|
|
|
|
|
[HttpGet]
|
|
|
- public async Task<IActionResult> AiPerformanceAnalysis_JobMarketingAsync(int userId, DateTime start, DateTime end)
|
|
|
+ public async Task<IActionResult> AiPerformanceAnalysis_JobMarketingAsync
|
|
|
+ (int userId, DateTime start, DateTime end, int createUserId)
|
|
|
{
|
|
|
var jw = JsonView(false);
|
|
|
|
|
|
@@ -3254,6 +3258,21 @@ OPTION (MAXRECURSION 0); -- 允许无限递归 ";
|
|
|
Answer = resp.Answer,
|
|
|
kaoqinAnswer = kaoqinResp.Answer
|
|
|
};
|
|
|
+
|
|
|
+ //保存至数据库中
|
|
|
+ Pm_PerformanceAnalysis insertData = new Pm_PerformanceAnalysis
|
|
|
+ {
|
|
|
+ CreateTime = DateTime.Now,
|
|
|
+ CreateUserId = createUserId,
|
|
|
+ IsDel = 0,
|
|
|
+ Year = start.Year,
|
|
|
+ Month = start.Month,
|
|
|
+ JsonResult = JsonConvert.SerializeObject(jw.Data),
|
|
|
+ UserId = userId,
|
|
|
+ };
|
|
|
+
|
|
|
+ await _sqlSugar.Insertable(insertData).ExecuteCommandAsync();
|
|
|
+
|
|
|
}
|
|
|
catch (Exception ex)
|
|
|
{
|
|
|
@@ -3267,6 +3286,781 @@ OPTION (MAXRECURSION 0); -- 允许无限递归 ";
|
|
|
return Ok(jw);
|
|
|
}
|
|
|
|
|
|
+ class wordTable
|
|
|
+ {
|
|
|
+ public int 序号 { get; set; }
|
|
|
+ public string 团组名 { get; set; }
|
|
|
+ public decimal 营业颔 { get; set; }
|
|
|
+
|
|
|
+ public string 出访日期 { get; set; }
|
|
|
+
|
|
|
+ public int 人数 { get; set; }
|
|
|
+
|
|
|
+ public string 收款日期 { get; set; }
|
|
|
+ };
|
|
|
+
|
|
|
+ [HttpPost]
|
|
|
+ public async Task<IActionResult> AiPerformanceAnalysis_JobMarketingFileDownAsync(int year, int month, int userId)
|
|
|
+ {
|
|
|
+ var jw = JsonView(false);
|
|
|
+
|
|
|
+ var user_entity = _sqlSugar.Queryable<Sys_Users>()
|
|
|
+ .First(e => e.Id == userId && e.IsDel == 0);
|
|
|
+
|
|
|
+ if (user_entity == null)
|
|
|
+ {
|
|
|
+ jw.Msg = "用户不存在!";
|
|
|
+ return Ok(jw);
|
|
|
+ }
|
|
|
+
|
|
|
+ var data = new Pm_PerformanceAnalysis();
|
|
|
+
|
|
|
+ if (year < 1 && month < 1)
|
|
|
+ {
|
|
|
+ data = await _sqlSugar.Queryable<Pm_PerformanceAnalysis>()
|
|
|
+ .Where(x => x.UserId == userId && x.IsDel == 0)
|
|
|
+ .OrderByDescending(x => x.CreateTime)
|
|
|
+ .FirstAsync();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ data = await _sqlSugar.Queryable<Pm_PerformanceAnalysis>()
|
|
|
+ .Where(x => x.UserId == userId && x.Year == year && x.Month == month && x.IsDel == 0)
|
|
|
+ .FirstAsync();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (data == null)
|
|
|
+ {
|
|
|
+ jw.Msg = "数据不存在!";
|
|
|
+ return Ok(jw);
|
|
|
+ }
|
|
|
+
|
|
|
+ var jsonResult = JObject.Parse(data.JsonResult);
|
|
|
+
|
|
|
+ var answer = jsonResult["Answer"]?.toString();
|
|
|
+ var kaoqinAnswer = jsonResult["kaoqinAnswer"]?.toString();
|
|
|
+ var tableapi = await this.AiPerformanceAnalysis_GroupStatisticsAsync(
|
|
|
+ userId,
|
|
|
+ new DateTime(data.Year, data.Month, 1),
|
|
|
+ new DateTime(data.Year, data.Month, 1).AddMonths(1)
|
|
|
+ );
|
|
|
+
|
|
|
+ var jwValue = (((tableapi as OkObjectResult).Value) as OASystem.Domain.ViewModels.JsonView);
|
|
|
+ if (jwValue.Code != 200)
|
|
|
+ {
|
|
|
+ jw.Msg = "获取团组统计数据失败!" + jwValue.Msg;
|
|
|
+ return Ok(jw);
|
|
|
+ }
|
|
|
+
|
|
|
+ var tableList = jwValue.Data as List<AiPerformanceAnalysis_GroupStatisticsView>;
|
|
|
+ if (tableList == null)
|
|
|
+ {
|
|
|
+ jw.Msg = "获取团组统计数据失败!";
|
|
|
+ return Ok(jw);
|
|
|
+ }
|
|
|
+
|
|
|
+ var tableListValue = tableList.Select(x => new wordTable
|
|
|
+ {
|
|
|
+ 序号 = x.RowNumber,
|
|
|
+ 团组名 = x.TeamName,
|
|
|
+ 营业颔 = x.GroupSales,
|
|
|
+ 出访日期 = x.VisitDate.ToString("yyyy-MM-dd"),
|
|
|
+ 人数 = x.VisitPNumber,
|
|
|
+ 收款日期 = x.CollectionDays.ToString("yyyy-MM-dd")
|
|
|
+ }).ToList();
|
|
|
+
|
|
|
+ var url = this.MarkdownToWord(new
|
|
|
+ List<WordContentItem>{
|
|
|
+ new
|
|
|
+ WordContentItem{
|
|
|
+ Type = WordContentType.Markdown,
|
|
|
+ MarkdownContent = answer
|
|
|
+ },
|
|
|
+ WordContentItem.FromObjectList(tableListValue, "团组统计")
|
|
|
+ ,
|
|
|
+ new
|
|
|
+ WordContentItem{
|
|
|
+ Type = WordContentType.Markdown,
|
|
|
+ MarkdownContent = kaoqinAnswer
|
|
|
+ }
|
|
|
+ }, $"{user_entity.CnName}_{data.Year}年{data.Month.ToString("00")}月绩效分析");
|
|
|
+
|
|
|
+ return Ok(new
|
|
|
+ {
|
|
|
+ url
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Word文档内容项类型
|
|
|
+ /// </summary>
|
|
|
+ public enum WordContentType
|
|
|
+ {
|
|
|
+ Markdown, // Markdown内容
|
|
|
+ Table // 表格内容
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Word文档内容项
|
|
|
+ /// </summary>
|
|
|
+ public class WordContentItem
|
|
|
+ {
|
|
|
+ /// <summary>
|
|
|
+ /// 内容类型
|
|
|
+ /// </summary>
|
|
|
+ public WordContentType Type { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Markdown内容(当Type为Markdown时使用)
|
|
|
+ /// </summary>
|
|
|
+ public string MarkdownContent { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 表格数据(当Type为Table时使用)- 第一行为表头
|
|
|
+ /// </summary>
|
|
|
+ public List<List<string>> TableData { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 表格标题(可选)
|
|
|
+ /// </summary>
|
|
|
+ public string TableTitle { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 从DataTable创建表格数据(便捷方法)
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="dataTable">数据表</param>
|
|
|
+ /// <param name="tableTitle">表格标题(可选)</param>
|
|
|
+ /// <param name="includeHeader">是否包含表头(默认true)</param>
|
|
|
+ /// <returns>WordContentItem实例</returns>
|
|
|
+ public static WordContentItem FromDataTable(DataTable dataTable, string? tableTitle = null, bool includeHeader = true)
|
|
|
+ {
|
|
|
+ if (dataTable == null || dataTable.Rows.Count == 0)
|
|
|
+ {
|
|
|
+ return new WordContentItem
|
|
|
+ {
|
|
|
+ Type = WordContentType.Table,
|
|
|
+ TableTitle = tableTitle,
|
|
|
+ TableData = new List<List<string>>()
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ var tableData = new List<List<string>>();
|
|
|
+
|
|
|
+ // 添加表头
|
|
|
+ if (includeHeader)
|
|
|
+ {
|
|
|
+ var headerRow = new List<string>();
|
|
|
+ foreach (DataColumn column in dataTable.Columns)
|
|
|
+ {
|
|
|
+ headerRow.Add(column.ColumnName);
|
|
|
+ }
|
|
|
+ tableData.Add(headerRow);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加数据行
|
|
|
+ foreach (DataRow row in dataTable.Rows)
|
|
|
+ {
|
|
|
+ var dataRow = new List<string>();
|
|
|
+ foreach (DataColumn column in dataTable.Columns)
|
|
|
+ {
|
|
|
+ dataRow.Add(row[column]?.ToString() ?? "");
|
|
|
+ }
|
|
|
+ tableData.Add(dataRow);
|
|
|
+ }
|
|
|
+
|
|
|
+ return new WordContentItem
|
|
|
+ {
|
|
|
+ Type = WordContentType.Table,
|
|
|
+ TableTitle = tableTitle,
|
|
|
+ TableData = tableData
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 从对象列表创建表格数据(便捷方法)
|
|
|
+ /// </summary>
|
|
|
+ /// <typeparam name="T">对象类型</typeparam>
|
|
|
+ /// <param name="items">对象列表</param>
|
|
|
+ /// <param name="tableTitle">表格标题(可选)</param>
|
|
|
+ /// <param name="columnMappings">列映射(属性名 -> 显示名称),如果为null则使用属性名</param>
|
|
|
+ /// <returns>WordContentItem实例</returns>
|
|
|
+ public static WordContentItem FromObjectList<T>(List<T> items, string? tableTitle = null, Dictionary<string, string>? columnMappings = null)
|
|
|
+ {
|
|
|
+ if (items == null || items.Count == 0)
|
|
|
+ {
|
|
|
+ return new WordContentItem
|
|
|
+ {
|
|
|
+ Type = WordContentType.Table,
|
|
|
+ TableTitle = tableTitle,
|
|
|
+ TableData = new List<List<string>>()
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ var tableData = new List<List<string>>();
|
|
|
+ var properties = typeof(T).GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
|
|
+
|
|
|
+ // 添加表头
|
|
|
+ var headerRow = new List<string>();
|
|
|
+ foreach (var prop in properties)
|
|
|
+ {
|
|
|
+ string headerName = columnMappings != null && columnMappings.ContainsKey(prop.Name)
|
|
|
+ ? columnMappings[prop.Name]
|
|
|
+ : prop.Name;
|
|
|
+ headerRow.Add(headerName);
|
|
|
+ }
|
|
|
+ tableData.Add(headerRow);
|
|
|
+
|
|
|
+ // 添加数据行
|
|
|
+ foreach (var item in items)
|
|
|
+ {
|
|
|
+ var dataRow = new List<string>();
|
|
|
+ foreach (var prop in properties)
|
|
|
+ {
|
|
|
+ var value = prop.GetValue(item);
|
|
|
+ dataRow.Add(value?.ToString() ?? "");
|
|
|
+ }
|
|
|
+ tableData.Add(dataRow);
|
|
|
+ }
|
|
|
+
|
|
|
+ return new WordContentItem
|
|
|
+ {
|
|
|
+ Type = WordContentType.Table,
|
|
|
+ TableTitle = tableTitle,
|
|
|
+ TableData = tableData
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 将多个Markdown内容和表格合并转换为Word文档
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="contentItems">内容项列表(可以是Markdown或表格)</param>
|
|
|
+ /// <param name="fileName">文件名(不含扩展名)</param>
|
|
|
+ /// <returns>返回文件访问URL</returns>
|
|
|
+ private string MarkdownToWord(List<WordContentItem> contentItems, string fileName)
|
|
|
+ {
|
|
|
+ if (contentItems == null || contentItems.Count == 0)
|
|
|
+ {
|
|
|
+ throw new ArgumentException("内容项不能为空", nameof(contentItems));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建新的Word文档
|
|
|
+ Aspose.Words.Document doc = new Aspose.Words.Document();
|
|
|
+ Aspose.Words.DocumentBuilder builder = new Aspose.Words.DocumentBuilder(doc);
|
|
|
+
|
|
|
+ // 设置默认字体
|
|
|
+ builder.Font.Name = "微软雅黑";
|
|
|
+ builder.Font.Size = 10.5;
|
|
|
+
|
|
|
+ // Markdown转换管道
|
|
|
+ var pipeline = new Markdig.MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
|
|
|
+
|
|
|
+ // 处理每个内容项
|
|
|
+ for (int i = 0; i < contentItems.Count; i++)
|
|
|
+ {
|
|
|
+ var item = contentItems[i];
|
|
|
+
|
|
|
+ // 如果是第一个内容项,先清理文档开头的空段落
|
|
|
+ if (i == 0)
|
|
|
+ {
|
|
|
+ // 清理文档开头的所有空段落
|
|
|
+ var firstBody = doc.FirstSection.Body;
|
|
|
+ while (firstBody.Paragraphs.Count > 0)
|
|
|
+ {
|
|
|
+ var firstPara = firstBody.Paragraphs[0];
|
|
|
+ if (string.IsNullOrWhiteSpace(firstPara.GetText().Trim()))
|
|
|
+ {
|
|
|
+ firstPara.Remove();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // 如果不是第一个内容项,确保前一个段落没有过大的间距
|
|
|
+ builder.MoveToDocumentEnd();
|
|
|
+ var lastParagraph = doc.LastSection.Body.LastParagraph;
|
|
|
+ if (lastParagraph != null)
|
|
|
+ {
|
|
|
+ // 减少前一个段落的间距
|
|
|
+ lastParagraph.ParagraphFormat.SpaceAfter = 0;
|
|
|
+ lastParagraph.ParagraphFormat.SpaceBefore = 0;
|
|
|
+ }
|
|
|
+ // 不插入额外的段落分隔,直接追加内容
|
|
|
+ }
|
|
|
+
|
|
|
+ if (item.Type == WordContentType.Markdown)
|
|
|
+ {
|
|
|
+ // 处理Markdown内容
|
|
|
+ if (!string.IsNullOrWhiteSpace(item.MarkdownContent))
|
|
|
+ {
|
|
|
+ // 将Markdown转换为HTML
|
|
|
+ string htmlBody = Markdig.Markdown.ToHtml(item.MarkdownContent, pipeline);
|
|
|
+
|
|
|
+ // 包装成完整的HTML片段,减少body的margin和padding
|
|
|
+ string htmlContent = $@"<!DOCTYPE html>
|
|
|
+<html>
|
|
|
+<head>
|
|
|
+ <meta charset=""UTF-8"">
|
|
|
+ <meta http-equiv=""Content-Type"" content=""text/html; charset=UTF-8"">
|
|
|
+ <style>
|
|
|
+ body {{ font-family: '微软雅黑', 'Microsoft YaHei', SimSun, sans-serif; font-size: 10.5pt; line-height: 1.5; margin: 0; padding: 0; }}
|
|
|
+ h1, h2, h3, h4, h5, h6 {{ font-family: '微软雅黑', 'Microsoft YaHei', SimHei, sans-serif; margin-top: 12pt; margin-bottom: 6pt; }}
|
|
|
+ h1 {{ margin-top: 0; }}
|
|
|
+ p {{ margin: 0; padding: 0; }}
|
|
|
+ table {{ border-collapse: collapse; width: 100%; margin: 10px 0; }}
|
|
|
+ table th, table td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
|
|
|
+ table th {{ background-color: #f2f2f2; font-weight: bold; }}
|
|
|
+ code {{ font-family: 'Consolas', 'Courier New', monospace; }}
|
|
|
+ pre {{ background-color: #f5f5f5; padding: 10px; border-radius: 4px; margin: 6pt 0; }}
|
|
|
+ ul, ol {{ margin: 6pt 0; padding-left: 20pt; }}
|
|
|
+ li {{ margin: 3pt 0; }}
|
|
|
+ </style>
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+{htmlBody}
|
|
|
+</body>
|
|
|
+</html>";
|
|
|
+
|
|
|
+ // 使用LoadOptions指定UTF-8编码
|
|
|
+ var loadOptions = new Aspose.Words.LoadOptions
|
|
|
+ {
|
|
|
+ LoadFormat = Aspose.Words.LoadFormat.Html,
|
|
|
+ Encoding = System.Text.Encoding.UTF8
|
|
|
+ };
|
|
|
+
|
|
|
+ // 从HTML创建临时文档
|
|
|
+ byte[] htmlBytes = System.Text.Encoding.UTF8.GetBytes(htmlContent);
|
|
|
+ using (MemoryStream htmlStream = new MemoryStream(htmlBytes))
|
|
|
+ {
|
|
|
+ Aspose.Words.Document tempDoc = new Aspose.Words.Document(htmlStream, loadOptions);
|
|
|
+
|
|
|
+ if (i == 0)
|
|
|
+ {
|
|
|
+ // 第一个内容项:先清理所有空段落
|
|
|
+ var firstBody = doc.FirstSection.Body;
|
|
|
+
|
|
|
+ // 删除所有空段落
|
|
|
+ while (firstBody.Paragraphs.Count > 0)
|
|
|
+ {
|
|
|
+ var firstPara = firstBody.Paragraphs[0];
|
|
|
+ if (string.IsNullOrWhiteSpace(firstPara.GetText().Trim()))
|
|
|
+ {
|
|
|
+ firstPara.Remove();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果文档完全为空,直接替换整个文档内容
|
|
|
+ if (firstBody.Paragraphs.Count == 0)
|
|
|
+ {
|
|
|
+ // 清空原文档的所有内容
|
|
|
+ doc.FirstSection.Body.RemoveAllChildren();
|
|
|
+
|
|
|
+ // 将临时文档的所有内容节点复制到主文档
|
|
|
+ foreach (Aspose.Words.Node node in tempDoc.FirstSection.Body.ChildNodes.ToArray())
|
|
|
+ {
|
|
|
+ var importedNode = doc.ImportNode(node, true);
|
|
|
+ doc.FirstSection.Body.AppendChild(importedNode);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // 文档有内容,在第一个段落之前插入
|
|
|
+ var firstPara = firstBody.Paragraphs[0];
|
|
|
+ builder.MoveTo(firstPara);
|
|
|
+ builder.InsertDocument(tempDoc, Aspose.Words.ImportFormatMode.KeepSourceFormatting);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 插入后再次清理开头的空段落,并统一格式
|
|
|
+ firstBody = doc.FirstSection.Body;
|
|
|
+ while (firstBody.Paragraphs.Count > 0)
|
|
|
+ {
|
|
|
+ var firstPara = firstBody.Paragraphs[0];
|
|
|
+ if (string.IsNullOrWhiteSpace(firstPara.GetText().Trim()))
|
|
|
+ {
|
|
|
+ firstPara.Remove();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // 找到第一个有内容的段落,确保它没有前间距
|
|
|
+ firstPara.ParagraphFormat.SpaceBefore = 0;
|
|
|
+ if (firstPara.ParagraphFormat.SpaceAfter > 6)
|
|
|
+ {
|
|
|
+ firstPara.ParagraphFormat.SpaceAfter = 6;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 统一字体格式
|
|
|
+ foreach (Aspose.Words.Run run in firstPara.Runs)
|
|
|
+ {
|
|
|
+ if (string.IsNullOrEmpty(run.Font.Name) ||
|
|
|
+ (!run.Font.Name.Contains("微软") && !run.Font.Name.Contains("Microsoft") &&
|
|
|
+ !run.Font.Name.Contains("Sim") && !run.Font.Name.Contains("宋体")))
|
|
|
+ {
|
|
|
+ run.Font.Name = "微软雅黑";
|
|
|
+ }
|
|
|
+ if (firstPara.ParagraphFormat.StyleIdentifier != Aspose.Words.StyleIdentifier.Heading1 &&
|
|
|
+ firstPara.ParagraphFormat.StyleIdentifier != Aspose.Words.StyleIdentifier.Heading2 &&
|
|
|
+ firstPara.ParagraphFormat.StyleIdentifier != Aspose.Words.StyleIdentifier.Heading3 &&
|
|
|
+ firstPara.ParagraphFormat.StyleIdentifier != Aspose.Words.StyleIdentifier.Heading4 &&
|
|
|
+ (run.Font.Size == 0 || (run.Font.Size != 12 && run.Font.Size != 14 && run.Font.Size != 16 && run.Font.Size != 18 && run.Font.Size != 20 && run.Font.Size != 24)))
|
|
|
+ {
|
|
|
+ run.Font.Size = 10.5;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 统一处理第一个内容项的所有段落格式
|
|
|
+ foreach (Aspose.Words.Paragraph para in firstBody.Paragraphs)
|
|
|
+ {
|
|
|
+ if (string.IsNullOrWhiteSpace(para.GetText().Trim()))
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 统一段落格式
|
|
|
+ para.ParagraphFormat.SpaceBefore = 0;
|
|
|
+ if (para.ParagraphFormat.SpaceAfter > 6)
|
|
|
+ {
|
|
|
+ para.ParagraphFormat.SpaceAfter = 6;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 统一字体格式 - 与非第一个内容项的处理逻辑完全一致
|
|
|
+ foreach (Aspose.Words.Run run in para.Runs)
|
|
|
+ {
|
|
|
+ // 统一字体名称 - 强制设置为微软雅黑
|
|
|
+ if (string.IsNullOrEmpty(run.Font.Name) ||
|
|
|
+ (!run.Font.Name.Contains("微软") && !run.Font.Name.Contains("Microsoft") &&
|
|
|
+ !run.Font.Name.Contains("Sim") && !run.Font.Name.Contains("宋体")))
|
|
|
+ {
|
|
|
+ run.Font.Name = "微软雅黑";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 统一字体大小 - 与非第一个内容项的逻辑完全一致
|
|
|
+ bool isHeading = para.ParagraphFormat.StyleIdentifier == Aspose.Words.StyleIdentifier.Heading1 ||
|
|
|
+ para.ParagraphFormat.StyleIdentifier == Aspose.Words.StyleIdentifier.Heading2 ||
|
|
|
+ para.ParagraphFormat.StyleIdentifier == Aspose.Words.StyleIdentifier.Heading3 ||
|
|
|
+ para.ParagraphFormat.StyleIdentifier == Aspose.Words.StyleIdentifier.Heading4;
|
|
|
+
|
|
|
+ if (!isHeading)
|
|
|
+ {
|
|
|
+ // 非标题:强制统一设置为10.5pt,确保与非第一个内容项一致
|
|
|
+ run.Font.Size = 10.5;
|
|
|
+ }
|
|
|
+ // 标题保持原有大小,但字体名称已在上面的逻辑中统一
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // 非第一个内容项:记录追加前的段落数量
|
|
|
+ int paraCountBefore = doc.FirstSection.Body.Paragraphs.Count;
|
|
|
+
|
|
|
+ // 追加到文档末尾
|
|
|
+ doc.AppendDocument(tempDoc, Aspose.Words.ImportFormatMode.KeepSourceFormatting);
|
|
|
+
|
|
|
+ // 追加后立即清理格式,确保与第一个内容项格式一致
|
|
|
+ var appendedBody = doc.FirstSection.Body;
|
|
|
+ var allParagraphs = appendedBody.Paragraphs;
|
|
|
+
|
|
|
+ // 处理所有追加的段落(从paraCountBefore开始)
|
|
|
+ for (int paraIdx = paraCountBefore; paraIdx < allParagraphs.Count; paraIdx++)
|
|
|
+ {
|
|
|
+ var para = allParagraphs[paraIdx];
|
|
|
+
|
|
|
+ // 删除空段落
|
|
|
+ if (string.IsNullOrWhiteSpace(para.GetText().Trim()))
|
|
|
+ {
|
|
|
+ para.Remove();
|
|
|
+ paraIdx--; // 调整索引
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 统一段落格式:移除前间距,限制后间距
|
|
|
+ para.ParagraphFormat.SpaceBefore = 0;
|
|
|
+ if (para.ParagraphFormat.SpaceAfter > 6)
|
|
|
+ {
|
|
|
+ para.ParagraphFormat.SpaceAfter = 6;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 统一字体格式 - 与第一个内容项的处理逻辑完全一致
|
|
|
+ foreach (Aspose.Words.Run run in para.Runs)
|
|
|
+ {
|
|
|
+ // 统一字体名称 - 强制设置为微软雅黑
|
|
|
+ if (string.IsNullOrEmpty(run.Font.Name) ||
|
|
|
+ (!run.Font.Name.Contains("微软") && !run.Font.Name.Contains("Microsoft") &&
|
|
|
+ !run.Font.Name.Contains("Sim") && !run.Font.Name.Contains("宋体")))
|
|
|
+ {
|
|
|
+ run.Font.Name = "微软雅黑";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 统一字体大小 - 与第一个内容项的逻辑完全一致
|
|
|
+ bool isHeading = para.ParagraphFormat.StyleIdentifier == Aspose.Words.StyleIdentifier.Heading1 ||
|
|
|
+ para.ParagraphFormat.StyleIdentifier == Aspose.Words.StyleIdentifier.Heading2 ||
|
|
|
+ para.ParagraphFormat.StyleIdentifier == Aspose.Words.StyleIdentifier.Heading3 ||
|
|
|
+ para.ParagraphFormat.StyleIdentifier == Aspose.Words.StyleIdentifier.Heading4;
|
|
|
+
|
|
|
+ if (!isHeading)
|
|
|
+ {
|
|
|
+ // 非标题:强制统一设置为10.5pt,确保与第一个内容项一致
|
|
|
+ run.Font.Size = 10.5;
|
|
|
+ }
|
|
|
+ // 标题保持原有大小,但字体名称已在上面的逻辑中统一
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确保追加内容前的最后一个段落没有过大的后间距
|
|
|
+ if (paraCountBefore > 0 && paraCountBefore <= allParagraphs.Count)
|
|
|
+ {
|
|
|
+ var lastParaBefore = allParagraphs[paraCountBefore - 1];
|
|
|
+ if (lastParaBefore != null)
|
|
|
+ {
|
|
|
+ lastParaBefore.ParagraphFormat.SpaceAfter = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 移动builder到文档末尾
|
|
|
+ builder.MoveToDocumentEnd();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (item.Type == WordContentType.Table)
|
|
|
+ {
|
|
|
+ // 处理表格内容
|
|
|
+ if (item.TableData != null && item.TableData.Count > 0)
|
|
|
+ {
|
|
|
+ // 确保builder在正确位置
|
|
|
+ if (i == 0)
|
|
|
+ {
|
|
|
+ // 第一个内容项,确保在文档开头
|
|
|
+ builder.MoveToDocumentStart();
|
|
|
+ // 清理开头的空段落
|
|
|
+ var tableBody = doc.FirstSection.Body;
|
|
|
+ while (tableBody.Paragraphs.Count > 0)
|
|
|
+ {
|
|
|
+ var firstPara = tableBody.Paragraphs[0];
|
|
|
+ if (string.IsNullOrWhiteSpace(firstPara.GetText().Trim()))
|
|
|
+ {
|
|
|
+ firstPara.Remove();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ builder.MoveToDocumentStart();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ builder.MoveToDocumentEnd();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加表格标题(如果有)
|
|
|
+ if (!string.IsNullOrWhiteSpace(item.TableTitle))
|
|
|
+ {
|
|
|
+ builder.InsertBreak(Aspose.Words.BreakType.ParagraphBreak);
|
|
|
+ builder.InsertBreak(Aspose.Words.BreakType.ParagraphBreak);
|
|
|
+ builder.InsertBreak(Aspose.Words.BreakType.ParagraphBreak);
|
|
|
+
|
|
|
+ builder.ParagraphFormat.SpaceAfter = 6;
|
|
|
+ builder.ParagraphFormat.SpaceBefore = 0; // 表格标题前不添加间距
|
|
|
+ builder.Font.Size = 12;
|
|
|
+ builder.Font.Bold = true;
|
|
|
+ builder.Font.Name = "微软雅黑";
|
|
|
+ builder.Writeln(item.TableTitle);
|
|
|
+ builder.Font.Bold = false;
|
|
|
+ builder.Font.Size = 10.5;
|
|
|
+ builder.ParagraphFormat.SpaceAfter = 3; // 标题和表格之间的小间距
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算列数(取第一行的列数)
|
|
|
+ int columnCount = item.TableData[0]?.Count ?? 0;
|
|
|
+ if (columnCount == 0)
|
|
|
+ {
|
|
|
+ continue; // 跳过空表格
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建表格
|
|
|
+ Aspose.Words.Tables.Table table = builder.StartTable();
|
|
|
+
|
|
|
+ // 设置列宽(平均分配)- 在添加行之前设置
|
|
|
+ for (int colIndex = 0; colIndex < columnCount; colIndex++)
|
|
|
+ {
|
|
|
+ builder.CellFormat.PreferredWidth = Aspose.Words.Tables.PreferredWidth.FromPercent(100.0 / columnCount);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 先添加表头(第一行),这样表格就不是空的了
|
|
|
+ if (item.TableData.Count > 0)
|
|
|
+ {
|
|
|
+ var headerRow = item.TableData[0];
|
|
|
+ foreach (var headerCell in headerRow)
|
|
|
+ {
|
|
|
+ builder.InsertCell();
|
|
|
+ builder.CellFormat.VerticalAlignment = Aspose.Words.Tables.CellVerticalAlignment.Center;
|
|
|
+ builder.CellFormat.Shading.BackgroundPatternColor = System.Drawing.Color.FromArgb(240, 240, 240);
|
|
|
+ builder.ParagraphFormat.Alignment = Aspose.Words.ParagraphAlignment.Center;
|
|
|
+ builder.Font.Bold = true;
|
|
|
+ builder.Font.Size = 10.5;
|
|
|
+ builder.Font.Name = "微软雅黑";
|
|
|
+ builder.Font.Color = System.Drawing.Color.Black;
|
|
|
+ builder.Write(headerCell ?? "");
|
|
|
+ builder.Font.Bold = false;
|
|
|
+ }
|
|
|
+ builder.EndRow();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 现在表格有行了,可以设置表格样式
|
|
|
+ table.StyleIdentifier = Aspose.Words.StyleIdentifier.LightGridAccent1;
|
|
|
+ table.AllowAutoFit = true;
|
|
|
+ table.PreferredWidth = Aspose.Words.Tables.PreferredWidth.FromPercent(100);
|
|
|
+ table.Alignment = Aspose.Words.Tables.TableAlignment.Center;
|
|
|
+
|
|
|
+ // 添加数据行
|
|
|
+ for (int rowIndex = 1; rowIndex < item.TableData.Count; rowIndex++)
|
|
|
+ {
|
|
|
+ var row = item.TableData[rowIndex];
|
|
|
+ // 确保列数一致
|
|
|
+ while (row.Count < columnCount)
|
|
|
+ {
|
|
|
+ row.Add("");
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach (var cell in row.Take(columnCount))
|
|
|
+ {
|
|
|
+ builder.InsertCell();
|
|
|
+ builder.CellFormat.VerticalAlignment = Aspose.Words.Tables.CellVerticalAlignment.Center;
|
|
|
+ builder.CellFormat.Shading.BackgroundPatternColor = System.Drawing.Color.White;
|
|
|
+ builder.ParagraphFormat.Alignment = Aspose.Words.ParagraphAlignment.Left;
|
|
|
+ builder.Font.Size = 10.5;
|
|
|
+ builder.Font.Name = "微软雅黑";
|
|
|
+ builder.Write(cell ?? "");
|
|
|
+ }
|
|
|
+ builder.EndRow();
|
|
|
+ }
|
|
|
+
|
|
|
+ builder.EndTable();
|
|
|
+
|
|
|
+ // 设置较小的段落间距
|
|
|
+ builder.ParagraphFormat.SpaceAfter = 6;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清理文档开头的空段落,避免第一段内容前出现空白页
|
|
|
+ var body = doc.FirstSection.Body;
|
|
|
+ var paragraphs = body.Paragraphs;
|
|
|
+ while (paragraphs.Count > 0)
|
|
|
+ {
|
|
|
+ var firstPara = paragraphs[0];
|
|
|
+ var paraText = firstPara.GetText().Trim();
|
|
|
+ // 如果第一个段落是空的或只包含空白字符,删除它
|
|
|
+ if (string.IsNullOrWhiteSpace(paraText))
|
|
|
+ {
|
|
|
+ firstPara.Remove();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // 找到第一个有内容的段落,确保它没有前间距
|
|
|
+ firstPara.ParagraphFormat.SpaceBefore = 0;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 遍历所有段落,确保字体设置正确,并统一段落间距
|
|
|
+ foreach (Aspose.Words.Paragraph paragraph in doc.GetChildNodes(Aspose.Words.NodeType.Paragraph, true))
|
|
|
+ {
|
|
|
+ // 统一段落间距,避免内容项之间出现大段空白
|
|
|
+ if (paragraph.ParagraphFormat.StyleIdentifier != Aspose.Words.StyleIdentifier.Heading1 &&
|
|
|
+ paragraph.ParagraphFormat.StyleIdentifier != Aspose.Words.StyleIdentifier.Heading2 &&
|
|
|
+ paragraph.ParagraphFormat.StyleIdentifier != Aspose.Words.StyleIdentifier.Heading3 &&
|
|
|
+ paragraph.ParagraphFormat.StyleIdentifier != Aspose.Words.StyleIdentifier.Heading4)
|
|
|
+ {
|
|
|
+ // 普通段落保持较小的间距
|
|
|
+ if (paragraph.ParagraphFormat.SpaceAfter > 6)
|
|
|
+ {
|
|
|
+ paragraph.ParagraphFormat.SpaceAfter = 6;
|
|
|
+ }
|
|
|
+ // 移除段落前的间距,避免内容项之间出现空白
|
|
|
+ if (paragraph.ParagraphFormat.SpaceBefore > 0)
|
|
|
+ {
|
|
|
+ paragraph.ParagraphFormat.SpaceBefore = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach (Aspose.Words.Run run in paragraph.Runs)
|
|
|
+ {
|
|
|
+ // 如果字体不是中文字体,设置为微软雅黑
|
|
|
+ if (string.IsNullOrEmpty(run.Font.Name) ||
|
|
|
+ (!run.Font.Name.Contains("微软") && !run.Font.Name.Contains("Microsoft") &&
|
|
|
+ !run.Font.Name.Contains("Sim") && !run.Font.Name.Contains("宋体")))
|
|
|
+ {
|
|
|
+ run.Font.Name = "微软雅黑";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存文件
|
|
|
+ string saveFolder = $"{AppSettingsHelper.Get("WordBasePath")}PerformanceAnalysis";
|
|
|
+ if (!Directory.Exists(saveFolder))
|
|
|
+ {
|
|
|
+ Directory.CreateDirectory(saveFolder);
|
|
|
+ }
|
|
|
+
|
|
|
+ string outputFile = $"{fileName}_{DateTime.Now:yyyyMMddHHmmss}.pdf";
|
|
|
+ string filePath = Path.Combine(saveFolder, outputFile);
|
|
|
+
|
|
|
+ doc.Save(filePath, Aspose.Words.SaveFormat.Pdf);
|
|
|
+
|
|
|
+ // 返回访问URL
|
|
|
+ return $"{AppSettingsHelper.Get("WordBaseUrl")}Office/Word/PerformanceAnalysis/{outputFile}";
|
|
|
+ }
|
|
|
+
|
|
|
+ [HttpGet]
|
|
|
+ public async Task<IActionResult> AiPerformanceAnalysis_QueryAsync(int year, int month, int userId)
|
|
|
+ {
|
|
|
+ if (userId < 1)
|
|
|
+ {
|
|
|
+ return Ok(JsonView(false, "请传入有效的userId参数!"));
|
|
|
+ }
|
|
|
+
|
|
|
+ var data = new Pm_PerformanceAnalysis();
|
|
|
+
|
|
|
+ if (year < 1 && month < 1)
|
|
|
+ {
|
|
|
+ data = await _sqlSugar.Queryable<Pm_PerformanceAnalysis>()
|
|
|
+ .Where(x => x.UserId == userId && x.IsDel == 0)
|
|
|
+ .OrderByDescending(x => x.CreateTime)
|
|
|
+ .FirstAsync();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ data = await _sqlSugar.Queryable<Pm_PerformanceAnalysis>()
|
|
|
+ .Where(x => x.UserId == userId && x.Year == year && x.Month == month && x.IsDel == 0)
|
|
|
+ .OrderByDescending(x => x.CreateTime)
|
|
|
+ .FirstAsync();
|
|
|
+ }
|
|
|
+
|
|
|
+ var result = JObject.Parse(data.JsonResult);
|
|
|
+
|
|
|
+ return Ok(JsonView(true, "操作成功!", new
|
|
|
+ {
|
|
|
+ data.JsonResult,
|
|
|
+ data.Year,
|
|
|
+ data.Month,
|
|
|
+ data.Id,
|
|
|
+ data.CreateTime,
|
|
|
+ data = result
|
|
|
+ }));
|
|
|
+ }
|
|
|
|
|
|
/// <summary>
|
|
|
/// Ai绩效分析
|
|
|
@@ -3313,11 +4107,23 @@ OPTION (MAXRECURSION 0); -- 允许无限递归 ";
|
|
|
|
|
|
RefAsync<int> total = 0;
|
|
|
|
|
|
+ var notidsJson = _sqlSugar.Queryable<Sys_SetData>().First(x => x.Id == 1463 && x.IsDel == 0)?.Remark;
|
|
|
+ var notids = new List<int>();
|
|
|
+ if (!string.IsNullOrWhiteSpace(notidsJson))
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ notids = JsonConvert.DeserializeObject<List<int>>(notidsJson) ?? new List<int>();
|
|
|
+ }
|
|
|
+ catch
|
|
|
+ { }
|
|
|
+ }
|
|
|
+
|
|
|
var query = _sqlSugar.Queryable<Sys_Users>()
|
|
|
.LeftJoin<Sys_Company>((u, c) => u.CompanyId == c.Id)
|
|
|
.LeftJoin<Sys_Department>((u, c, d) => u.DepId == d.Id)
|
|
|
.LeftJoin<Sys_JobPost>((u, c, d, jp) => u.JobPostId == jp.Id)
|
|
|
- .Where((u, c, d, jp) => u.IsDel == 0)
|
|
|
+ .Where((u, c, d, jp) => u.IsDel == 0 && !notids.Contains(u.Id))
|
|
|
.WhereIF(!string.IsNullOrEmpty(dto.ScreeningCriteria?.Trim()), (u, c, d, jp) =>
|
|
|
u.CnName.Contains(dto.ScreeningCriteria.Trim()) ||
|
|
|
c.CompanyName.Contains(dto.ScreeningCriteria.Trim()) ||
|