yuanrf 1 week ago
parent
commit
838aac64fe

+ 258 - 66
travelExport/Home.cs

@@ -15,6 +15,7 @@ using System.IO;
 using System.Threading;
 using travelExport.utility;
 using DiffMatchPatch;
+using Aspose.Words.Tables;
 
 namespace travelExport
 {
@@ -59,7 +60,7 @@ namespace travelExport
             {
                 Id = 2,
                 Name = "贵州行程模板",
-                Path = "C:\\贵州行程模板.docx#D:\\贵州行程模板1.docx"
+                Path = "C:\\贵州行程模板.docx#D:\\贵州行程模板.docx"
             },
         };
 
@@ -189,6 +190,7 @@ namespace travelExport
             cb.MaxDropDownItems = 8; // 自动弹出下拉框
         }
 
+        //导入简要行程
         private async void btnImport_Click(object sender, EventArgs e)
         {
             var tripTempValue = tripItemSelect.SelectedValue;
@@ -721,17 +723,19 @@ namespace travelExport
                             }
 
                             //比较上一次的文本
-                            foreach (var newItem in NewListTravel)
-                            {
-                                foreach (var oldItem in listTravel)
-                                {
-                                    if (newItem.Date == oldItem.Date)
-                                    {
-                                        var str = CheckStr(oldItem.Trip, newItem.Trip);
-                                        break;
-                                    }
-                                }
-                            }
+                            //foreach (var newItem in NewListTravel)
+                            //{
+                            //    foreach (var oldItem in listTravel)
+                            //    {
+                            //        if (newItem.Date == oldItem.Date)
+                            //        {
+                            //            oldItem.Trip = ReplaceNewlineIfNeeded(oldItem.Trip);
+                            //            var str = CheckStr(oldItem.Trip, newItem.Trip);
+                            //            newItem.Trip = str;
+                            //            break;
+                            //        }
+                            //    }
+                            //}
 
                             db.Grp_TravelList.RemoveRange(db.Grp_TravelList.Where(x => x.IsDel == 0 && x.Diid == diid));
                             db.Grp_TravelList.AddRange(NewListTravel);
@@ -965,6 +969,7 @@ namespace travelExport
             return path.Substring(0, path.LastIndexOf("\\") + 1);
         }
 
+        //导入详细行程
         public async void btnImportInfo()
         {
             var diid = -1;
@@ -1034,6 +1039,8 @@ namespace travelExport
             //本团行程单数据
             List<Grp_TravelList> listTravel = ExecuteDbContext((db => { return db.Grp_TravelList.AsNoTracking().Where(x => x.Diid == diid && x.IsDel == 0).ToList(); }));
 
+            var tags = CheckAndCenterTags(diid);
+
             ChildThreadExceptionHandler wt = new
                  ChildThreadExceptionHandler((msg) =>
                  {
@@ -1063,7 +1070,7 @@ namespace travelExport
                             var stopCity = string.Empty;
 
                             //本团车导地接信息
-                            var listctg = db.Grp_CarTouristGuideGroundReservations.Where(x => x.DiId == diid && x.IsDel == 0).ToList();
+                            //var listctg = db.Grp_CarTouristGuideGroundReservations.Where(x => x.DiId == diid && x.IsDel == 0).ToList();
 
                             //本团酒店信息
                             var listht = db.Grp_HotelReservations.Where(x => x.DiId == diid && x.IsDel == 0).ToList();
@@ -1072,7 +1079,7 @@ namespace travelExport
                             var listgw = db.Res_OfficialActivities.Where(x => x.DiId == diid && x.IsDel == 0).ToList();
 
                             //邀请公务资料
-                            var listgwzl = db.Grp_InvitationOfficialActivities.Where(x => x.DiId == diid && x.IsDel == 0).ToList();
+                            //var listgwzl = db.Grp_InvitationOfficialActivities.Where(x => x.DiId == diid && x.IsDel == 0).ToList();
 
                             Grp_HotelReservations lastHotel = null;
 
@@ -1363,8 +1370,8 @@ namespace travelExport
 
                                     if (time.Hour < 9)  // && (airArrive < new DateTime(airArrive.Year,airArrive.Month,airArrive.Day,6, 30, 0))
                                     {
-                                        var nineTime = GetTimeGwInfo(ref listgw, new DateTime(time.Year, time.Month, time.Day, 09, 00, 00), null);
-                                        var halfPasTen = GetTimeGwInfo(ref listgw, new DateTime(time.Year, time.Month, time.Day, 10, 30, 00), null);
+                                        var nineTime = GetTimeGwInfo(ref listgw, new DateTime(time.Year, time.Month, time.Day, 09, 00, 00));
+                                        var halfPasTen = GetTimeGwInfo(ref listgw, new DateTime(time.Year, time.Month, time.Day, 10, 30, 00));
                                         trip += @"
 09:00 公务活动;
 10:30 公务活动;";
@@ -1380,7 +1387,7 @@ namespace travelExport
                                     }
                                     else if (time.Hour < 10)
                                     {
-                                        var halfPasTen = GetTimeGwInfo(ref listgw, new DateTime(time.Year, time.Month, time.Day, 10, 30, 00), null);
+                                        var halfPasTen = GetTimeGwInfo(ref listgw, new DateTime(time.Year, time.Month, time.Day, 10, 30, 00));
                                         trip += @"
 10:30 公务活动;";
                                         if (!string.IsNullOrWhiteSpace(halfPasTen))
@@ -1396,8 +1403,8 @@ namespace travelExport
 
                                     if (time < new DateTime(time.Year, time.Month, time.Day, 14, 30, 0))
                                     {
-                                        var twoPointsPM = GetTimeGwInfo(ref listgw, new DateTime(time.Year, time.Month, time.Day, 14, 00, 00), null);
-                                        var fourPointsPM = GetTimeGwInfo(ref listgw, new DateTime(time.Year, time.Month, time.Day, 16, 00, 00), null);
+                                        var twoPointsPM = GetTimeGwInfo(ref listgw, new DateTime(time.Year, time.Month, time.Day, 14, 00, 00));
+                                        var fourPointsPM = GetTimeGwInfo(ref listgw, new DateTime(time.Year, time.Month, time.Day, 16, 00, 00));
                                         trip += @"
 14:00 公务活动;
 16:00 公务活动;";
@@ -1412,7 +1419,7 @@ namespace travelExport
                                     }
                                     else if (time.Hour < 16)
                                     {
-                                        var fourPointsPM = GetTimeGwInfo(ref listgw, new DateTime(time.Year, time.Month, time.Day, 16, 00, 00), null);
+                                        var fourPointsPM = GetTimeGwInfo(ref listgw, new DateTime(time.Year, time.Month, time.Day, 16, 00, 00));
                                         trip += @"
 16:00 公务活动;";
                                         if (!string.IsNullOrWhiteSpace(fourPointsPM))
@@ -1428,6 +1435,31 @@ namespace travelExport
 
                                     //time = time.AddHours(1);
                                     //trip += $"\r\n{time.ToString("HH:mm")} 搭乘专车前往酒店,抵达后办理入住;";
+
+
+                                    var htTime = DateTime.Now;
+                                    var htTimeOut = DateTime.Now;
+                                    foreach (var h in listht)
+                                    {
+                                        if (DateTime.TryParse(h.CheckInDate, out htTime) && DateTime.TryParse(h.CheckOutDate, out htTimeOut))
+                                        {
+                                            if (htTime <= Convert.ToDateTime(item) && htTimeOut >= Convert.ToDateTime(item))
+                                            {
+                                                lastHotel = h;
+
+                                                #region 酒店详细信息
+                                                trip += "\r\n00:00 乘车前往酒店";
+                                                trip += "\r\n00:00 抵达酒店办理入住手续";
+
+                                                trip = trip + "\r\n"
+                                                + "          酒店名称:" + h.HotelName + "\r\n"
+                                                + "          酒店地址:" + h.HotelAddress + "\r\n"
+                                                + "          酒店电话:" + h.HotelTel + "      酒店传真:" + (string.IsNullOrWhiteSpace(h.HotelFax) ? "-" : h.HotelFax);
+                                                #endregion
+                                            }
+                                        }
+                                    }
+
                                 }
                                 else
                                 {
@@ -1454,10 +1486,10 @@ namespace travelExport
 18:00 晚餐于当地餐厅;
 19:00 入住酒店休息;";
 
-                                    var nineTime = GetTimeGwInfo(ref listgw, new DateTime(time.Year, time.Month, time.Day, 09, 00, 00), end_Object.City);
-                                    var halfPasTen = GetTimeGwInfo(ref listgw, new DateTime(time.Year, time.Month, time.Day, 10, 30, 00), end_Object.City);
-                                    var twoPointsPM = GetTimeGwInfo(ref listgw, new DateTime(time.Year, time.Month, time.Day, 14, 00, 00), end_Object.City);
-                                    var fourPointsPM = GetTimeGwInfo(ref listgw, new DateTime(time.Year, time.Month, time.Day, 16, 00, 00), end_Object.City);
+                                    var nineTime = GetTimeGwInfo(ref listgw, new DateTime(time.Year, time.Month, time.Day, 09, 00, 00));
+                                    var halfPasTen = GetTimeGwInfo(ref listgw, new DateTime(time.Year, time.Month, time.Day, 10, 30, 00));
+                                    var twoPointsPM = GetTimeGwInfo(ref listgw, new DateTime(time.Year, time.Month, time.Day, 14, 00, 00));
+                                    var fourPointsPM = GetTimeGwInfo(ref listgw, new DateTime(time.Year, time.Month, time.Day, 16, 00, 00));
                                     if (!string.IsNullOrWhiteSpace(nineTime))
                                     {
                                         trip = trip.Replace("09:00 公务活动", nineTime);
@@ -1482,30 +1514,6 @@ namespace travelExport
                                     traffic = new string[] { "汽车", "" };
                                 }
 
-                                var htTime = DateTime.Now;
-                                var htTimeOut = DateTime.Now;
-                                foreach (var h in listht)
-                                {
-                                    if (DateTime.TryParse(h.CheckInDate, out htTime) && DateTime.TryParse(h.CheckOutDate, out htTimeOut))
-                                    {
-                                        if (htTime <= Convert.ToDateTime(item)
-                                         && htTimeOut >= Convert.ToDateTime(item))
-                                        {
-                                            lastHotel = h;
-
-                                            #region 酒店详细信息
-                                            trip += "\r\n00:00 乘车前往酒店";
-                                            trip += "\r\n00:00 抵达酒店办理入住手续";
-
-                                            trip = trip + "\r\n"
-                                            + "          酒店名称:" + h.HotelName + "\r\n"
-                                            + "          酒店地址:" + h.HotelAddress + "\r\n"
-                                            + "          酒店电话:" + h.HotelTel + "      酒店传真:" + (string.IsNullOrWhiteSpace(h.HotelFax) ? "-" : h.HotelFax);
-                                            #endregion
-                                        }
-                                    }
-                                }
-
                                 NewListTravel.Add(new Grp_TravelList
                                 {
                                     CreateTime = DateTime.Now,
@@ -1533,6 +1541,65 @@ namespace travelExport
                                                .Replace("\r\n10:30 公务活动;", string.Empty);
                             });
 
+
+                            //处理标记
+                            foreach (var item in tags)
+                            {
+                                bool isEnter = false;
+                                string QueryTag = item.QueryTag;
+                                if (QueryTag.Contains("\r\n\r\n"))
+                                {
+                                    QueryTag = QueryTag.Replace("\r\n\r\n", "\r\n");
+                                    isEnter = true;
+                                }
+
+                                var NewListTravelToTags = NewListTravel
+                                                        .Where(x => x.Trip.Contains(QueryTag)
+                                                            && x.Date == item.Date.ToString("M月d日"))
+                                                        .ToList();
+                                if (NewListTravelToTags.Count > 0 && !item.IsFinally)
+                                {
+                                    foreach (var x in NewListTravelToTags)
+                                    {
+                                        var tagIndex = x.Trip.IndexOf(QueryTag);
+                                        tagIndex += tagContent.tagLength;
+                                        var insertStr = tagContent.TagFormat.Item1 + item.Content + tagContent.TagFormat.Item2;
+                                        if (isEnter)
+                                        {
+                                            insertStr += "\r\n";
+                                        }
+                                        x.Trip = x.Trip.Insert(tagIndex, insertStr);
+                                        NewListTravel.ForEach(x1 => { if (x.Date == x1.Date) { x1.Trip = x.Trip; } });
+                                        break;
+                                    }
+                                }
+                                else
+                                {
+                                    foreach (var x in NewListTravel)
+                                    {
+                                        if (x.Date == item.Date.ToString("M月d日"))
+                                        {
+                                            x.Trip += "\r\n" + tagContent.TagFormat.Item1 + item.Content + tagContent.TagFormat.Item2;
+                                            break;
+                                        }
+                                    }
+                                }
+                            }
+
+                            //比较上一次的文本
+                            //foreach (var newItem in NewListTravel)
+                            //{
+                            //    foreach (var oldItem in listTravel)
+                            //    {
+                            //        if (newItem.Date == oldItem.Date)
+                            //        {
+                            //            var str = CheckStr(oldItem.Trip, newItem.Trip);
+                            //            break;
+                            //        }
+                            //    }
+                            //}
+
+
                             db.Grp_TravelList.RemoveRange(db.Grp_TravelList.Where(x => x.IsDel == 0 && x.Diid == diid));
                             db.Grp_TravelList.AddRange(NewListTravel);
                             db.SaveChanges();
@@ -1551,7 +1618,8 @@ namespace travelExport
         }
 
         private delegate void ChildThreadExceptionHandler(string message);
-
+        
+        //导出文件
         private void btnOutput_Click(object sender, EventArgs e)
         {
             var diid = -1;
@@ -1583,6 +1651,9 @@ namespace travelExport
                 return;
             }
 
+            // 指定的文字
+            var targetTextDic = new Dictionary<string,List<string>>();
+
             if (tempValue != 1)
             {
                 execTemp(tempValue,diid, di);
@@ -1603,6 +1674,12 @@ namespace travelExport
             //获取数据,放到datatable
             foreach (var item in _travelList)
             {
+                targetTextDic.Add(item.Date, ExtractContentBetweenTags(item.Trip, "[++]"));
+
+                item.Trip = item.Trip.Replace(tagContent.TagFormat.Item1, string.Empty)
+                                     .Replace(tagContent.TagFormat.Item2, string.Empty)
+                                     .Replace("[++]", string.Empty);
+
                 DataRow dr = dtSource.NewRow();
                 dr["Days"] = item.Days;
                 dr["Date"] = item.Date;
@@ -1706,6 +1783,58 @@ namespace travelExport
                 tableOne.Rows.RemoveAt(1 + dtSource.Rows.Count);//(1+dtSource.Rows.Count + 1)-1
             }
 
+            var targetTextList = new List<string>();
+
+            // 遍历文档中的表格
+            foreach (Table table in doc.GetChildNodes(NodeType.Table, true))
+            {
+                foreach (Row row in table.Rows)
+                {
+                    var cellIndex = 0;
+
+                    foreach (Cell cell in row.Cells)
+                    {
+                        if (cellIndex == 1 && row.Cells.Count == 4 )
+                        {
+                            foreach (var target in targetTextDic.Keys)
+                            {
+                                if (cell.GetText().Contains(target))
+                                {
+                                    targetTextList = targetTextDic[target];
+                                    break;
+                                }
+                            }
+                        }
+
+                        // 遍历单元格中的所有段落
+                        foreach (Paragraph paragraph in cell.Paragraphs)
+                        {
+                            // 使用临时列表存储原始 Runs,避免修改时影响遍历
+                            List<Run> runs = new List<Run>();
+                            foreach (Run run in paragraph.Runs)
+                            {
+                                runs.Add(run);
+                            }
+                            foreach (Run run in runs)
+                            {
+                                foreach (var targetText in targetTextList)
+                                {
+                                    // 检查文字是否包含指定文字
+                                    if (run.Text.Contains(targetText))
+                                    {
+                                        // 将包含的部分设置为红色
+                                        HighlightText(run, targetText, Color.Red);
+                                    }
+                                }
+                            }
+                        }
+
+                        cellIndex++;
+                    }
+                }
+            }
+
+
             string savePath = "C:\\OP行程单\\";
             if (!Directory.Exists(savePath))
             {
@@ -1732,6 +1861,26 @@ namespace travelExport
             }
         }
 
+        List<string> ExtractContentBetweenTags(string input, string tag)
+        {
+            List<string> results = new List<string>();
+
+            // 构造正则表达式,匹配形如 [++]内容[++] 的部分
+            string pattern = $@"{Regex.Escape(tag)}(.*?){Regex.Escape(tag)}";
+
+            // 添加 RegexOptions.Singleline 选项使 . 匹配包括换行符在内的所有字符
+            MatchCollection matches = Regex.Matches(input, pattern, RegexOptions.Singleline);
+
+            string[] strValueArr = new string[1];
+            foreach (Match match in matches)
+            {
+                var strValue =  match.Groups[1].Value;
+                strValueArr = Regex.Split(strValue, @"\r\n|\n");
+            }
+            results.AddRange(strValueArr.Where(x=>!string.IsNullOrWhiteSpace(x)));
+            return results;
+        }
+
         private string GetAllCity(int diid)
         {
             //城市缓存
@@ -2383,7 +2532,7 @@ namespace travelExport
         }
 
 
-        string GetTimeGwInfo(ref List<Res_OfficialActivities> listgw, DateTime gwTime, string nowCity)
+        string GetTimeGwInfo(ref List<Res_OfficialActivities> listgw, DateTime gwTime)
         {
             List<Res_OfficialActivitiesFormat> formatResGw = listgw.Select(x => new Res_OfficialActivitiesFormat
             {
@@ -2474,21 +2623,6 @@ namespace travelExport
                     }
                 }
             }
-
-            //判断是否在一个城市
-            if (isFindTimeGw && gl != null && !string.IsNullOrEmpty(gl.Area))
-            {
-                if (gl.Area.Contains(nowCity))
-                {
-
-                }
-                else
-                {
-                    var ApiResult = GetDirectionByGoogleApiFormat(nowCity + "市区", gl.Area);
-                    info = info.Insert(0, $"{newGwTime.AddHours(-1).ToString("HH:mm")} 搭乘专车前往{gl.Area}(路程约{ApiResult.Distance},距离约{ApiResult.Time})\r\n");
-                }
-            }
-
             return info;
         }
 
@@ -2612,10 +2746,11 @@ namespace travelExport
                         break;
                     case Operation.INSERT:
                         // 插入的部分,添加标记并添加到最终字符串
-                        finalString.Append("[+]" + diff.text+ "[+]");
+                        finalString.Append("[++]" + diff.text+ "[++]");
                         break;
                     case Operation.DELETE:
                         // 删除的部分,不添加到最终字符串
+                        finalString.Append("[++]" + diff.text + "[++]");
                         break;
                 }
             }
@@ -2624,10 +2759,67 @@ namespace travelExport
             return finalString.ToString();
         }
 
+        private Popover popover;
         private void airLable_Click(object sender, EventArgs e)
         {
+            // 创建新的弹出框
+            popover = new Popover
+            {
+                Size = new Size(150, 150) // 设置弹出框大小
+            };
+
+            // 调整弹出框位置
+            popover.AdjustPosition(airLable);
 
+            popover.Show(); // 显示弹出框
         }
+
+        // 高亮包含指定文字的部分
+        void HighlightText(Run run, string targetText, Color color)
+        {
+            string text = run.Text;
+            int startIndex = text.IndexOf(targetText, StringComparison.Ordinal);
+
+            if (startIndex >= 0)
+            {
+                Paragraph parentParagraph = (Paragraph)run.ParentNode;
+
+                // 前部分
+                if (startIndex > 0)
+                {
+                    Run beforeRun = (Run)run.Clone(true);
+                    beforeRun.Text = text.Substring(0, startIndex);
+                    parentParagraph.InsertBefore(beforeRun, run);
+                }
+
+                // 匹配部分
+                Run matchRun = (Run)run.Clone(true);
+                matchRun.Text = text.Substring(startIndex, targetText.Length);
+                matchRun.Font.Color = color;
+                parentParagraph.InsertBefore(matchRun, run);
+
+                // 后部分
+                if (startIndex + targetText.Length < text.Length)
+                {
+                    Run afterRun = (Run)run.Clone(true);
+                    afterRun.Text = text.Substring(startIndex + targetText.Length);
+                    parentParagraph.InsertBefore(afterRun, run);
+                }
+
+                // 删除原始Run
+                run.Remove();
+            }
+        }
+
+        string ReplaceNewlineIfNeeded(string input)
+        {
+            if (!input.Contains("\r\n") && input.Contains("\n"))
+            {
+                input = input.Replace("\n", "\r\n");
+            }
+            return input;
+        }
+
     }
 
     public class GoogleApiFormat

+ 46 - 0
travelExport/Popover.Designer.cs

@@ -0,0 +1,46 @@
+namespace travelExport
+{
+    partial class Popover
+    {
+        /// <summary>
+        /// Required designer variable.
+        /// </summary>
+        private System.ComponentModel.IContainer components = null;
+
+        /// <summary>
+        /// Clean up any resources being used.
+        /// </summary>
+        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+            {
+                components.Dispose();
+            }
+            base.Dispose(disposing);
+        }
+
+        #region Windows Form Designer generated code
+
+        /// <summary>
+        /// Required method for Designer support - do not modify
+        /// the contents of this method with the code editor.
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.SuspendLayout();
+            // 
+            // Popover
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.ClientSize = new System.Drawing.Size(800, 450);
+            this.Name = "Popover";
+            this.Text = "Popover";
+            this.ResumeLayout(false);
+
+        }
+
+        #endregion
+    }
+}

+ 108 - 0
travelExport/Popover.cs

@@ -0,0 +1,108 @@
+using System;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Windows.Forms;
+
+namespace travelExport
+{
+    public partial class Popover : Form
+    {
+
+        private int triangleSize = 10; // 小三角的边长
+        private int triangleOffset; // 小三角的水平偏移量(相对于弹出框)
+
+        public Popover()
+        {
+            // 初始化弹出框样式
+            this.FormBorderStyle = FormBorderStyle.None; // 无边框
+            this.StartPosition = FormStartPosition.Manual; // 手动设置位置
+            this.BackColor = Color.White; // 背景颜色
+            this.Padding = new Padding(10); // 内边距
+            this.ShowInTaskbar = false; // 不在任务栏显示
+            this.Opacity = 0.95; // 设置透明度
+            this.Size = new Size(300, 150); // 默认大小
+
+            // 圆角边框设置
+            this.Load += (s, e) => ApplyRoundedCorners();
+
+            // 当弹出框失去焦点时关闭
+            this.Deactivate += (s, e) => this.Close();
+
+            // 添加内容
+            Label label = new Label
+            {
+                Text = "这是一个带指向箭头的圆角弹出框",
+                AutoSize = true,
+                Dock = DockStyle.Top
+            };
+
+            Button button = new Button
+            {
+                Text = "确认",
+                Dock = DockStyle.Bottom
+            };
+            button.Click += (s, e) => this.Close(); // 点击关闭弹出框
+
+            this.Controls.Add(button);
+            //this.Controls.Add(label);
+        }
+
+        // 圆角和三角指向处理
+        private void ApplyRoundedCorners()
+        {
+            int cornerRadius = 20; // 圆角半径
+            var path = new GraphicsPath();
+
+            // 绘制上方圆角矩形
+            path.AddArc(0, triangleSize, cornerRadius, cornerRadius, 180, 90);
+            path.AddArc(this.Width - cornerRadius, triangleSize, cornerRadius, cornerRadius, 270, 90);
+            path.AddArc(this.Width - cornerRadius, this.Height - cornerRadius, cornerRadius, cornerRadius, 0, 90);
+            path.AddArc(0, this.Height - cornerRadius, cornerRadius, cornerRadius, 90, 90);
+            path.CloseFigure();
+
+            // 绘制小三角
+            var triangle = new PointF[]
+            {
+            new PointF(triangleOffset - triangleSize / 2, triangleSize),
+            new PointF(triangleOffset + triangleSize / 2, triangleSize),
+            new PointF(triangleOffset, 0)
+            };
+            path.AddPolygon(triangle);
+
+            this.Region = new Region(path);
+        }
+
+        // 根据目标控件的位置调整弹出框的位置
+        public void AdjustPosition(Control target, int margin = 10)
+        {
+            var screenBounds = Screen.FromControl(target).WorkingArea;
+            var targetBounds = target.RectangleToScreen(target.ClientRectangle);
+
+            // 默认弹出框显示在控件下方
+            var x = targetBounds.Left;
+            var y = targetBounds.Bottom + margin;
+
+            // 如果弹出框超出屏幕右侧,调整到控件右侧
+            if (x + this.Width > screenBounds.Right)
+            {
+                x = screenBounds.Right - this.Width - margin;
+            }
+
+            // 如果弹出框超出屏幕下方,调整到控件上方
+            if (y + this.Height > screenBounds.Bottom)
+            {
+                y = targetBounds.Top - this.Height - margin;
+            }
+
+            // 确保弹出框不会超出屏幕左侧或顶部
+            x = Math.Max(x, screenBounds.Left + margin);
+            y = Math.Max(y, screenBounds.Top + margin);
+
+            this.Location = new Point(x, y);
+
+            // 计算小三角的水平偏移量
+            triangleOffset = targetBounds.Left + targetBounds.Width / 2 - x;
+        }
+
+    }
+}

+ 120 - 0
travelExport/Popover.resx

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 9 - 0
travelExport/travelExport.csproj

@@ -337,6 +337,12 @@
     <Compile Include="Pm_WageSheet.cs">
       <DependentUpon>DB.tt</DependentUpon>
     </Compile>
+    <Compile Include="Popover.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="Popover.Designer.cs">
+      <DependentUpon>Popover.cs</DependentUpon>
+    </Compile>
     <Compile Include="Program.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Res_AirCompany.cs">
@@ -452,6 +458,9 @@
     <EmbeddedResource Include="Home.resx">
       <DependentUpon>Home.cs</DependentUpon>
     </EmbeddedResource>
+    <EmbeddedResource Include="Popover.resx">
+      <DependentUpon>Popover.cs</DependentUpon>
+    </EmbeddedResource>
     <EmbeddedResource Include="Properties\Resources.resx">
       <Generator>ResXFileCodeGenerator</Generator>
       <LastGenOutput>Resources.Designer.cs</LastGenOutput>