Bläddra i källkod

feat: 接收单、使用单

JaneDoe 2 månader sedan
förälder
incheckning
eda5ffb5bf

+ 1 - 0
UniformMaterialManagementSystem/App.xaml.cs

@@ -117,6 +117,7 @@ namespace UniformMaterialManagementSystem
             services.AddTransient<SelectApplyContractDialogViewModel>();
             services.AddTransient<InspectionOrganizationPageViewModel>();
             services.AddTransient<MaterialReceiptViewModel>();
+            services.AddTransient<MaterialUsageViewModel>();
 
             // Service 注册
             services.AddScoped(typeof(IDataBaseService<>), typeof(DataBaseService<>));

+ 70 - 71
UniformMaterialManagementSystem/ViewModels/DeliveryReceiptViewModel.cs

@@ -43,6 +43,9 @@ namespace UniformMaterialManagementSystem.ViewModels
         [ObservableProperty]
         private ObservableCollection<DeliveryReceipt> _deliveryReceipts = [];
 
+        [ObservableProperty]
+        private ObservableCollection<DeliveryReceiptDetail> _deliveryReceiptDetails = [];
+
         [ObservableProperty]
         private DeliveryReceipt? _currDeliveryReceipt = null;
 
@@ -309,6 +312,9 @@ namespace UniformMaterialManagementSystem.ViewModels
                 if (args.AddedItems[0] is not DeliveryReceipt newDelivery) { return; }
                 fdgDelivery.SelectedItem = newDelivery; // 刚刚为了界面数据赋值,所以这里需要设置为切换后的行
                 //fdgDelivery.Items.Refresh(); // 不必刷新前台界面
+
+                // 更新子表的行
+                DeliveryReceiptDetails = new ObservableCollection<DeliveryReceiptDetail>(newDelivery.DeliveryReceiptDetails);
             }
 
             // 取消屏蔽行切换
@@ -410,7 +416,7 @@ namespace UniformMaterialManagementSystem.ViewModels
 
             // 记录包号和材料批号信息
             Dictionary<string, string> packBatckNos = new Dictionary<string, string>();
-            foreach (DeliveryReceiptDetail dtl in CurrDeliveryReceipt.DeliveryReceiptDetails)
+            foreach (DeliveryReceiptDetail dtl in DeliveryReceiptDetails)
             {
                 packBatckNos.Add(dtl.PacketNo, dtl.BatchNo);
             }
@@ -480,7 +486,7 @@ namespace UniformMaterialManagementSystem.ViewModels
                 CurrDeliveryReceipt.IsNewRow = false;
                 _service.Insert(CurrDeliveryReceipt);
 
-                foreach (DeliveryReceiptDetail dtl in CurrDeliveryReceipt.DeliveryReceiptDetails)
+                foreach (DeliveryReceiptDetail dtl in DeliveryReceiptDetails)
                 {
                     dtl.IsNewRow = false;
                     serviceDtl?.Insert(dtl);
@@ -506,24 +512,22 @@ namespace UniformMaterialManagementSystem.ViewModels
                 // 已保存行更新到数据库
                 _service.Update(CurrDeliveryReceipt);
 
-                foreach (DeliveryReceiptDetail dtl in CurrDeliveryReceipt.DeliveryReceiptDetails)
+                // 如果子表新增行,则需要反写检验申请明细
+                foreach (DeliveryReceiptDetail dtl in DeliveryReceiptDetails)
                 {
                     if (dtl.IsNewRow)
                     {
-                        dtl.IsNewRow = false;
-                        serviceDtl?.Insert(dtl);
-                    }
-                    else
-                    {
-                        serviceDtl?.Update(dtl);
+                        needUpdtIAD = true;
+                        break;
                     }
                 }
+
+                // 主表没更新,子表新增/删除、更新时,不需要手动执行 Insert/Update ,EFCore 在 SaveChanges() 时自动保存到数据库。
+                // 子表新增: 如果使用 CurrDeliveryReceipt.DRDetails 绑定前台,不使用 [ObservableProperty],那么后台新增时,DbContext 识别不到新增行,SaveChanges() 会抛出异常:DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but acctually affcted 0 row(s)
             }
 
             // 提示保存结果
             bool isMainSuccess = _service.SaveChanges();
-            // todo 受影响行数为 0 ,但保存到数据库了
-            bool? isDtlSuccess = serviceDtl?.SaveChanges();
             if (!isMainSuccess)
             {
                 MessageBox.Show("数据库保存失败!", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
@@ -532,11 +536,13 @@ namespace UniformMaterialManagementSystem.ViewModels
             MessageBox.Show("当前行保存成功。");
 
             // 反写合同明细数据
+            StringBuilder updateHint = new StringBuilder();
             bool isSuccCD = false;
             if (needUpdtCD)
             {
                 isSuccCD = UpdataContractDetail(CurrDeliveryReceipt.ContractNo, CurrDeliveryReceipt.ProductName);
                 if (!isSuccCD) { return; } // 反写不成功会提示异常
+                updateHint.Append("合同明细、");
             }
 
             // 反写检验申请明细的 ShippedStatus:是否发货完成
@@ -544,18 +550,20 @@ namespace UniformMaterialManagementSystem.ViewModels
             if (needUpdtIAD)
             {
                 Dictionary<string, string> packBatckNos = new Dictionary<string, string>();
-                foreach (DeliveryReceiptDetail dtl in CurrDeliveryReceipt.DeliveryReceiptDetails)
+                foreach (DeliveryReceiptDetail dtl in DeliveryReceiptDetails)
                 {
                     packBatckNos.Add(dtl.PacketNo, dtl.BatchNo);
                 }
                 isSuccAD = UpdateApplyDetail(packBatckNos, true);
                 if (!isSuccCD) { return; }
+                updateHint.Append("检验申请明细、");
             }
 
             // 反写成功提示
-            if (isSuccCD && isSuccAD)
+            if (isSuccCD || isSuccAD)
             {
-                MessageBox.Show("反写合同明细、检验申请明细成功。");
+                updateHint = updateHint.Remove(updateHint.Length - 1, 1);
+                MessageBox.Show($"反写{updateHint}成功。");
             }
         }
 
@@ -574,12 +582,12 @@ namespace UniformMaterialManagementSystem.ViewModels
 
             //  校验子表接收数量列
             StringBuilder nullHint = new StringBuilder();
-            foreach (DeliveryReceiptDetail dtl in CurrDeliveryReceipt.DeliveryReceiptDetails)
+            foreach (DeliveryReceiptDetail dtl in DeliveryReceiptDetails)
             {
                 decimal? receQty = dtl.ReceiveQuantity;
                 if (receQty == null || receQty == 0)
                 {
-                    int index = CurrDeliveryReceipt.DeliveryReceiptDetails.IndexOf(dtl);
+                    int index = DeliveryReceiptDetails.IndexOf(dtl);
                     nullHint.AppendLine($"第 {index + 1} 行");
                 }
             }
@@ -770,10 +778,9 @@ namespace UniformMaterialManagementSystem.ViewModels
 
                         // 汇总大于 0 的行
                         dtl.ShippedQuantity = Math.Round(dtl.ShippedQuantity, 1); // 虽然显示文本自动格式化为1位小数,但字段值还是多位小数,转化为1位小数
-                        ObservableCollection<DeliveryReceiptDetail> details = CurrDeliveryReceipt!.DeliveryReceiptDetails;
-                        int deliCount = details.Where(x => x.ShippedQuantity > 0).Count();
-                        CurrDeliveryReceipt.ShippedPackets = deliCount;
-                        decimal deliQty = details.Where(x => x.ShippedQuantity > 0).Select(x => x.ShippedQuantity).Sum();
+                        int deliCount = DeliveryReceiptDetails.Where(x => x.ShippedQuantity > 0).Count();
+                        CurrDeliveryReceipt!.ShippedPackets = deliCount;
+                        decimal deliQty = DeliveryReceiptDetails.Where(x => x.ShippedQuantity > 0).Select(x => x.ShippedQuantity).Sum();
                         CurrDeliveryReceipt.ShippedQty = deliQty;
                     }
                     else if (cellInfo.Column.Header.Equals("接收数量*"))
@@ -841,7 +848,7 @@ namespace UniformMaterialManagementSystem.ViewModels
 
             // 新增一行子表
             DeliveryReceiptDetail detail = new();
-            CurrDeliveryReceipt!.DeliveryReceiptDetails.Add(detail);
+            DeliveryReceiptDetails.Add(detail);
         }
 
         [RelayCommand]
@@ -862,16 +869,15 @@ namespace UniformMaterialManagementSystem.ViewModels
             fdgDeliveryDetail.UnselectAllCells();
 
             // 没选中行则提示后返回
-            ObservableCollection<DeliveryReceiptDetail> details = CurrDeliveryReceipt.DeliveryReceiptDetails;
             var serviceDtl = App.Current.Services.GetService<IDataBaseService<DeliveryReceiptDetail>>();
             int selectedCount = 0;
             StringBuilder hint = new StringBuilder();
             List<DeliveryReceiptDetail> savedRows = [];
             bool isModified = false;
-            for (int i = details.Count - 1; i >= 0; i--)
+            for (int i = DeliveryReceiptDetails.Count - 1; i >= 0; i--)
             {
                 // 跳过未勾选的明细
-                DeliveryReceiptDetail dtl = details[i];
+                DeliveryReceiptDetail dtl = DeliveryReceiptDetails[i];
                 if (!dtl.IsSelected) { continue; }
 
                 selectedCount++;
@@ -879,7 +885,7 @@ namespace UniformMaterialManagementSystem.ViewModels
                 // 新增行直接删除
                 if (dtl.IsNewRow)
                 {
-                    details.Remove(dtl);
+                    DeliveryReceiptDetails.Remove(dtl);
                     continue;
                 }
 
@@ -899,7 +905,7 @@ namespace UniformMaterialManagementSystem.ViewModels
                             savedRows.Add(dtl);
                             isModified = true;
 
-                            int index = details.IndexOf(dtl);
+                            int index = DeliveryReceiptDetails.IndexOf(dtl);
                             hint.Append($"\n第 {index + 1} 行");
                             break;
                         }
@@ -913,8 +919,8 @@ namespace UniformMaterialManagementSystem.ViewModels
             if (savedRows.Count == 0)
             {
                 // 删除后更新数据
-                CurrDeliveryReceipt.ShippedPackets = details.Count;
-                CurrDeliveryReceipt.ShippedQty = details.Select(x => x.ShippedQuantity).Sum();
+                CurrDeliveryReceipt.ShippedPackets = DeliveryReceiptDetails.Count;
+                CurrDeliveryReceipt.ShippedQty = DeliveryReceiptDetails.Select(x => x.ShippedQuantity).Sum();
                 OnPropertyChanged(nameof(CurrDeliveryReceipt));
                 fdgDeliveryDetail.Items.Refresh(); // 刷新子表索引
                 return;
@@ -934,7 +940,7 @@ namespace UniformMaterialManagementSystem.ViewModels
             foreach (DeliveryReceiptDetail dtl in savedRows)
             {
                 // 界面删除
-                details.Remove(dtl);
+                DeliveryReceiptDetails.Remove(dtl);
                 // 数据库删除
                 serviceDtl?.Delete(dtl);
 
@@ -944,8 +950,8 @@ namespace UniformMaterialManagementSystem.ViewModels
 
             // 删除成功后更新数据
             fdgDeliveryDetail.Items.Refresh();
-            CurrDeliveryReceipt.ShippedPackets = details.Count;
-            CurrDeliveryReceipt.ShippedQty = details.Select(x => x.ShippedQuantity).Sum();
+            CurrDeliveryReceipt.ShippedPackets = DeliveryReceiptDetails.Count;
+            CurrDeliveryReceipt.ShippedQty = DeliveryReceiptDetails.Select(x => x.ShippedQuantity).Sum();
             OnPropertyChanged(nameof(CurrDeliveryReceipt));
 
             // 保存到数据库
@@ -984,7 +990,7 @@ namespace UniformMaterialManagementSystem.ViewModels
             }
 
             // 询问
-            if (CurrDeliveryReceipt.DeliveryReceiptDetails.Count > 0)
+            if (DeliveryReceiptDetails.Count > 0)
             {
                 MessageBoxResult res = MessageBox.Show("导入数据将覆盖当前的明细数据,是否确定导入?", "询问", MessageBoxButton.YesNoCancel, MessageBoxImage.Question);
                 if (res != MessageBoxResult.Yes) { return; }
@@ -997,7 +1003,7 @@ namespace UniformMaterialManagementSystem.ViewModels
                 Filter = "Excel 文件|*.xlsx;*.xls|Excel 工作簿|*.xlsx|Excel 97-2003工作簿|*.xls" // 只能导入Excel
             };
             string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
-            openFileDialog.InitialDirectory = desktopPath; // 默认从桌面获取文件
+            openFileDialog.InitialDirectory = Environment.CurrentDirectory;
             if (openFileDialog.ShowDialog() != true) { return; }
 
             // 获取导入数据
@@ -1224,13 +1230,13 @@ namespace UniformMaterialManagementSystem.ViewModels
 
             // 记录导入前的数据
             Dictionary<string, string> origiPackBatchNos = new Dictionary<string, string>();
-            foreach (DeliveryReceiptDetail dtl in CurrDeliveryReceipt.DeliveryReceiptDetails)
+            foreach (DeliveryReceiptDetail dtl in DeliveryReceiptDetails)
             {
                 origiPackBatchNos.Add(dtl.PacketNo, dtl.BatchNo);
             }
 
             // 清空子表数据
-            foreach (DeliveryReceiptDetail dtl in CurrDeliveryReceipt.DeliveryReceiptDetails)
+            foreach (DeliveryReceiptDetail dtl in DeliveryReceiptDetails)
             {
                 serviceDtl?.Delete(dtl);
             }
@@ -1280,7 +1286,7 @@ namespace UniformMaterialManagementSystem.ViewModels
         [RelayCommand]
         public void ExportDetail()
         {
-            if (CurrDeliveryReceipt == null || CurrDeliveryReceipt.DeliveryReceiptDetails == null) { return; }
+            if (CurrDeliveryReceipt == null || DeliveryReceiptDetails == null) { return; }
 
             // 自动清除新增的空行
             DeleteEmptyRows();
@@ -1300,7 +1306,7 @@ namespace UniformMaterialManagementSystem.ViewModels
                 Filter = "Excel 文件|*.xlsx;*.xls|Excel 工作簿|*.xlsx|Excel 97-2003工作簿|*.xls|所有文件|*.*"
             };
             string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
-            saveFileDialog.InitialDirectory = desktopPath; // 默认保存到桌面。若不设置,默认保存到 文档 路径下
+            saveFileDialog.InitialDirectory = Environment.CurrentDirectory; // 默认保存到 文档 路径下
             string fileName = _exportFileName + "_" + DateTime.Now.ToString("yyyyMMdd_HHmmss");
             saveFileDialog.FileName = fileName; // 默认文件名
             if (saveFileDialog.ShowDialog() != true) { return; }
@@ -1318,11 +1324,10 @@ namespace UniformMaterialManagementSystem.ViewModels
         {
             if (CurrDeliveryReceipt == null) { return; }
 
-            ObservableCollection<DeliveryReceiptDetail> details = CurrDeliveryReceipt.DeliveryReceiptDetails;
-            if (details == null || details.Count == 0) { return; }
+            if (DeliveryReceiptDetails == null || DeliveryReceiptDetails.Count == 0) { return; }
 
             // 全部接收:将发货数量列赋值到接收数量列
-            foreach (DeliveryReceiptDetail dtl in details)
+            foreach (DeliveryReceiptDetail dtl in DeliveryReceiptDetails)
             {
                 dtl.ReceiveQuantity = dtl.ShippedQuantity;
             }
@@ -1333,11 +1338,10 @@ namespace UniformMaterialManagementSystem.ViewModels
         {
             if (CurrDeliveryReceipt == null) { return; }
 
-            ObservableCollection<DeliveryReceiptDetail> details = CurrDeliveryReceipt.DeliveryReceiptDetails;
-            if (details == null || details.Count == 0) { return; }
+            if (DeliveryReceiptDetails == null || DeliveryReceiptDetails.Count == 0) { return; }
 
             // 全部接收:将发货数量列赋值到接收数量列
-            foreach (DeliveryReceiptDetail dtl in details)
+            foreach (DeliveryReceiptDetail dtl in DeliveryReceiptDetails)
             {
                 dtl.UsageQuantity = dtl.ReceiveQuantity;
             }
@@ -1431,15 +1435,15 @@ namespace UniformMaterialManagementSystem.ViewModels
         /// </summary>
         private void DeleteEmptyRows()
         {
-            if (CurrDeliveryReceipt == null || CurrDeliveryReceipt.DeliveryReceiptDetails == null) { return; }
+            if (CurrDeliveryReceipt == null || DeliveryReceiptDetails == null) { return; }
 
             // 删除子表的新增空行:发运数量为空的行
-            var emptyRows = CurrDeliveryReceipt.DeliveryReceiptDetails.Where(x => x.IsNewRow && string.IsNullOrEmpty(x.BatchNo) && string.IsNullOrEmpty(x.PacketNo) && x.ShippedQuantity == 0);
+            var emptyRows = DeliveryReceiptDetails.Where(x => x.IsNewRow && string.IsNullOrEmpty(x.BatchNo) && string.IsNullOrEmpty(x.PacketNo) && x.ShippedQuantity == 0);
             if (emptyRows.Any())
             {
                 for (int i = emptyRows.Count() - 1; i >= 0; i--)
                 {
-                    CurrDeliveryReceipt.DeliveryReceiptDetails.Remove(emptyRows.ElementAt(i));
+                     DeliveryReceiptDetails.Remove(emptyRows.ElementAt(i));
                 }
             }
         }
@@ -1461,10 +1465,10 @@ namespace UniformMaterialManagementSystem.ViewModels
             dtDetail.Columns.Add("备注", typeof(string));
 
             // 创建 DataTable 的行
-            foreach (DeliveryReceiptDetail dtl in CurrDeliveryReceipt!.DeliveryReceiptDetails) // 创建时子表一定不为 null
+            foreach (DeliveryReceiptDetail dtl in DeliveryReceiptDetails) // 创建时子表一定不为 null
             {
                 DataRow dr = dtDetail.NewRow();
-                dr["成品企业"] = CurrDeliveryReceipt.ReceivedCompanyName;
+                dr["成品企业"] = CurrDeliveryReceipt!.ReceivedCompanyName;
                 dr["材料名称"] = CurrDeliveryReceipt.ProductName;
                 dr["材料批号"] = dtl.BatchNo;
                 dr["包号"] = dtl.PacketNo;
@@ -1535,10 +1539,9 @@ namespace UniformMaterialManagementSystem.ViewModels
         /// </summary>
         private void SummaryDeliveryDetail()
         {
-            ObservableCollection<DeliveryReceiptDetail> details = CurrDeliveryReceipt!.DeliveryReceiptDetails;
-            int deliCount = details.Where(x => x.ShippedQuantity > 0).Count();
-            CurrDeliveryReceipt.ShippedPackets = deliCount;
-            decimal deliQty = details.Where(x => x.ShippedQuantity > 0).Select(x => x.ShippedQuantity).Sum();
+            int deliCount = DeliveryReceiptDetails.Where(x => x.ShippedQuantity > 0).Count();
+            CurrDeliveryReceipt!.ShippedPackets = deliCount;
+            decimal deliQty = DeliveryReceiptDetails.Where(x => x.ShippedQuantity > 0).Select(x => x.ShippedQuantity).Sum();
             CurrDeliveryReceipt.ShippedQty = deliQty;
         }
 
@@ -1547,10 +1550,9 @@ namespace UniformMaterialManagementSystem.ViewModels
         /// </summary>
         private void SummaryReceiveDetail()
         {
-            ObservableCollection<DeliveryReceiptDetail> details = CurrDeliveryReceipt!.DeliveryReceiptDetails;
-            int receCount = details.Where(x => x.ReceiveQuantity != null && x.ReceiveQuantity > 0).Count();
-            CurrDeliveryReceipt.ReceivedPackets = receCount;
-            decimal? receQty = details.Where(x => x.ReceiveQuantity != null && x.ReceiveQuantity > 0).Select(x => x.ReceiveQuantity).Sum();
+            int receCount = DeliveryReceiptDetails.Where(x => x.ReceiveQuantity != null && x.ReceiveQuantity > 0).Count();
+            CurrDeliveryReceipt!.ReceivedPackets = receCount;
+            decimal? receQty = DeliveryReceiptDetails.Where(x => x.ReceiveQuantity != null && x.ReceiveQuantity > 0).Select(x => x.ReceiveQuantity).Sum();
             CurrDeliveryReceipt.ReceivedQty = receQty ?? 0;
         }
 
@@ -1559,8 +1561,7 @@ namespace UniformMaterialManagementSystem.ViewModels
         /// </summary>
         private void SummaryUsageDetail()
         {
-            ObservableCollection<DeliveryReceiptDetail> details = CurrDeliveryReceipt!.DeliveryReceiptDetails;
-            foreach (DeliveryReceiptDetail dtl in details)
+            foreach (DeliveryReceiptDetail dtl in DeliveryReceiptDetails)
             {
                 if (dtl.UsageQuantity < dtl.ReceiveQuantity)
                 {
@@ -1587,17 +1588,16 @@ namespace UniformMaterialManagementSystem.ViewModels
             }
 
             // 子表必须有行
-            ObservableCollection<DeliveryReceiptDetail> details = CurrDeliveryReceipt!.DeliveryReceiptDetails;
-            if (CurrDeliveryReceipt.ShippedPackets == 0 || CurrDeliveryReceipt.ShippedQty == 0 || details == null || details.Count == 0)
+            if (CurrDeliveryReceipt!.ShippedPackets == 0 || CurrDeliveryReceipt.ShippedQty == 0 || DeliveryReceiptDetails == null || DeliveryReceiptDetails.Count == 0)
             {
                 MessageBox.Show("发运数量 或 发运包数为 0 ,不允许保存!");
                 return false;
             }
 
             // 检查子表必录项
-            var nullBatchNoRows = CurrDeliveryReceipt!.DeliveryReceiptDetails.Where(x => string.IsNullOrEmpty(x.BatchNo));
-            var nullPackageNoRows = CurrDeliveryReceipt.DeliveryReceiptDetails.Where(x => string.IsNullOrEmpty(x.PacketNo));
-            var nullShippedQty = CurrDeliveryReceipt.DeliveryReceiptDetails.Where(x => x.ShippedQuantity == 0);
+            var nullBatchNoRows =  DeliveryReceiptDetails.Where(x => string.IsNullOrEmpty(x.BatchNo));
+            var nullPackageNoRows = DeliveryReceiptDetails.Where(x => string.IsNullOrEmpty(x.PacketNo));
+            var nullShippedQty = DeliveryReceiptDetails.Where(x => x.ShippedQuantity == 0);
             StringBuilder nullFields = new StringBuilder();
             if (nullBatchNoRows.Any())
             {
@@ -1649,10 +1649,9 @@ namespace UniformMaterialManagementSystem.ViewModels
             }
 
             // 保存时检查材料批号和包号不允许重复
-            var repeatNos = CurrDeliveryReceipt.DeliveryReceiptDetails
-                                               .GroupBy(x => new { BatchNo = x.BatchNo, PacketNo = x.PacketNo })
-                                               .Where(g => g.Count() > 1)
-                                               .Select(g => g.Key);
+            var repeatNos = DeliveryReceiptDetails.GroupBy(x => new { BatchNo = x.BatchNo, PacketNo = x.PacketNo })
+                                                  .Where(g => g.Count() > 1)
+                                                  .Select(g => g.Key);
             if (repeatNos.Any())
             {
                 // 若存在重复项则提示后返回
@@ -1672,7 +1671,7 @@ namespace UniformMaterialManagementSystem.ViewModels
             StringBuilder batchNoMsg = new StringBuilder();
             StringBuilder packNoMsg = new StringBuilder();
             InspectApply? apply = null;
-            foreach (DeliveryReceiptDetail dtl in CurrDeliveryReceipt.DeliveryReceiptDetails)
+            foreach (DeliveryReceiptDetail dtl in DeliveryReceiptDetails)
             {
                 // 根据生产企业、产品名称、材料批号从数据库查询检验申请
                 string batchNo = dtl.BatchNo;
@@ -1744,7 +1743,7 @@ namespace UniformMaterialManagementSystem.ViewModels
         /// <returns></returns>
         private bool VerifyReceipt()
         {
-            var nullReceivedQty = CurrDeliveryReceipt!.DeliveryReceiptDetails.Where(x => x.ReceiveQuantity == 0);
+            var nullReceivedQty = DeliveryReceiptDetails.Where(x => x.ReceiveQuantity == 0);
             if (string.IsNullOrEmpty(CurrDeliveryReceipt.ReceivedStatus))
             {
                 MessageBox.Show("收货单状态为空,不允许保存!");
@@ -1765,8 +1764,8 @@ namespace UniformMaterialManagementSystem.ViewModels
         /// </summary>
         private bool VerifyUsage()
         {
-            var nullUsageQty = CurrDeliveryReceipt!.DeliveryReceiptDetails.Where(x => x.UsageQuantity == 0);
-            if (CurrDeliveryReceipt.Licence == null)
+            var nullUsageQty = DeliveryReceiptDetails.Where(x => x.UsageQuantity == 0);
+            if (CurrDeliveryReceipt!.Licence == null)
             {
                 MessageBox.Show("未上传许可证,不允许保存!");
                 return false;

+ 183 - 90
UniformMaterialManagementSystem/ViewModels/MaterialReceiptViewModel.cs

@@ -1,24 +1,22 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.ComponentModel;
-using System.Diagnostics;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.Collections.ObjectModel;
+using System.Data;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Controls.Primitives;
+using System.Windows.Media;
 using CommunityToolkit.Mvvm.ComponentModel;
 using CommunityToolkit.Mvvm.Input;
-using DocumentFormat.OpenXml.Wordprocessing;
+using DocumentFormat.OpenXml.Drawing;
+using DocumentFormat.OpenXml.Spreadsheet;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Win32;
 using UniformMaterialManagementSystem.Entities;
 using UniformMaterialManagementSystem.Models;
 using UniformMaterialManagementSystem.Services;
 using UniformMaterialManagementSystem.Utils;
 using UniformMaterialManagementSystem.Views;
+using Colors = System.Windows.Media.Colors;
 
 namespace UniformMaterialManagementSystem.ViewModels
 {
@@ -39,7 +37,7 @@ namespace UniformMaterialManagementSystem.ViewModels
         // 行切换事件内部屏蔽自身
         private bool _isExcuting = false;
 
-        private string _exportFileName = "附件3成品生产企业报送接收数据模板";
+        private string _attachmentThreeName = "附件3成品生产企业报送接收数据模板";
 
         private LoginUser? _currUser = App.CurrentUser;
 
@@ -108,6 +106,21 @@ namespace UniformMaterialManagementSystem.ViewModels
             _isExcuting = false;
         }
 
+        [RelayCommand]
+        public void DeliverySelectionChanged(MaterialReceiptPage page)
+        {
+            if (CurrDeliveryReceipt == null) { return; }
+
+            // 已导出附件3的行不允许编辑:主表界面字段、子表、工具栏按钮
+            page.dpReceivedDate.IsEnabled = page.fieldReceivedMan.IsEnabled = page.fieldReceivedTel.IsEnabled = page.cbReceivedStatus.IsEnabled
+            = page.fdgDeliveryDetail.IsEnabled
+            = page.btnSave.IsEnabled = page.btnCheck.IsEnabled = page.btnExportThree.IsEnabled = page.btnReceiveAll.IsEnabled
+            = !CurrDeliveryReceipt.IsExportReceive;
+
+            // 收货单状态更新值
+            page.cbReceivedStatus.SelectedItem = CurrDeliveryReceipt.ReceivedStatus;
+        }
+
         [RelayCommand]
         public void Check(DataGrid fdgDeliveryDetail)
         {
@@ -120,30 +133,42 @@ namespace UniformMaterialManagementSystem.ViewModels
                 return;
             }
 
-            //  校验子表接收数量列
+            //  校验子表接收数量列:为 0 也属于异常
             CheckDetailDataGrid(fdgDeliveryDetail, true);
-            // MessageBox.Show("子表中存在以下为空的接收数量,不允许复核!\n" + nullHint.ToString());
 
             // 更新为已复核
             CurrDeliveryReceipt.ReceivedStatus = "已复核";
+            OnPropertyChanged(nameof(CurrDeliveryReceipt));
+
+            // 保存到数据库
+            _service.Update(CurrDeliveryReceipt);
+            bool isSucc = _service.SaveChanges();
+            if (!isSucc)
+            {
+                MessageBox.Show("更新接收状态失败!");
+                return;
+            }
         }
 
-        // todo 
         [RelayCommand]
-        public void Save(ComboBox cbReceivedStatus)
+        public void Save(DataGrid fdgDeliveryDetail)
         {
             // 未选择行或者当前行已导出,直接返回
             if (CurrDeliveryReceipt == null) { return; }
             if (CurrDeliveryReceipt.IsExportReceive) { return; }
 
             // 若 收货单状态 为空,默认赋值为 未复核
-            var selectedStatus = cbReceivedStatus.SelectedItem;
-            if (selectedStatus == null)
+            string status = CurrDeliveryReceipt.ReceivedStatus ?? "";
+            if (string.IsNullOrEmpty(status))
             {
-                cbReceivedStatus.SelectedItem = "未复核";
+                CurrDeliveryReceipt.ReceivedStatus = "未复核";
             }
 
-            // 子表存在空值时
+            //  校验子表接收数量列:不校验为 0 的行
+            bool isValid = CheckDetailDataGrid(fdgDeliveryDetail, false);
+            if (!isValid) { return; }
+
+            // 若存在接收数量为 0 的行,提示用户
             var nullReceivedQty = DeliveryReceiptDetails.Where(x => x.ReceiveQuantity == 0);
             if (nullReceivedQty.Any())
             {
@@ -151,78 +176,107 @@ namespace UniformMaterialManagementSystem.ViewModels
                 if (res != MessageBoxResult.Yes) { return; }
             }
 
-            // todo 检查每一行的数据:防止输入文本或超出发运数量时,不切换单元格(获取单元格 DataGridCell ,DataGridCell.Content 是TextBlock/TextBox,获取文本)
-
-
-
-            // 保存到数据库
-            var serviceDtl = App.Current.Services.GetService<IDataBaseService<DeliveryReceiptDetail>>();
-            if (CurrDeliveryReceipt.IsNewRow)
-            {
-                // 新增行插入到数据库
-                CurrDeliveryReceipt.IsNewRow = false;
-                _service.Insert(CurrDeliveryReceipt);
-
-                foreach (DeliveryReceiptDetail dtl in DeliveryReceiptDetails)
-                {
-                    //dtl.IsNewRow = false;
-                    //serviceDtl?.Insert(dtl);
-
-                    // 已有发货数据,所以不用 Insert() 用 Update() todo 要不要分表,如果要分表,需要判断 IsNewRow 后插入/更新
-                    serviceDtl?.Update(dtl);
-                }
-            }
-            else
-            {
-                // 已保存行更新到数据库
-                // todo 主表已保存,但子表明细 新增/删除/修改 怎么保存
-                _service.Update(CurrDeliveryReceipt);
-
-                foreach (DeliveryReceiptDetail dtl in DeliveryReceiptDetails)
-                {
-                    serviceDtl?.Update(dtl);
-                    //if (dtl.IsNewRow)
-                    //{
-                    //    dtl.IsNewRow = false;
-                    //    serviceDtl?.Insert(dtl);
-                    //}
-                    //else
-                    //{
-                    //    serviceDtl?.Update(dtl);
-                    //}
-                }
-            }
+            // todo 主表要不要分表;子表要不要分表?如果分表,判断 IsNewRow 后插入/更新
+            // 已保存行更新到数据库:不分表时,接收单不会新增/删除主表,也不会新增/删除明细,只会更新
+            _service.Update(CurrDeliveryReceipt);
+            // 主表没更新,子表新增、删除时,不需要手动 Insert/Update ,在 SaveChanges() 时自动处理这些变化;
+            // 主表没更新,子表修改时,通常不需要手动 Update ,但可以显式地调用 Update 来确保实体被标记为已修改,然后调用 SaveChanges() 保存到数据库
+            //var serviceDtl = App.Current.Services.GetService<IDataBaseService<DeliveryReceiptDetail>>();
+            //foreach (DeliveryReceiptDetail dtl in DeliveryReceiptDetails)
+            //{
+            //    serviceDtl?.Update(dtl);
+            //}
 
             // 提示保存结果
             bool isMainSuccess = _service.SaveChanges();
-            // todo 受影响行数为 0 ,但保存到数据库了
-            bool? isDtlSuccess = serviceDtl?.SaveChanges();
             if (!isMainSuccess)
             {
                 MessageBox.Show("数据库保存失败!", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
                 return;
             }
-
             MessageBox.Show("保存成功");
         }
 
         [RelayCommand]
-        public void ExportThree()
+        public void ExportThree(MaterialReceiptPage page)
         {
+            if (CurrDeliveryReceipt == null)
+            {
+                MessageBox.Show("请选择一条接收单后导出附件3!");
+                return;
+            }
 
+            bool isChanged = IsChanged(CurrDeliveryReceipt);
+            if (isChanged)
+            {
+                MessageBox.Show("当前接收单未保存,不允许导出!");
+                return;
+            }
+
+            if (CurrDeliveryReceipt.IsExportReceive)
+            {
+                MessageBox.Show("当前接收单已经导出附件3,不允许重复导出!");
+                return;
+            }
+
+            string status = CurrDeliveryReceipt.ReceivedStatus ?? "";
+            if (!status.Equals("已复核"))
+            {
+                MessageBox.Show("当前接收单未复核,不允许导出!");
+                return;
+            }
+
+            // 校验子表数据
+            bool isValid = CheckDetailDataGrid(page.fdgDeliveryDetail, true);
+            if (!isValid) { return; }
+
+            // 校验通过后提示
+            MessageBoxResult res = MessageBox.Show("接收单导出附件3后不允许修改、删除、重复导出,是否确认导出?", "询问", MessageBoxButton.YesNoCancel, MessageBoxImage.Question);
+            if (res != MessageBoxResult.Yes) { return; }
+
+            // 导出对话框
+            SaveFileDialog saveFileDialog = new SaveFileDialog();
+            saveFileDialog.Title = "选择保存文件的路径";
+            saveFileDialog.Filter = "Excel 文件|*.xlsx;*.xls|Excel 工作簿|*.xlsx|Excel 97-2003工作簿|*.xls|所有文件|*.*";
+            saveFileDialog.InitialDirectory = Environment.CurrentDirectory;
+            string fileName = _attachmentThreeName + "-" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".xlsx";
+            saveFileDialog.FileName = fileName; // 默认文件名
+            if (saveFileDialog.ShowDialog() != true) { return; }
+
+            // 导出为 Excel
+            DataTable dtExport = GenerateDataTable();
+            ExcelUtil.CreateExcelFromDataTable(dtExport, saveFileDialog.FileName);
+            MessageBox.Show("导出成功。");
+
+            // 导出状态设置为已导出
+            CurrDeliveryReceipt.IsExportReceive = true;
+            _service.Update(CurrDeliveryReceipt);
+            bool isSucc = _service.SaveChanges();
+            if (!isSucc)
+            {
+                MessageBox.Show("更新导出状态失败,请联系系统管理员!");
+                return;
+            }
+
+            // 当前界面禁止编辑
+            page.dpReceivedDate.IsEnabled = page.fieldReceivedMan.IsEnabled = page.fieldReceivedTel.IsEnabled = page.cbReceivedStatus.IsEnabled
+            = page.fdgDeliveryDetail.IsEnabled
+            = page.btnSave.IsEnabled = page.btnCheck.IsEnabled = page.btnExportThree.IsEnabled = page.btnReceiveAll.IsEnabled
+            = false;
         }
 
+        // todo 
         [RelayCommand]
         public void ExportDB()
         {
-            // 成品企业-接收单导出附件3的时候赋值 IsExportReceive 字段为 true:导出后不可编辑,不可重复导出、
-
+            // 成品企业-接收单导出附件3的时候赋值 IsExportReceive 字段为 true:导出后不可编辑,不可重复导出
             if (CurrDeliveryReceipt != null)
             {
                 bool isChanged = IsChanged(CurrDeliveryReceipt);
                 if (isChanged)
                 {
                     MessageBox.Show("当前发货单未保存,请保存后导出数据包!");
+                    return;
                 }
             }
 
@@ -304,12 +358,14 @@ namespace UniformMaterialManagementSystem.ViewModels
 
                     // todo 若输入的值不是 decimal,提示!
 
-                    // 校验发运数量
+                    // 输入时自动转换为一位小数
                     DeliveryReceiptDetail? dtl = cellInfo.Item as DeliveryReceiptDetail;
+                    if (dtl == null) { continue; }
+                    dtl.ReceiveQuantity = Math.Round(dtl.ReceiveQuantity ?? 0, 1);
+
+                    // 校验接收数量
                     if (dtl?.ReceiveQuantity < 0)
                     {
-                        DeliveryReceiptDetail? dtl0 = DeliveryReceiptDetails[0];
-                        if (dtl0 == dtl) { } // true
                         dtl.ReceiveQuantity = 0; // todo 使用 ObserveCollection[] 会不会自动刷新前台?
                         fdgDeliveryDetail.Items.Refresh(); // 有效,但难免效率低一些
                         //OnPropertyChanged(nameof(DeliveryReceiptDetails)); // 无效。
@@ -343,31 +399,31 @@ namespace UniformMaterialManagementSystem.ViewModels
             // 单元格切换时,Added 里的是新单元格;Removed是旧单元格
             if (e.AddedCells.Count > 0)
             {
-                foreach (DataGridCellInfo cellInfo in e.AddedCells)
-                {
-                    // 进入单元格时自动进入编辑状态
-                    fdgDeliveryDetail.BeginEdit();
+                // 进入单元格时自动进入编辑状态
+                fdgDeliveryDetail.BeginEdit();
 
-                    // 勾选/取消勾选行
-                    if (cellInfo.Column is DataGridCheckBoxColumn colCheck)
-                    {
-                        if (cellInfo.Item is not DeliveryReceiptDetail dtl) { continue; }
-                        dtl.IsSelected = !dtl.IsSelected;
-                        fdgDeliveryDetail.CommitEdit(); // 前台刷新勾选状态
-                    }
-                }
+                //foreach (DataGridCellInfo cellInfo in e.AddedCells)
+                //{
+                //    // 勾选/取消勾选行
+                //    if (cellInfo.Column is DataGridCheckBoxColumn colCheck)
+                //    {
+                //        if (cellInfo.Item is not DeliveryReceiptDetail dtl) { continue; }
+                //        dtl.IsSelected = !dtl.IsSelected;
+                //        fdgDeliveryDetail.CommitEdit(); // 前台刷新勾选状态
+                //    }
+                //}
             }
         }
 
         [RelayCommand]
-        public void ReceiveAll(MaterialReceiptPage page)
+        public void ReceiveAll(DataGrid fdgDeliveryDetail)
         {
             if (CurrDeliveryReceipt == null) { return; }
             if (DeliveryReceiptDetails == null || DeliveryReceiptDetails.Count == 0) { return; }
 
             // 提交编辑:
-            page.fdgDeliveryDetail.CommitEdit();
-            //page.fdgDeliveryDetail.UnselectAllCells(); // 会触发单元格切换,这里不用校验发货数量,直接更新就好
+            fdgDeliveryDetail.CommitEdit();
+            //fdgDeliveryDetail.UnselectAllCells(); // 会触发单元格切换,这里不用校验发货数量,直接更新就好
 
             // 全部接收:将子表发货数量列赋值到接收数量列
             var serviceDtl = App.Current.Services.GetService<IDataBaseService<DeliveryReceiptDetail>>();
@@ -433,8 +489,8 @@ namespace UniformMaterialManagementSystem.ViewModels
             OnPropertyChanged(nameof(CurrDeliveryReceipt));
             //OnPropertyChanged(nameof(CurrDeliveryReceipt.DeliveryReceiptDetails));
             // 刷新子表数据:值已更新,但没有显示
-            page.fdgDeliveryDetail.CommitEdit(); // 当正在编辑单元格时,刷新会报错(单元格在编辑状态时直接点击 全部接收 按钮会报错)
-            page.fdgDeliveryDetail.Items.Refresh();
+            fdgDeliveryDetail.CommitEdit(); // 当正在编辑单元格时,刷新会报错(单元格在编辑状态时直接点击 全部接收 按钮会报错)
+            fdgDeliveryDetail.Items.Refresh();
         }
 
         /// <summary>
@@ -475,7 +531,7 @@ namespace UniformMaterialManagementSystem.ViewModels
             }
 
             // 已保存的行检查数据库状态
-            EntityState state = _service.Entry(deliveryReceipt);
+            EntityState state = _service.Entry(deliveryReceipt); // 会自动提交编辑
             if (state != EntityState.Unchanged)
             {
                 return true;
@@ -484,10 +540,11 @@ namespace UniformMaterialManagementSystem.ViewModels
             // 检查子表行
             foreach (DeliveryReceiptDetail detail in deliveryReceipt.DeliveryReceiptDetails)
             {
-                if (detail.IsNewRow)
-                {
-                    return true;
-                }
+                // 接收单时子表行必然存在,不判断 IsNewRow
+                //if (detail.IsNewRow)
+                //{
+                //    return true;
+                //}
 
                 // 会自动提交子表的编辑,不用手动 CommitEdit()
                 EntityState? detailState = App.Current.Services.GetService<IDataBaseService<DeliveryReceiptDetail>>()?.Entry(detail);
@@ -500,6 +557,9 @@ namespace UniformMaterialManagementSystem.ViewModels
             return false;
         }
 
+        /// <summary>
+        /// 检查明细表数据
+        /// </summary>
         private bool CheckDetailDataGrid(DataGrid fdgDeliveryDetail, bool isCheckZero)
         {
             foreach (DeliveryReceiptDetail dtl in fdgDeliveryDetail.Items)
@@ -560,5 +620,38 @@ namespace UniformMaterialManagementSystem.ViewModels
 
             return true;
         }
+
+        /// <summary>
+        /// 界面数据转换为 DataTable
+        /// </summary>
+        /// <returns></returns>
+        private DataTable GenerateDataTable()
+        {
+            // 创建 DataTable 的列
+            DataTable dtExport = new DataTable();
+            dtExport.Columns.Add("成品企业", typeof(string));
+            dtExport.Columns.Add("材料名称", typeof(string));
+            dtExport.Columns.Add("材料批号", typeof(string));
+            dtExport.Columns.Add("包号", typeof(string));
+            dtExport.Columns.Add("接收数量", typeof(string));
+            dtExport.Columns.Add("材料企业", typeof(string));
+            dtExport.Columns.Add("备注", typeof(string));
+
+            // 创建 DataTable 的行
+            foreach (DeliveryReceiptDetail dtl in CurrDeliveryReceipt!.DeliveryReceiptDetails) // 创建时子表一定不为 null
+            {
+                DataRow dr = dtExport.NewRow();
+                dr["成品企业"] = CurrDeliveryReceipt.ReceivedCompanyName;
+                dr["材料名称"] = CurrDeliveryReceipt.ProductName;
+                dr["材料批号"] = dtl.BatchNo;
+                dr["包号"] = dtl.PacketNo;
+                dr["接收数量"] = (dtl.ReceiveQuantity ?? 0).ToString("#0.0");
+                dr["材料企业"] = CurrDeliveryReceipt.CompanyName;
+                dr["备注"] = dtl.ShipNote;
+                dtExport.Rows.Add(dr);
+            }
+
+            return dtExport;
+        }
     }
 }

+ 686 - 0
UniformMaterialManagementSystem/ViewModels/MaterialUsageViewModel.cs

@@ -0,0 +1,686 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Data;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using DocumentFormat.OpenXml.Office2016.Drawing.ChartDrawing;
+using DocumentFormat.OpenXml.Spreadsheet;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.ChangeTracking;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Win32;
+using UniformMaterialManagementSystem.Data;
+using UniformMaterialManagementSystem.Entities;
+using UniformMaterialManagementSystem.Models;
+using UniformMaterialManagementSystem.Services;
+using UniformMaterialManagementSystem.Utils;
+using UniformMaterialManagementSystem.Views;
+
+namespace UniformMaterialManagementSystem.ViewModels
+{
+    public partial class MaterialUsageViewModel : ObservableObject
+    {
+        [ObservableProperty]
+        private ObservableCollection<DeliveryReceipt> _deliveryReceipts = [];
+
+        [ObservableProperty]
+        private ObservableCollection<DeliveryReceiptDetail> _deliveryReceiptDetails = [];
+
+        [ObservableProperty]
+        private DeliveryReceipt? _currDeliveryReceipt = null;
+
+        // 主表行切换
+        private bool _isRowChanging = false;
+
+        // 子表单元格切换
+        private bool _isDetailExcuting = false;
+
+        private string _attachmentFourName = "附件4成品生产企业报送使用数据模板";
+
+        private LoginUser? _currUser = App.CurrentUser;
+
+        private IDataBaseService<DeliveryReceipt> _service = null!;
+
+        public MaterialUsageViewModel(IDataBaseService<DeliveryReceipt> service)
+        {
+            _service = service;
+
+            if (_currUser == null || _currUser.CompanyName == null)
+            {
+                MessageBox.Show("获取用户数据失败,请联系系统管理员!");
+                return;
+            }
+
+            LoadUsageData();
+        }
+
+        public void FdgDelivery_SelectionChanged(object sender, SelectionChangedEventArgs args)
+        {
+            // 手动屏蔽本事件
+            if (_isRowChanging) { return; }
+
+            if (sender is not DataGrid fdgDelivery) { return; }
+
+            // 暂时屏蔽行切换事件
+            _isRowChanging = true;
+
+            // 离开行之前,判断当前行是否未保存
+            bool isNeedReset = false;
+            if (args.RemovedItems.Count > 0)
+            {
+                if (args.RemovedItems[0] is not DeliveryReceipt leavingDelivery) { return; }
+                bool isChanged = IsChanged(leavingDelivery); // 会自动提交编辑,不用手动提交编辑了
+                if (isChanged)
+                {
+                    // 当前行未保存时,询问用户之前切换为旧行(为了主表编辑界面的值不切换)
+                    fdgDelivery.SelectedItem = leavingDelivery; // 循环触发本事件(本方法和绑定的命令)
+
+                    // 询问是否切换
+                    MessageBoxResult res = MessageBox.Show("当前行已修改未保存,是否确认切换行?", "询问", MessageBoxButton.YesNoCancel, MessageBoxImage.Question);
+                    // 不切换:退出本事件
+                    if (res != MessageBoxResult.Yes)
+                    {
+                        // todo 效率更高的方法
+                        // 刷新前台主表的选中行
+                        fdgDelivery.Items.Refresh();
+                        // OnPropertyChanged(nameof(CurrDeliveryReceipt)); // 无效
+                        // fdgDelivery.UpdateLayout(); // 无效
+
+                        // 取消屏蔽后返回
+                        _isRowChanging = false;
+                        return;
+                    }
+                    // 改变但切换:需要重新赋值
+                    else
+                    {
+                        // 询问但继续切换时
+                        isNeedReset = true;
+                    }
+                }
+            }
+
+            // 进入新的行时,界面展示新行数据
+            if (args.AddedItems.Count > 0)
+            {
+                if (args.AddedItems[0] is not DeliveryReceipt newDelivery) { return; }
+                if (isNeedReset)
+                {
+                    // todo 默认值是 0 ?
+                    fdgDelivery.SelectedItem = newDelivery; // 重复触发本事件
+                }
+
+                // 更新子表的行
+                DeliveryReceiptDetails = new ObservableCollection<DeliveryReceiptDetail>(newDelivery.DeliveryReceiptDetails);
+            }
+
+            // 取消屏蔽行切换
+            _isRowChanging = false;
+        }
+
+        [RelayCommand]
+        public void DeliverySelectionChanged(MaterialUsagePage page)
+        {
+            if(_isRowChanging) { return; }
+           
+            if (CurrDeliveryReceipt == null) { return; }
+
+            // 未上传附件的使用单 下载许可证 按钮不可用
+            page.btnDownloadLic.IsEnabled = CurrDeliveryReceipt.Licence != null;
+
+            // 已导出附件3的使用单界面按钮和子表不可用
+            page.btnSave.IsEnabled = page.btnExportFour.IsEnabled = page.btnUploadLic.IsEnabled = page.btnUseAll.IsEnabled
+            = page.fdgDeliveryDetail.IsEnabled
+            = !CurrDeliveryReceipt.IsExportUsage;
+        }
+
+        // todo
+        [RelayCommand]
+        public void Save(MaterialUsagePage page)
+        {
+            if (CurrDeliveryReceipt == null) { return; }
+            if (CurrDeliveryReceipt.IsExportUsage) { return; }
+
+            // 保存检查许可证是否录入
+            byte[]? licence = CurrDeliveryReceipt.Licence;
+            if (licence == null)
+            {
+                MessageBox.Show("许可证未上传,不允许保存!");
+//                return;
+            }
+
+            // 检查子表:不允许异常数据,允许为空,未使用/部分使用/全部使用
+            CheckDetailDataGrid(page.fdgDeliveryDetail, false);
+
+            // 已保存行更新到数据库:EFCore 自动跟踪主表和子表
+            _service.Update(CurrDeliveryReceipt);
+            bool isMainSuccess = _service.SaveChanges();
+            if (!isMainSuccess)
+            {
+                MessageBox.Show("数据库保存失败!", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+                return;
+            }
+            MessageBox.Show("当前行保存成功。");
+        }
+
+        [RelayCommand]
+        public void ExportFour(MaterialUsagePage page)
+        {
+            if (CurrDeliveryReceipt == null)
+            {
+                MessageBox.Show("请选择一条使用单后,再导出附件4!");
+                return;
+            }
+
+            bool isChanged = IsChanged(CurrDeliveryReceipt);
+            if (isChanged)
+            {
+                MessageBox.Show("当前使用单未保存,不允许导出!");
+                return;
+            }
+
+            if (CurrDeliveryReceipt.IsExportUsage)
+            {
+                MessageBox.Show("当前接收单已经导出附件4,不允许重复导出!");
+                return;
+            }
+
+            var licence = CurrDeliveryReceipt.Licence;
+            if (licence == null)
+            {
+                MessageBox.Show("当前使用单没有上传许可证,不允许导出!");
+                return;
+            }
+
+            // 校验子表数据
+            bool isValid = CheckDetailDataGrid(page.fdgDeliveryDetail, true);
+            if (!isValid) { return; }
+
+            // 校验通过后提示
+            MessageBoxResult res = MessageBox.Show("使用单导出附件4后不允许修改、删除、重复导出,是否确认导出?", "询问", MessageBoxButton.YesNoCancel, MessageBoxImage.Question);
+            if (res != MessageBoxResult.Yes) { return; }
+
+            // 导出对话框
+            SaveFileDialog saveFileDialog = new SaveFileDialog();
+            saveFileDialog.Title = "选择保存文件的路径";
+            saveFileDialog.Filter = "Excel 文件|*.xlsx;*.xls|Excel 工作簿|*.xlsx|Excel 97-2003工作簿|*.xls|所有文件|*.*";
+            saveFileDialog.InitialDirectory = Environment.CurrentDirectory;
+            string fileName = _attachmentFourName + "-" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".xlsx";
+            saveFileDialog.FileName = fileName; // 默认文件名
+            if (saveFileDialog.ShowDialog() != true) { return; }
+
+            // 导出为 Excel
+            DataTable dtExport = GenerateDataTable();
+            ExcelUtil.CreateExcelFromDataTable(dtExport, saveFileDialog.FileName);
+            MessageBox.Show("导出成功。");
+
+            // 导出状态设置为已导出
+            CurrDeliveryReceipt.IsExportUsage = true;
+            _service.Update(CurrDeliveryReceipt);
+            bool isSucc = _service.SaveChanges();
+            if (!isSucc)
+            {
+                MessageBox.Show("更新导出状态失败,请联系系统管理员!");
+                return;
+            }
+
+            // 当前界面按钮和子表不可用
+            page.btnSave.IsEnabled = page.btnExportFour.IsEnabled = page.btnUploadLic.IsEnabled = page.btnUseAll.IsEnabled = page.fdgDeliveryDetail.IsEnabled = false;
+            //CurrDeliveryReceipt = null;
+        }
+
+        [RelayCommand]
+        public void ExportDB(DataGrid fdgDelivery)
+        {
+            // 检查当前是否存在未保存的行
+            foreach (DeliveryReceipt delivery in DeliveryReceipts)
+            {
+                // 跳过已导出的行
+                if (delivery.IsExportUsage) { continue; }
+
+                bool isChanged = IsChanged(delivery);
+                if (isChanged)
+                {
+                    MessageBox.Show($"使用单 [{delivery.ContractNo}, {delivery.ProductName}] 未保存,请保存后重试!");
+                    return;
+                }
+            }
+
+            // 清空当前行(实现导出后不允许编辑)
+            CurrDeliveryReceipt = null;
+
+            // 导出数据包
+            try
+            {
+                // 选择的发货单导出到 .db 文件:自动迁移数据库
+                DataBaseUtil.ExportTable(DeliveryReceipts, "DeliveryReceipts", true);
+
+                // 选择发货单的明细导出到 .db 文件
+                List<DeliveryReceiptDetail> details = new List<DeliveryReceiptDetail>();
+                foreach (DeliveryReceipt delivery in DeliveryReceipts)
+                {
+                    details.AddRange(delivery.DeliveryReceiptDetails);
+                }
+                DataBaseUtil.ExportTable(details, "DeliveryReceiptDetails", false);
+
+                // 将选择的数据导出为加密数据包
+                string exportPath = DataBaseUtil.ExportData();
+                if (!string.IsNullOrEmpty(exportPath))
+                {
+                    MessageBox.Show("导出成功。");
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show("导出出错:" + ex.Message);
+            }
+        }
+
+        [RelayCommand]
+        public async Task ImportDB()
+        {
+            if (CurrDeliveryReceipt != null)
+            {
+                bool isChanged = IsChanged(CurrDeliveryReceipt);
+                if (isChanged)
+                {
+                    MessageBox.Show("当前发货单未保存,请保存后导入数据包!");
+                    return;
+                }
+            }
+
+            // 从加密数据包中获取出 .db 放到 path 目录下(bin\DataBase 目录)
+            string path = DataBaseUtil.ImportData();
+            if (string.IsNullOrEmpty(path)) { return; }
+
+            // 导入发货单
+            var deliveries = DataBaseUtil.QueryAll<DeliveryReceipt>(path, x => x.Include(x => x.DeliveryReceiptDetails))
+                                         .Result;
+            await DataBaseUtil.ImportTable("DeliveryReceipts", deliveries);
+
+            // 导入发货单明细
+            var drDetails = DataBaseUtil.QueryAll<DeliveryReceiptDetail>(path)
+                                         .Result;
+            await DataBaseUtil.ImportTable("DeliveryReceiptDetails", drDetails);
+
+            MessageBox.Show("导入成功");
+        }
+
+        [RelayCommand]
+        public void UploadLicence()
+        {
+            if (CurrDeliveryReceipt == null) { return; }
+            //if (CurrDeliveryReceipt.IsExportUsage) { return; } // 导出后按钮不可用
+
+            // 已上传则询问覆盖
+            if (CurrDeliveryReceipt.Licence != null)
+            {
+                MessageBoxResult res = MessageBox.Show("当前使用单的许可证已上传,是否移除原来的文件并继续上传?", "询问", MessageBoxButton.YesNoCancel, MessageBoxImage.Question);
+                if (res != MessageBoxResult.Yes) { return; }
+            }
+
+            OpenFileDialog openFileDialog = new OpenFileDialog();
+            openFileDialog.Title = "选择导入文件的路径";
+            openFileDialog.Filter = "PDF 文件|*.pdf";
+            openFileDialog.InitialDirectory = Environment.CurrentDirectory;
+            if (openFileDialog.ShowDialog() != true) { return; }
+
+            // 导入的文件保存到数据库
+            string filePath = openFileDialog.FileName;
+            // File.ReadAllBytes(): 适合小文件和需要一次性读取整个文件内容的场景,代码简洁但内存消耗大。
+            // FileStream: 适合大文件和需要逐块读取文件内容的场景,灵活性高但代码复杂。
+            byte[] licence = File.ReadAllBytes(filePath);
+            CurrDeliveryReceipt.Licence = licence;
+            _service.Update(CurrDeliveryReceipt);
+            bool isSucc = _service.SaveChanges();
+            if (!isSucc)
+            {
+                MessageBox.Show("保存许可证到数据库失败!");
+                return;
+            }
+            MessageBox.Show("上传成功。");
+        }
+
+        [RelayCommand]
+        public void DownloadLicence()
+        {
+            if (CurrDeliveryReceipt == null || CurrDeliveryReceipt.Licence == null) { return; }
+
+            SaveFileDialog saveFileDialog = new SaveFileDialog();
+            saveFileDialog.Title = "选择保存文件的路径";
+            saveFileDialog.Filter = "PDF 文件(*.pdf)|*.pdf";
+            saveFileDialog.InitialDirectory = Environment.CurrentDirectory;
+            if (saveFileDialog.ShowDialog() != true) { return; }
+
+            // byte[] 转文件
+            string path = saveFileDialog.FileName;
+            File.WriteAllBytes(path, CurrDeliveryReceipt.Licence);
+            MessageBox.Show("下载成功。");
+        }
+
+        public void FdgDeliveryDetail_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
+        {
+            if (sender is not DataGrid fdgDeliveryDetail) { return; }
+
+            //if (_isDetailExcuting) { return; }
+
+            _isDetailExcuting = true;
+
+            if (e.RemovedCells.Count > 0)
+            {
+                // 离开单元格时提交编辑
+                fdgDeliveryDetail.CommitEdit();
+
+                // todo 当清空值时,会抛出异常
+                bool isValid = CheckDetailDataGrid(fdgDeliveryDetail, false);
+                if (!isValid)
+                {
+                    _isDetailExcuting = false;
+                    return;
+                }
+
+                // 更新使用状态
+                foreach (DataGridCellInfo cellInfo in e.RemovedCells)
+                {
+                    // 删除行时为 null
+                    if (!cellInfo.IsValid) { continue; }
+
+                    if (!cellInfo.Column.Header.Equals("使用数量*")) { continue; }
+
+                    // 输入时自动转换为一位小数
+                    DeliveryReceiptDetail? dtl = cellInfo.Item as DeliveryReceiptDetail;
+                    if (dtl == null) { continue; }
+                    dtl.UsageQuantity = Math.Round(dtl.UsageQuantity ?? 0, 1);
+
+                    // 校验使用数量
+                    if (dtl.UsageQuantity < 0)
+                    {
+                        dtl.UsageQuantity = 0;
+                        dtl.UsageStatus = "未使用";
+                        fdgDeliveryDetail.Items.Refresh(); // 刷新前台数据
+                        MessageBox.Show("输入的数量异常,请重新输入!");
+
+                        _isDetailExcuting = false;
+                        return;
+                    }
+                    else if (dtl.UsageQuantity > dtl.ReceiveQuantity)
+                    {
+                        dtl.UsageQuantity = dtl.ReceiveQuantity;
+                        dtl.UsageStatus = "全部使用";
+                        fdgDeliveryDetail.Items.Refresh();
+                        MessageBox.Show("使用数量不能超过接收数量,请重新输入!");
+
+                        _isDetailExcuting = false;
+                        return;
+                    }
+
+                    // 更新使用状态
+                    if (dtl.UsageQuantity == null) { continue; }
+                    dtl.UsageStatus = dtl.UsageQuantity == 0 ? "未使用" : dtl.UsageQuantity < dtl.ReceiveQuantity ? "部分使用" : "全部使用";
+
+                    // 获取单元格,给单元格编辑器的文本赋值
+                    int rowIndex = DeliveryReceiptDetails.IndexOf(dtl);
+                    DataGridCell? cell = GetDataGridCell(fdgDeliveryDetail, rowIndex, 5); // 不允许拖动改变列顺序
+                    if (cell == null) { continue; }
+                    if (cell.Content is TextBlock textBlock)
+                    {
+                        textBlock.Text = dtl.UsageStatus;
+                    }
+                }
+
+                // 更新前台界面数据的方法:
+                //// 方法一:
+                //fdgDeliveryDetail.Items.Refresh(); // 会触发本事件(手动屏蔽依然会进入);DataGrid 会失去焦点,导致不能单击编辑
+                //fdgDeliveryDetail.Focus();
+                // 方法二:手动触发值更新事件
+                // todo DeliveryReceiptDetail 类外面包一层 ViewModel ,继承 INotifyPropertyChanged,实现 OnPropertyChanged 事件
+                //OnPropertyChanged(nameof(dtl.UsageStatus));
+                // 方法三:获取单元格,给单元格编辑器的文本赋值
+
+            }
+
+            // 新单元格
+            if (e.AddedCells.Count > 0)
+            {
+                // 进入单元格时自动进入编辑状态
+                fdgDeliveryDetail.BeginEdit();
+            }
+
+            _isDetailExcuting = false;
+        }
+
+        [RelayCommand]
+        public void UseAll(DataGrid fdgDeliveryDetail)
+        {
+            if (CurrDeliveryReceipt == null) { return; }
+            if (CurrDeliveryReceipt.IsExportUsage) { return; }
+            if (DeliveryReceiptDetails == null || DeliveryReceiptDetails.Count == 0) { return; }
+
+            // 当焦点在接收数量列时,检查异常
+            fdgDeliveryDetail.CommitEdit();  // 确保焦点在接收数量列时,刷新数据不会抛出异常
+            bool isValid = CheckDetailDataGrid(fdgDeliveryDetail, false);
+            if (!isValid) { return; }
+
+            // 全部使用:将接受数量列赋值到使用数量列
+            foreach (DeliveryReceiptDetail dtl in DeliveryReceiptDetails)
+            {
+                dtl.UsageQuantity = dtl.ReceiveQuantity;
+                dtl.UsageStatus = "全部使用";
+            }
+            fdgDeliveryDetail.CommitEdit();
+            fdgDeliveryDetail.Items.Refresh();
+
+            if (CurrDeliveryReceipt.Licence == null)
+            {
+                MessageBox.Show("许可证未上传,请上传后手动保存!");
+                return;
+            }
+
+            // 自动保存到数据库
+            _service.Update(CurrDeliveryReceipt);
+
+            bool isSucc = _service.SaveChanges();
+            if (!isSucc)
+            {
+                MessageBox.Show("保存失败,请联系系统管理员!");
+                return;
+            }
+            MessageBox.Show("保存成功。");
+        }
+
+
+
+        /// <summary>
+        /// 加载使用单数据
+        /// </summary>
+        internal void LoadUsageData()
+        {
+            // 筛选当前工作年度、当前成品企业、已复核的发货单
+            var deliveryReceipts = _service.Query(x => x.WorkYear.Equals(_currUser!.WorkYear) && x.ReceivedCompanyName.Equals(_currUser.CompanyName) && (x.ReceivedStatus ?? "").Equals("已复核"))
+                                           .Include(x => x.DeliveryReceiptDetails);
+            DeliveryReceipts = new ObservableCollection<DeliveryReceipt>(deliveryReceipts);
+
+            foreach (DeliveryReceipt delivery in DeliveryReceipts)
+            {
+                // 是否为新行:否
+                delivery.IsNewRow = false;
+
+                // 子表明细行:子表行必然存在
+                //var details = delivery.DeliveryReceiptDetails.OrderBy(x => x.PacketNo);
+                //foreach (DeliveryReceiptDetail dtl in details)
+                //{
+                //    dtl.IsNewRow = false;
+                //}
+                //delivery.DeliveryReceiptDetails = new ObservableCollection<DeliveryReceiptDetail>(details);
+            }
+        }
+
+        /// <summary>
+        /// 判断行是否已修改
+        /// </summary>
+        private bool IsChanged(DeliveryReceipt deliveryReceipt)
+        {
+            // 新增行直接返回 true
+            if (deliveryReceipt.IsNewRow)
+            {
+                return true;
+            }
+
+            // 已保存的行检查数据库状态
+            EntityState state = _service.Entry(deliveryReceipt);
+            if (state != EntityState.Unchanged)
+            {
+                return true;
+            }
+
+            // 检查子表行
+            foreach (DeliveryReceiptDetail detail in deliveryReceipt.DeliveryReceiptDetails)
+            {
+                //if (detail.IsNewRow)
+                //{
+                //    return true;
+                //}
+
+                EntityState? detailState = App.Current.Services.GetService<IDataBaseService<DeliveryReceiptDetail>>()?.Entry(detail);
+                if (detailState != EntityState.Unchanged)
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// 检查明细表数据
+        /// </summary>
+        private bool CheckDetailDataGrid(DataGrid fdgDeliveryDetail, bool isCheckNum)
+        {
+            foreach (DeliveryReceiptDetail dtl in fdgDeliveryDetail.Items)
+            {
+                // 获取行
+                DataGridRow? row = fdgDeliveryDetail.ItemContainerGenerator.ContainerFromItem(dtl) as DataGridRow;
+                if (row == null)
+                {
+                    // 获取失败则滚动界面后重试
+                    fdgDeliveryDetail.UpdateLayout();
+                    fdgDeliveryDetail.ScrollIntoView(dtl);
+                    row = fdgDeliveryDetail.ItemContainerGenerator.ContainerFromItem(dtl) as DataGridRow;
+                }
+                if (row == null) { continue; }
+
+                // 获取单元格
+                DataGridCellsPresenter? cellsPresenter = VisualUtil.FindVisualChild<DataGridCellsPresenter>(row);
+                DataGridCell? cell = cellsPresenter?.ItemContainerGenerator.ContainerFromIndex(4) as DataGridCell; // 不允许拖动改变列顺序
+                if (cell == null) { continue; }
+
+                // 获取单元格的值
+                string cellValue = "";
+                if (cell.Content is TextBox textBox)
+                {
+                    cellValue = textBox.Text;
+                }
+                else if (cell.Content is TextBlock textBlock)
+                {
+                    cellValue = textBlock.Text;
+                }
+
+                if (string.IsNullOrWhiteSpace(cellValue))
+                {
+                    dtl.UsageQuantity = 0;
+                    cellValue = "0.0";
+                }
+
+                // 非数值型
+                bool isValid = decimal.TryParse(cellValue, out decimal useQty);
+                if (!isValid)
+                {
+                    MessageBox.Show("子表[使用数量]列存在异常数据,请检查数据后重试!");
+                    return false;
+                }
+                // 负数
+                if (useQty < 0)
+                {
+                    MessageBox.Show("子表[使用数量]列存在异常数据,请检查数据后重试!");
+                    return false;
+                }
+                // 0
+                if (isCheckNum && useQty == 0)
+                {
+                    MessageBox.Show("子表[使用数量]列存在为 0 的数据,请检查数据后重试!");
+                    return false;
+                }
+                // 超出发运数量
+                if (useQty > dtl.ShippedQuantity)
+                {
+                    MessageBox.Show("子表[使用数量]列存在为超出[接收数量]的数据,请检查数据后重试!");
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// 获取单元格
+        /// </summary>
+        private DataGridCell? GetDataGridCell(DataGrid dataGrid, int rowIndex, int colIndex)
+        {
+            // 获取行
+            DataGridRow? row = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) as DataGridRow;
+            if (row == null)
+            {
+                // 获取失败则滚动界面后重试
+                dataGrid.UpdateLayout();
+                dataGrid.ScrollIntoView(dataGrid.Items[rowIndex]);
+                row = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) as DataGridRow;
+            }
+            if (row == null) { return null; }
+
+            // 获取发运数量单元格
+            DataGridCellsPresenter? cellsPresenter = VisualUtil.FindVisualChild<DataGridCellsPresenter>(row);
+            DataGridCell? cell = cellsPresenter?.ItemContainerGenerator.ContainerFromIndex(colIndex) as DataGridCell; // 不允许拖动改变列顺序
+            //if (cell == null) { return null; }
+            return cell;
+        }
+
+        /// <summary>
+        /// 界面数据转换为要导出的 DataTable
+        /// </summary>
+        private DataTable GenerateDataTable()
+        {
+            // 创建 DataTable 的列
+            DataTable dtExport = new DataTable();
+            dtExport.Columns.Add("成品企业", typeof(string));
+            dtExport.Columns.Add("材料名称", typeof(string));
+            dtExport.Columns.Add("材料批号", typeof(string));
+            dtExport.Columns.Add("包号", typeof(string));
+            dtExport.Columns.Add("使用数量", typeof(string));
+            dtExport.Columns.Add("材料企业", typeof(string));
+            dtExport.Columns.Add("备注", typeof(string));
+
+            // 创建 DataTable 的行
+            foreach (DeliveryReceiptDetail dtl in DeliveryReceiptDetails)
+            {
+                DataRow dr = dtExport.NewRow();
+                dr["成品企业"] = CurrDeliveryReceipt!.ReceivedCompanyName;
+                dr["材料名称"] = CurrDeliveryReceipt.ProductName;
+                dr["材料批号"] = dtl.BatchNo;
+                dr["包号"] = dtl.PacketNo;
+                dr["使用数量"] = (dtl.UsageQuantity ?? 0).ToString("#0.0");
+                dr["材料企业"] = CurrDeliveryReceipt.CompanyName;
+                dr["备注"] = dtl.ShipNote;
+                dtExport.Rows.Add(dr);
+            }
+
+            return dtExport;
+        }
+    }
+}

+ 2 - 3
UniformMaterialManagementSystem/ViewModels/MaterialViewModel.cs

@@ -252,7 +252,7 @@ namespace UniformMaterialManagementSystem.ViewModels
             openFileDialog.Title = "选择导入文件的路径";
             openFileDialog.Filter = "Excel 文件|*.xlsx;*.xls|Excel 工作簿|*.xlsx|Excel 97-2003工作簿|*.xls"; // 只能导入Excel
             string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
-            openFileDialog.InitialDirectory = desktopPath; // 默认从桌面获取文件
+            openFileDialog.InitialDirectory = Environment.CurrentDirectory;
             if (openFileDialog.ShowDialog() != true) { return; }
 
             // 导入 Excel
@@ -359,8 +359,7 @@ namespace UniformMaterialManagementSystem.ViewModels
             SaveFileDialog saveFileDialog = new SaveFileDialog();
             saveFileDialog.Title = "选择保存文件的路径";
             saveFileDialog.Filter = "Excel 文件|*.xlsx;*.xls|Excel 工作簿|*.xlsx|Excel 97-2003工作簿|*.xls|所有文件|*.*";
-            string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
-            saveFileDialog.InitialDirectory = desktopPath; // 默认保存到桌面。若不设置,默认保存到 文档 路径下
+            saveFileDialog.InitialDirectory = Environment.CurrentDirectory;
             string fileName = "材料目录-" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".xlsx";
             saveFileDialog.FileName = fileName; // 默认文件名
             if (saveFileDialog.ShowDialog() != true) { return; }

+ 22 - 18
UniformMaterialManagementSystem/Views/DeliveryReceiptControl.xaml

@@ -16,7 +16,7 @@
              x:Name="delveryReceiptControl">
     <UserControl.Resources>
         <!-- 工具栏按钮模板 -->
-        <ControlTemplate x:Key="CustomToolBarButtomTemplate" TargetType="Button">
+        <ControlTemplate x:Key="CustomToolBarButtonTemplate" TargetType="Button">
             <!-- 按钮文本超出2个字,设置 Width 宽度自适应。再设置 Padding ,增加按钮左右的空白 -->
             <Border Width="auto"
                     Background="{TemplateBinding Background}" 
@@ -34,6 +34,9 @@
                 <Trigger Property="IsMouseOver" Value="True">
                     <Setter Property="Background" Value="LightGray" />
                 </Trigger>
+                <Trigger Property="IsEnabled" Value="False">
+                    <Setter Property="Foreground" Value="Gray" />
+                </Trigger>
             </ControlTemplate.Triggers>
         </ControlTemplate>
 
@@ -270,46 +273,47 @@
                 <Button x:Name="btnNew"
                         Content="新增发货单"
                         Tag="{x:Static utils:RegularFontUtil.Add_Square_32}"
-                        Template="{StaticResource CustomToolBarButtomTemplate}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
                         Command="{Binding AddDeliveryCommand}" />
                 <Button x:Name="btnDelete"
                         Content="删除发货单"
                         Tag="{x:Static utils:RegularFontUtil.Delete_32}"
-                        Template="{StaticResource CustomToolBarButtomTemplate}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
                         Command="{Binding DeleteDeliveryCommand}"
                         CommandParameter="{Binding ElementName=fdgDelivery}" />
                 <Separator x:Name="sep0" />
-                <Button Content="保存" 
+                <Button x:Name="btnSave"
+                        Content="保存" 
                         Tag="{x:Static utils:RegularFontUtil.Save_32}"
-                        Template="{StaticResource CustomToolBarButtomTemplate}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
                         Command="{Binding SaveCommand}"
                         CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}" />
                 <Separator x:Name="sep1" />
                 <Button x:Name="btnExportThree"
                         Content="导出附件3" 
                         Tag="{x:Static utils:RegularFontUtil.Open_32}"
-                        Template="{StaticResource CustomToolBarButtomTemplate}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
                         Command="{Binding ExportThreeCommand}" />
                 <Button x:Name="btnCheck"
                         Content="复核" 
                         Tag="{x:Static utils:RegularFontUtil.Clipboard_Checkmark_24}"
-                        Template="{StaticResource CustomToolBarButtomTemplate}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
                         Command="{Binding CheckCommand}"
                         CommandParameter="{Binding ElementName=cbReceivedStatus}"/>
                 <Button x:Name="btnExportFour"
                         Content="导出附件4" 
                         Tag="{x:Static utils:RegularFontUtil.Arrow_Turn_Right_48}"
-                        Template="{StaticResource CustomToolBarButtomTemplate}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
                         Command="{Binding ExportFourCommand}" />
                 <Separator x:Name="sep2" />
                 <Button Content="导出数据包" 
                         Tag="{x:Static utils:RegularFontUtil.Arrow_Export_Up_24}"
-                        Template="{StaticResource CustomToolBarButtomTemplate}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
                         Command="{Binding ExportDBCommand}"
                         CommandParameter="{Binding ElementName=fdgDelivery}"/>
                 <Button Content="导入数据包" 
                         Tag="{x:Static utils:RegularFontUtil.Arrow_Download_24}"
-                        Template="{StaticResource CustomToolBarButtomTemplate}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
                         Command="{Binding ImportDBCommand}" />
 
             </ToolBar>
@@ -708,36 +712,36 @@
                 <Button x:Name="btnAddDtl"
                         Content="新增"
                         Tag="{x:Static utils:RegularFontUtil.Add_Circle_32}"
-                        Template="{StaticResource CustomToolBarButtomTemplate}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
                         Command="{Binding AddDetailCommand}"
                         CommandParameter="{Binding ElementName=fdgDeliveryDetail}" />
                 <Button x:Name="btnDeleteDtls"
                         Content="删除"
                         Tag="{x:Static utils:RegularFontUtil.Delete_32}"
-                        Template="{StaticResource CustomToolBarButtomTemplate}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
                         Command="{Binding DeleteDetailsCommand}"
                         CommandParameter="{Binding ElementName=fdgDeliveryDetail}" />
                 <Separator x:Name="sepDtl" />
                 <Button x:Name="btnImportDtl"
                         Content="导入"
                         Tag="{x:Static utils:RegularFontUtil.Arrow_Circle_Down_32}"
-                        Template="{StaticResource CustomToolBarButtomTemplate}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
                         Command="{Binding ImportDetailCommand}"
                         CommandParameter="{Binding ElementName=fdgDelivery}"/>
                 <Button x:Name="btnExportDtl"
                         Content="导出"
                         Tag="{x:Static utils:RegularFontUtil.Arrow_Circle_Up_32}"
-                        Template="{StaticResource CustomToolBarButtomTemplate}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
                         Command="{Binding ExportDetailCommand}" />
                 <Button x:Name="btnReceiveDtl"
                         Content="全部接收"
                         Tag="{x:Static utils:RegularFontUtil.Clipboard_Checkmark_24}"
-                        Template="{StaticResource CustomToolBarButtomTemplate}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
                         Command="{Binding ReceiveAllCommand}" />
                 <Button x:Name="btnUseDtl"
                         Content="全部使用"
                         Tag="{x:Static utils:RegularFontUtil.Box_Checkmark_24}"
-                        Template="{StaticResource CustomToolBarButtomTemplate}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
                         Command="{Binding UseAllCommand}" />
             </ToolBar>
         </Border>
@@ -762,7 +766,7 @@
                             RowHeaderStyle="{StaticResource CustomRowHeaderStyle}"
                             RowStyle="{StaticResource CustomRowStyle}"
                             CellStyle="{StaticResource CustomCellStyle}"
-                            ItemsSource="{Binding CurrDeliveryReceipt.DeliveryReceiptDetails, Mode=TwoWay}">
+                            ItemsSource="{Binding DeliveryReceiptDetails, Mode=TwoWay}">
 
             <!-- DataGrid 事件 -->
             <b:Interaction.Triggers>
@@ -785,7 +789,7 @@
                 <fdg:FilterDataGridCheckBoxColumn Header="  " Width="30"
                                                   ElementStyle="{StaticResource CheckBoxColumnElementStyle}"
                                                   EditingElementStyle="{StaticResource CheckBoxColumnElementStyle}"
-                                                  Binding="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged}"/>
+                                                  Binding="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged}" />
                 <fdg:FilterDataGridTextColumn Header="材料批号" Width="200"
                                               ElementStyle="{StaticResource TextColumnElementStyle}"
                                               EditingElementStyle="{StaticResource TextColumnEditingStyle}"

+ 4 - 4
UniformMaterialManagementSystem/Views/MaterialCompany.xaml

@@ -15,7 +15,7 @@
 
     <UserControl.Resources>
         <!-- 工具栏按钮模板 -->
-        <ControlTemplate x:Key="CustomToolBarButtomTemplate" TargetType="Button">
+        <ControlTemplate x:Key="CustomToolBarButtonTemplate" TargetType="Button">
             <Border Width="40"
                     Background="{TemplateBinding Background}"
                     CornerRadius="5">
@@ -174,14 +174,14 @@
                             CommandParameter="{Binding ElementName=dataGridMC}"
                             Content="新增"
                             Tag="{x:Static utils:RegularFontUtil.Add_Circle_32}"
-                            Template="{StaticResource CustomToolBarButtomTemplate}" />
+                            Template="{StaticResource CustomToolBarButtonTemplate}" />
                     <Button x:Name="tsBtnDelete"
                             HorizontalContentAlignment="Center"
                             Command="{Binding DeleteCommand}"
                             CommandParameter="{Binding ElementName=dataGridMC}"
                             Content="删除"
                             Tag="{x:Static utils:RegularFontUtil.Delete_32}"
-                            Template="{StaticResource CustomToolBarButtomTemplate}" />
+                            Template="{StaticResource CustomToolBarButtonTemplate}" />
                     <Separator />
                     <Button x:Name="tsBtnSave"
                             HorizontalContentAlignment="Center"
@@ -189,7 +189,7 @@
                             CommandParameter="{Binding ElementName=dataGridMC}"
                             Content="保存"
                             Tag="{x:Static utils:RegularFontUtil.Save_32}"
-                            Template="{StaticResource CustomToolBarButtomTemplate}" />
+                            Template="{StaticResource CustomToolBarButtonTemplate}" />
                 </ToolBar>
             </ToolBarPanel>
         </Border>

+ 6 - 6
UniformMaterialManagementSystem/Views/MaterialControl.xaml

@@ -12,7 +12,7 @@
 
     <UserControl.Resources>
         <!-- 工具栏按钮模板 -->
-        <ControlTemplate x:Key="CustomToolBarButtomTemplate" TargetType="Button">
+        <ControlTemplate x:Key="CustomToolBarButtonTemplate" TargetType="Button">
             <Border Width="40"
                     Background="{TemplateBinding Background}"
                     CornerRadius="5">
@@ -180,33 +180,33 @@
                     <Button x:Name="tsBtnAppend"
                             Content="新增"
                             Tag="{x:Static utils:RegularFontUtil.Add_Circle_32}"
-                            Template="{StaticResource CustomToolBarButtomTemplate}"
+                            Template="{StaticResource CustomToolBarButtonTemplate}"
                             Command="{Binding AppendCommand}"
                             CommandParameter="{Binding ElementName=dataGridMaterial}" />
                     <Button x:Name="tsBtnDelete"
                             Content="删除"
                             Tag="{x:Static utils:RegularFontUtil.Delete_32}"
-                            Template="{StaticResource CustomToolBarButtomTemplate}" 
+                            Template="{StaticResource CustomToolBarButtonTemplate}" 
                             Command="{Binding DeleteCommand}"
                             CommandParameter="{Binding ElementName=dataGridMaterial}" />
                     <Separator />
                     <Button x:Name="tsBtnSave"
                             Content="保存"
                             Tag="{x:Static utils:RegularFontUtil.Save_32}"
-                            Template="{StaticResource CustomToolBarButtomTemplate}" 
+                            Template="{StaticResource CustomToolBarButtonTemplate}" 
                             Command="{Binding SaveCommand}"
                             CommandParameter="{Binding ElementName=dataGridMaterial}"/>
                     <Separator />
                     <Button x:Name="tsBtnImprot"
                             Content="导入"
                             Tag="{x:Static utils:RegularFontUtil.Share_Close_Tray_24}"
-                            Template="{StaticResource CustomToolBarButtomTemplate}" 
+                            Template="{StaticResource CustomToolBarButtonTemplate}" 
                             Command="{Binding ImportCommand}"
                             CommandParameter="{Binding ElementName=dataGridMaterial}" />
                     <Button x:Name="tsBtnExprot"
                             Content="导出"
                             Tag="{x:Static utils:RegularFontUtil.Share_Screen_Start_24}"
-                            Template="{StaticResource CustomToolBarButtomTemplate}" 
+                            Template="{StaticResource CustomToolBarButtonTemplate}" 
                             Command="{Binding ExportCommand}"
                             CommandParameter="{Binding ElementName=dataGridMaterial}" />
                 </ToolBar>

+ 43 - 30
UniformMaterialManagementSystem/Views/MaterialReceiptPage.xaml

@@ -15,24 +15,26 @@
 
     <UserControl.Resources>
         <!-- 工具栏按钮模板 -->
-        <ControlTemplate x:Key="CustomToolBarButtomTemplate" TargetType="Button">
-            <!-- 按钮文本超出2个字,设置 Width 宽度自适应。再设置 Padding ,增加按钮左右的空白 -->
+        <ControlTemplate x:Key="CustomToolBarButtonTemplate" TargetType="Button">
             <Border Width="auto"
-                Background="{TemplateBinding Background}" 
-                CornerRadius="5">
+                    Background="{TemplateBinding Background}" 
+                    CornerRadius="5">
                 <StackPanel HorizontalAlignment="Center" Orientation="Vertical">
                     <TextBlock HorizontalAlignment="Center"
-                           FontFamily="{StaticResource FluentSystemIconsRegular}"
-                           FontSize="20"
-                           Text="{TemplateBinding Tag}" />
+                               FontFamily="{StaticResource FluentSystemIconsRegular}"
+                               FontSize="20"
+                               Text="{TemplateBinding Tag}" />
                     <TextBlock Text="{TemplateBinding Content}"
-                           Padding="5 0 5 0" />
+                               Padding="5 0 5 0" />
                 </StackPanel>
             </Border>
             <ControlTemplate.Triggers>
                 <Trigger Property="IsMouseOver" Value="True">
                     <Setter Property="Background" Value="LightGray" />
                 </Trigger>
+                <Trigger Property="IsEnabled" Value="False">
+                    <Setter Property="Foreground" Value="Gray" />
+                </Trigger>
             </ControlTemplate.Triggers>
         </ControlTemplate>
 
@@ -220,9 +222,6 @@
             <Setter Property="Margin" Value="5" />
         </Style>
 
-        <!-- 转换器 -->
-        <converters:MultiParamConverter x:Key="MultiParamConverter" />
-
     </UserControl.Resources>
 
     <Grid>
@@ -247,32 +246,36 @@
                 BorderThickness="1">
             <ToolBar Background="White"
                      ToolBarTray.IsLocked="True">
-                <Button Content="保存" 
+                <Button x:Name="btnSave"
+                        Content="保存" 
                         Tag="{x:Static utils:RegularFontUtil.Save_32}"
-                        Template="{StaticResource CustomToolBarButtomTemplate}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
                         Command="{Binding SaveCommand}"
-                        CommandParameter="{Binding ElementName=cbReceivedStatus}" />
+                        CommandParameter="{Binding ElementName=cbReceivedStatus}">
+
+                </Button>
                 <Separator />
                 <Button x:Name="btnCheck"
                         Content="复核" 
                         Tag="{x:Static utils:RegularFontUtil.Clipboard_Checkmark_24}"
-                        Template="{StaticResource CustomToolBarButtomTemplate}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
                         Command="{Binding CheckCommand}"
                         CommandParameter="{Binding ElementName=fdgDeliveryDetail}" />
                 <Separator />
                 <Button x:Name="btnExportThree"
                         Content="导出附件3" 
                         Tag="{x:Static utils:RegularFontUtil.Open_32}"
-                        Template="{StaticResource CustomToolBarButtomTemplate}"
-                        Command="{Binding ExportThreeCommand}" />
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
+                        Command="{Binding ExportThreeCommand}"
+                        CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"/>
                 <Separator />
                 <Button Content="导出数据包" 
                     Tag="{x:Static utils:RegularFontUtil.Arrow_Export_Up_24}"
-                    Template="{StaticResource CustomToolBarButtomTemplate}"
+                    Template="{StaticResource CustomToolBarButtonTemplate}"
                     Command="{Binding ExportDBCommand}" />
                 <Button Content="导入数据包" 
                     Tag="{x:Static utils:RegularFontUtil.Arrow_Download_24}"
-                    Template="{StaticResource CustomToolBarButtomTemplate}"
+                    Template="{StaticResource CustomToolBarButtonTemplate}"
                     Command="{Binding ImportDBCommand}" />
 
             </ToolBar>
@@ -309,6 +312,8 @@
                 <b:EventTrigger EventName="SelectionChanged">
                     <b:CallMethodAction TargetObject="{Binding }" 
                                         MethodName="FdgDelivery_SelectionChanged" />
+                    <b:InvokeCommandAction Command="{Binding DeliverySelectionChangedCommand}"
+                                           CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}" />
                 </b:EventTrigger>
             </b:Interaction.Triggers>
 
@@ -374,6 +379,18 @@
                                           IsColumnFiltered="True"
                                           ElementStyle="{StaticResource TextColumnElementStyle}"
                                           Binding="{Binding ReceivedStatus}" />
+                <!-- 只读 -->
+                <fdg:FilterDataGridCheckBoxColumn Header="是否导出附件3" Width="100"
+                                  Binding="{Binding IsExportReceive}"
+                                  IsReadOnly="True">
+                    <fdg:FilterDataGridCheckBoxColumn.ElementStyle>
+                        <Style TargetType="CheckBox">
+                            <Setter Property="HorizontalAlignment" Value="Center" />
+                            <Setter Property="VerticalAlignment" Value="Center" />
+                            <Setter Property="IsEnabled" Value="False" />
+                        </Style>
+                    </fdg:FilterDataGridCheckBoxColumn.ElementStyle>
+                </fdg:FilterDataGridCheckBoxColumn>
             </fdg:FilterDataGrid.Columns>
         </fdg:FilterDataGrid>
 
@@ -493,13 +510,11 @@
                                      Text="{Binding CurrDeliveryReceipt.ShippedDate, StringFormat= 'yyyy/MM/dd'}"
                                      IsEnabled="False"
                                      Style="{StaticResource CustomFieldTextBoxStyle}" />
-                            <TextBox x:Name="fieldShippedMan"
-                                     Tag="发运承办人:"
+                            <TextBox Tag="发运承办人:"
                                      Text="{Binding CurrDeliveryReceipt.ShippedMan}"
                                      IsEnabled="False"
                                      Style="{StaticResource CustomFieldTextBoxStyle}" />
-                            <TextBox x:Name="fieldShippedTel"
-                                     Tag="联系电话:"
+                            <TextBox Tag="联系电话:"
                                      Text="{Binding CurrDeliveryReceipt.ShippedTel}"
                                      IsEnabled="False"
                                      Style="{StaticResource CustomFieldTextBoxStyle}" />
@@ -589,11 +604,12 @@
                 Height="{Binding Path=ActualHeight,ElementName=toolBarDelivery}">
             <ToolBar Background="White"
                      ToolBarTray.IsLocked="True">
-                <Button Content="全部接收"
+                <Button x:Name="btnReceiveAll"
+                        Content="全部接收"
                         Tag="{x:Static utils:RegularFontUtil.Clipboard_Checkmark_24}"
-                        Template="{StaticResource CustomToolBarButtomTemplate}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
                         Command="{Binding ReceiveAllCommand}"
-                        CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor,  AncestorType={x:Type UserControl}}}" />
+                        CommandParameter="{Binding ElementName=fdgDeliveryDetail}" />
             </ToolBar>
         </Border>
 
@@ -631,17 +647,14 @@
             <fdg:FilterDataGrid.Columns>
                 <fdg:FilterDataGridTextColumn Header="材料批号" Width="200"
                                               ElementStyle="{StaticResource TextColumnElementStyle}"
-                                              EditingElementStyle="{StaticResource TextColumnEditingStyle}"
                                               Binding="{Binding BatchNo}"
                                               IsReadOnly="True" />
                 <fdg:FilterDataGridTextColumn Header="包号" Width="200"
                                               ElementStyle="{StaticResource TextColumnElementStyle}"
-                                              EditingElementStyle="{StaticResource TextColumnEditingStyle}"
                                               Binding="{Binding PacketNo}"
                                               IsReadOnly="True" />
                 <fdg:FilterDataGridTextColumn Header="发运数量" Width="100"
                                               ElementStyle="{StaticResource TextColumnElementStyle}"
-                                              EditingElementStyle="{StaticResource TextColumnEditingStyle}"
                                               Binding="{Binding ShippedQuantity}" 
                                               IsReadOnly="True" />
                 <fdg:FilterDataGridTextColumn Header="接收数量*" Width="100"
@@ -655,5 +668,5 @@
             </fdg:FilterDataGrid.Columns>
         </fdg:FilterDataGrid>
     </Grid>
-    
+
 </UserControl>

+ 700 - 5
UniformMaterialManagementSystem/Views/MaterialUsagePage.xaml

@@ -4,15 +4,710 @@
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
              xmlns:local="clr-namespace:UniformMaterialManagementSystem.Views"
+             xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
+             xmlns:utils="clr-namespace:UniformMaterialManagementSystem.Utils"
+             xmlns:fdg="http://FilterDataGrid.Control.com/2024"
              mc:Ignorable="d" 
              d:DesignHeight="450" d:DesignWidth="800">
+    <UserControl.Resources>
+        <!-- 工具栏按钮模板 -->
+        <ControlTemplate x:Key="CustomToolBarButtonTemplate" TargetType="Button">
+            <!-- 按钮文本超出2个字,设置 Width 宽度自适应。再设置 Padding ,增加按钮左右的空白 -->
+            <Border Width="auto"
+                    Background="{TemplateBinding Background}" 
+                    CornerRadius="5">
+                <StackPanel HorizontalAlignment="Center" Orientation="Vertical">
+                    <TextBlock HorizontalAlignment="Center"
+                               FontFamily="{StaticResource FluentSystemIconsRegular}"
+                               FontSize="20"
+                               Text="{TemplateBinding Tag}" />
+                    <TextBlock Text="{TemplateBinding Content}"
+                               Padding="5 0 5 0" />
+                </StackPanel>
+            </Border>
+            <ControlTemplate.Triggers>
+                <Trigger Property="IsMouseOver" Value="True">
+                    <Setter Property="Background" Value="LightGray" />
+                </Trigger>
+                <Trigger Property="IsEnabled" Value="False">
+                    <Setter Property="Foreground" Value="Gray" />
+                </Trigger>
+            </ControlTemplate.Triggers>
+        </ControlTemplate>
+
+        <!-- 列标题模板:必须在列标题样式引用之前定义 -->
+        <ControlTemplate x:Key="CustomColumnHeaderTemplate" TargetType="DataGridColumnHeader">
+            <!-- BorderThickness 分别设置左,上,右,下的边框宽度。 -->
+            <Border BorderThickness="0, 0, 1, 1" BorderBrush="Black">
+                <Grid>
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition />
+                        <ColumnDefinition Width="Auto" />
+                    </Grid.ColumnDefinitions>
+                    <!-- 重定义控件模板,在 Style 中设置的居中属性会被覆盖,所以在这里也需要设置居中 -->
+                    <ContentPresenter Grid.Column="0"
+                                      HorizontalAlignment="Center" VerticalAlignment="Center" />
+                    <!-- 列排序图标 -->
+                    <Path x:Name="SortArrow"
+                          Grid.Column="1"
+                          Margin="0,0,5,0"
+                          VerticalAlignment="Center"
+                          Data="M 0 0 L 10 0 L 5 5 Z"
+                          Fill="Gray"
+                          Visibility="Collapsed" />
+                </Grid>
+            </Border>
+            <!-- 列排序按钮 -->
+            <ControlTemplate.Triggers>
+                <Trigger Property="SortDirection" Value="Ascending">
+                    <Setter TargetName="SortArrow" Property="Data" Value="M 0 5 L 10 5 L 5 0 Z" />
+                    <Setter TargetName="SortArrow" Property="Visibility" Value="Visible" />
+                </Trigger>
+                <Trigger Property="SortDirection" Value="Descending">
+                    <Setter TargetName="SortArrow" Property="Data" Value="M 0 0 L 10 0 L 5 5 Z" />
+                    <Setter TargetName="SortArrow" Property="Visibility" Value="Visible" />
+                </Trigger>
+            </ControlTemplate.Triggers>
+        </ControlTemplate>
+
+        <!-- 列标题样式 -->
+        <Style x:Key="CustomColumnHeaderStyle" TargetType="DataGridColumnHeader">
+            <Setter Property="Background" Value="White" />
+            <Setter Property="Foreground" Value="Black" />
+            <Setter Property="FontWeight" Value="Bold" />
+            <Setter Property="HorizontalContentAlignment" Value="Center" />
+            <Setter Property="VerticalContentAlignment" Value="Center" />
+            <Setter Property="Height" Value="40" />
+            <Setter Property="Template" Value="{StaticResource CustomColumnHeaderTemplate}" />
+        </Style>
+
+        <!-- 行标题样式 -->
+        <Style x:Key="CustomRowHeaderStyle" TargetType="DataGridRowHeader">
+            <!-- 通过 Template 设置行号、居中、边框 -->
+            <Setter Property="Template">
+                <Setter.Value>
+                    <ControlTemplate TargetType="DataGridRowHeader">
+                        <!-- 在这里设置 Width 会受到 DataGrid.RowHeaderWidth 的限制,显示不全 -->
+                        <Label Width="30"
+                               Background="White"
+                               BorderBrush="Black"
+                               BorderThickness="0 0 1 1"
+                               Content="{Binding Path=Header, RelativeSource={RelativeSource AncestorType=DataGridRow}}"
+                               HorizontalContentAlignment="Center" />
+                    </ControlTemplate>
+                </Setter.Value>
+            </Setter>
+        </Style>
+
+        <!-- DataGrid 左上角样式 -->
+        <Style TargetType="Button" 
+               x:Key="{ComponentResourceKey ResourceId=DataGridSelectAllButtonStyle, TypeInTargetAssembly={x:Type DataGrid}}">
+            <!-- 模板里面放 Border 添加边框 -->
+            <Setter Property="Template">
+                <Setter.Value>
+                    <ControlTemplate TargetType="Button">
+                        <Border Background="White" 
+                                BorderBrush="Black"
+                                BorderThickness="0 0 1 1" />
+                    </ControlTemplate>
+                </Setter.Value>
+            </Setter>
+        </Style>
+
+        <!-- 行样式 -->
+        <Style x:Key="CustomRowStyle" TargetType="DataGridRow">
+            <Setter Property="Background" Value="White" />
+            <Setter Property="FontSize" Value="14" />
+            <Setter Property="Height" Value="30" />
+
+            <Style.Triggers>
+                <Trigger Property="AlternationIndex" Value="1">
+                    <Setter Property="Background" Value="WhiteSmoke" />
+                </Trigger>
+                <Trigger Property="IsSelected" Value="True">
+                    <Setter Property="Background" Value="LightBlue" />
+                </Trigger>
+            </Style.Triggers>
+        </Style>
+
+        <!-- todo 为什么必须设置文本列样式之后才能居中,单元格设置不起作用? -->
+        <!-- 单元格样式 -->
+        <Style x:Key="CustomCellStyle" TargetType="DataGridCell"
+               BasedOn="{StaticResource {x:Type DataGridCell}}">
+            <Setter Property="Background" Value="White" />
+            <Setter Property="HorizontalContentAlignment" Value="Center" />
+            <Setter Property="VerticalContentAlignment" Value="Center" />
+            <Setter Property="Padding" Value="10" />
+            <Setter Property="BorderThickness" Value="0" />
+            <Style.Triggers>
+                <Trigger Property="IsSelected" Value="True">
+                    <Setter Property="Background" Value="LightBlue" />
+                    <Setter Property="Foreground" Value="Black" />
+                </Trigger>
+            </Style.Triggers>
+        </Style>
+
+        <!--  文本列居中  -->
+        <Style x:Key="TextColumnElementStyle" TargetType="TextBlock">
+            <Setter Property="HorizontalAlignment" Value="Center" />
+            <Setter Property="VerticalAlignment" Value="Center" />
+            <Setter Property="TextAlignment" Value="Center" />
+        </Style>
+
+        <!-- 文本列编辑状态居左 -->
+        <Style x:Key="TextColumnEditingStyle" TargetType="TextBox">
+            <Setter Property="HorizontalContentAlignment" Value="Left" />
+            <Setter Property="VerticalContentAlignment" Value="Center" />
+        </Style>
+
+        <!-- 复选框列居中 -->
+        <Style x:Key="CheckBoxColumnElementStyle" TargetType="CheckBox">
+            <Setter Property="HorizontalAlignment" Value="Center" />
+            <Setter Property="VerticalAlignment" Value="Center" />
+            <Setter Property="Padding" Value="0, 5, 0, 5" />
+        </Style>
+
+        <!-- todo Template 里的使用样式;特殊的字段单独设置样式和模板 -->
+        <!-- 编辑界面字段 -->
+        <Style x:Key="CustomFieldTextBoxStyle" TargetType="TextBox">
+            <Setter Property="Template" >
+                <Setter.Value>
+                    <ControlTemplate>
+                        <StackPanel Orientation="Horizontal">
+                            <TextBlock Text="{Binding Tag, RelativeSource={RelativeSource TemplatedParent}}"
+                                       Width="100"
+                                       Height="20"
+                                       FontSize="14"
+                                       VerticalAlignment="Center"
+                                       TextAlignment="Right"
+                                       Margin="5" />
+                            <TextBox Text="{Binding Text, RelativeSource={RelativeSource TemplatedParent}}" 
+                                     IsEnabled="{Binding IsEnabled, RelativeSource={RelativeSource TemplatedParent}}"
+                                     BorderBrush="Gray"
+                                     BorderThickness="1"
+                                     Width="250"
+                                     Height="30"
+                                     FontSize="14"
+                                     HorizontalContentAlignment="Center"
+                                     VerticalContentAlignment="Center"
+                                     Margin="0 5 0 5" />
+                        </StackPanel>
+                    </ControlTemplate>
+                </Setter.Value>
+            </Setter>
+        </Style>
+
+        <!-- TextBlock 样式 -->
+        <Style x:Key="CustomTextBlockStyle" TargetType="TextBlock">
+            <Setter Property="Width" Value="100" />
+            <Setter Property="Height" Value="20" />
+            <Setter Property="FontSize" Value="14" />
+            <Setter Property="VerticalAlignment" Value="Center" />
+            <Setter Property="TextAlignment" Value="Right" />
+            <Setter Property="Margin" Value="5" />
+        </Style>
+
+        <!-- Label 样式:自带边框 -->
+        <Style x:Key="CustomLabelStyle" TargetType="Label">
+            <Setter Property="BorderBrush" Value="Gray" />
+            <Setter Property="BorderThickness" Value="1 0 1 0" />
+            <Setter Property="Width" Value="100" />
+            <Setter Property="FontWeight" Value="Bold" />
+            <Setter Property="VerticalAlignment" Value="Stretch" />
+            <Setter Property="HorizontalContentAlignment" Value="Center" />
+            <Setter Property="VerticalContentAlignment" Value="Center" />
+            <!--<Setter Property="FontSize" Value="14" />-->
+        </Style>
+
+        <!-- 按钮模板 -->
+        <ControlTemplate x:Key="CustomButtonTemplate" TargetType="Button">
+            <Border Width="auto"
+                    BorderBrush="Gray"
+                    BorderThickness="1"
+                    Background="{TemplateBinding Background}" 
+                    CornerRadius="5">
+                <StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
+                    <TextBlock HorizontalAlignment="Center"
+                               FontFamily="{StaticResource FluentSystemIconsRegular}"
+                               FontSize="20"
+                               VerticalAlignment="Center"
+                               Text="{TemplateBinding Tag}" />
+                    <TextBlock Text="{TemplateBinding Content}"
+                               VerticalAlignment="Center"
+                               FontSize="14"
+                               Padding="5 0 5 0" />
+                </StackPanel>
+            </Border>
+            <ControlTemplate.Triggers>
+                <Trigger Property="IsMouseOver" Value="True">
+                    <Setter Property="Background" Value="LightGray" />
+                </Trigger>
+                <Trigger Property="IsEnabled" Value="False">
+                    <Setter Property="Foreground" Value="Gray" />
+                </Trigger>
+            </ControlTemplate.Triggers>
+        </ControlTemplate>
+
+        <!-- 转换器 -->
+        <!--<converters:MultiParamConverter x:Key="MultiParamConverter" />-->
+
+    </UserControl.Resources>
+
     <Grid>
-        <Border BorderBrush="Gray"
-                BorderThickness="1"
-                Background="White">
-            <ContentControl x:Name="currContentControl" />
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="*" />
+            <ColumnDefinition Width="*" />
+        </Grid.ColumnDefinitions>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="auto" />
+            <RowDefinition Height="auto" />
+            <RowDefinition Height="auto" />
+            <RowDefinition Height="auto" />
+            <RowDefinition Height="*" />
+        </Grid.RowDefinitions>
+
+        <!-- 主表工具栏 -->
+        <Border x:Name="toolBarDelivery"
+                Grid.Row="0" 
+                Grid.Column="0"
+                Grid.ColumnSpan="2"
+                BorderBrush="Gray"
+                BorderThickness="1">
+            <ToolBar Background="White"
+                     ToolBarTray.IsLocked="True">
+                <Button x:Name="btnSave"
+                        Content="保存" 
+                        Tag="{x:Static utils:RegularFontUtil.Save_32}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
+                        Command="{Binding SaveCommand}"
+                        CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}" />
+                <Separator x:Name="sep1" />
+                <Button x:Name="btnExportFour"
+                        Content="导出附件4" 
+                        Tag="{x:Static utils:RegularFontUtil.Arrow_Turn_Right_48}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
+                        Command="{Binding ExportFourCommand}"
+                        CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}" />
+                <Separator x:Name="sep2" />
+                <Button Content="导出数据包" 
+                        Tag="{x:Static utils:RegularFontUtil.Arrow_Export_Up_24}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
+                        Command="{Binding ExportDBCommand}"
+                        CommandParameter="{Binding ElementName=fdgDelivery}"/>
+                <Button Content="导入数据包" 
+                        Tag="{x:Static utils:RegularFontUtil.Arrow_Download_24}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
+                        Command="{Binding ImportDBCommand}" />
 
+            </ToolBar>
         </Border>
-            
+
+        <!-- 主表 -->
+        <fdg:FilterDataGrid x:Name="fdgDelivery"
+                            Grid.Row="1" 
+                            Grid.Column="0"
+                            Grid.ColumnSpan="2"
+                            FilterLanguage="SimplifiedChinese"
+                            Background="White"
+                            AutoGenerateColumns="False"
+                            CanUserAddRows="False"
+                            CanUserResizeRows="False"
+                            SelectionMode="Single"
+                            SelectionUnit="FullRow"
+                            HeadersVisibility="All"
+                            ShowRowsCount="True"
+                            RowHeaderWidth="30"
+                            LoadingRow="DataGrid_LoadingRow"
+                            ColumnHeaderStyle="{StaticResource CustomColumnHeaderStyle}"
+                            RowHeaderStyle="{StaticResource CustomRowHeaderStyle}"
+                            RowStyle="{StaticResource CustomRowStyle}"
+                            CellStyle="{StaticResource CustomCellStyle}"
+                            IsReadOnly="True"
+                            MinHeight="200"
+                            SelectedItem="{Binding CurrDeliveryReceipt, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
+                            ItemsSource="{Binding DeliveryReceipts, Mode=TwoWay}">
+
+            <!-- DataGrid 事件 -->
+            <b:Interaction.Triggers>
+                <!-- 选择行切换事件 -->
+                <b:EventTrigger EventName="SelectionChanged">
+                    <b:CallMethodAction TargetObject="{Binding }" 
+                                        MethodName="FdgDelivery_SelectionChanged" />
+                    <b:InvokeCommandAction Command="{Binding DeliverySelectionChangedCommand}"
+                                           CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"/>
+                </b:EventTrigger>
+            </b:Interaction.Triggers>
+
+            <!-- 数据结构 -->
+            <fdg:FilterDataGrid.Columns>
+                <!--<fdg:FilterDataGridCheckBoxColumn Header="  " Width="30"
+                                               ElementStyle="{StaticResource CheckBoxColumnElementStyle}"
+                                               EditingElementStyle="{StaticResource CheckBoxColumnElementStyle}"
+                                               Binding="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged}"/>-->
+                <fdg:FilterDataGridTextColumn Header="合同编号" Width="200"
+                                              IsColumnFiltered="True"
+                                              ElementStyle="{StaticResource TextColumnElementStyle}"
+                                              Binding="{Binding ContractNo}" />
+                <fdg:FilterDataGridTextColumn Header="产品名称" Width="250"
+                                              IsColumnFiltered="True"
+                                              ElementStyle="{StaticResource TextColumnElementStyle}"
+                                              Binding="{Binding ProductName}" />
+                <fdg:FilterDataGridTextColumn Header="生产企业" Width="250"
+                                              IsColumnFiltered="True"
+                                              ElementStyle="{StaticResource TextColumnElementStyle}"
+                                              Binding="{Binding CompanyName}" />
+                <fdg:FilterDataGridTextColumn Header="接收单位" Width="250"
+                                              IsColumnFiltered="True"
+                                              ElementStyle="{StaticResource TextColumnElementStyle}"
+                                              Binding="{Binding ReceivedCompanyName}" />
+                <fdg:FilterDataGridTextColumn Header="发运包数" Width="100"
+                                              IsColumnFiltered="True"
+                                              ElementStyle="{StaticResource TextColumnElementStyle}"
+                                              Binding="{Binding ShippedPackets}" />
+                <fdg:FilterDataGridTextColumn Header="发运数量" Width="100"
+                                              IsColumnFiltered="True"
+                                              ElementStyle="{StaticResource TextColumnElementStyle}"
+                                              Binding="{Binding ShippedQty}" />
+                <fdg:FilterDataGridTextColumn Header="发运时间" Width="100"
+                                              IsColumnFiltered="True"
+                                              ElementStyle="{StaticResource TextColumnElementStyle}"
+                                              Binding="{Binding ShippedDate, StringFormat='yyyy/MM/dd'}" />
+                <fdg:FilterDataGridTextColumn Header="发运承办人" Width="100"
+                                              IsColumnFiltered="True"
+                                              ElementStyle="{StaticResource TextColumnElementStyle}"
+                                              Binding="{Binding ShippedMan}" />
+                <fdg:FilterDataGridTextColumn Header="发运联系电话" Width="200"
+                                              IsColumnFiltered="True"
+                                              ElementStyle="{StaticResource TextColumnElementStyle}"
+                                              Binding="{Binding ShippedTel}" />
+                <fdg:FilterDataGridTextColumn Header="接收包数" Width="100"
+                                              IsColumnFiltered="True"
+                                              ElementStyle="{StaticResource TextColumnElementStyle}"
+                                              Binding="{Binding ReceivedPackets}" />
+                <fdg:FilterDataGridTextColumn Header="接收数量" Width="100"
+                                              IsColumnFiltered="True"
+                                              ElementStyle="{StaticResource TextColumnElementStyle}"
+                                              Binding="{Binding ReceivedQty}" />
+                <fdg:FilterDataGridTextColumn Header="接收时间" Width="100"
+                                              IsColumnFiltered="True"
+                                              ElementStyle="{StaticResource TextColumnElementStyle}"
+                                              Binding="{Binding ReceivedDate, StringFormat='yyyy/MM/dd'}" />
+                <fdg:FilterDataGridTextColumn Header="接收承办人" Width="100"
+                                              IsColumnFiltered="True"
+                                              ElementStyle="{StaticResource TextColumnElementStyle}"
+                                              Binding="{Binding ReceivedMan}" />
+                <fdg:FilterDataGridTextColumn Header="接收联系电话" Width="200"
+                                              IsColumnFiltered="True"
+                                              ElementStyle="{StaticResource TextColumnElementStyle}"
+                                              Binding="{Binding ReceivedTel}" />
+                <fdg:FilterDataGridTextColumn Header="接收状态" Width="100"
+                                              IsColumnFiltered="True"
+                                              ElementStyle="{StaticResource TextColumnElementStyle}"
+                                              Binding="{Binding ReceivedStatus}" />
+                <!-- 只读 -->
+                <fdg:FilterDataGridCheckBoxColumn Header="是否导出附件4" Width="100"
+                                                  Binding="{Binding IsExportUsage}"
+                                                  IsReadOnly="True">
+                    <fdg:FilterDataGridCheckBoxColumn.ElementStyle>
+                        <Style TargetType="CheckBox">
+                            <Setter Property="HorizontalAlignment" Value="Center" />
+                            <Setter Property="VerticalAlignment" Value="Center" />
+                            <Setter Property="IsEnabled" Value="False" />
+                        </Style>
+                    </fdg:FilterDataGridCheckBoxColumn.ElementStyle>
+                </fdg:FilterDataGridCheckBoxColumn>
+
+            </fdg:FilterDataGrid.Columns>
+        </fdg:FilterDataGrid>
+
+        <!-- todo 拖动限制:左右拖动至少留出检验申请/发货单的一部分,上下拖动至少;留出主表列标题/子表工具栏+列标题 -->
+        <!-- 水平拖动分割线 -->
+        <GridSplitter Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Height="5" HorizontalAlignment="Stretch" />
+
+        <!-- 发货单信息界面 -->
+        <Border Grid.Row="3" 
+                Grid.RowSpan="2"
+                Grid.Column="0"
+                BorderBrush="Gray"
+                BorderThickness="1">
+            <Grid Background="White">
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="*" />
+                    <RowDefinition Height="auto" />
+                    <RowDefinition Height="auto" />
+                    <RowDefinition Height="auto" />
+                    <RowDefinition Height="auto" />
+                    <RowDefinition Height="*" />
+                </Grid.RowDefinitions>
+
+                <!-- 表头信息 -->
+                <Border Grid.Row="1" 
+                        BorderBrush="Gray" 
+                        BorderThickness="1">
+                    <Grid Background="White">
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="auto" />
+                            <ColumnDefinition Width="*" />
+                        </Grid.ColumnDefinitions>
+
+                        <Label Grid.Column="0" Content="表头信息" 
+                               Style="{StaticResource CustomLabelStyle}" />
+
+                        <WrapPanel Grid.Column="1" 
+                                   Orientation="Horizontal">
+                            <TextBox Tag="产品名称:"
+                                     Text="{Binding CurrDeliveryReceipt.ProductName}"
+                                     IsEnabled="False"
+                                     Style="{StaticResource CustomFieldTextBoxStyle}" />
+                            <TextBox Tag="生产企业:"
+                                     Text="{Binding CurrDeliveryReceipt.CompanyName}"
+                                     IsEnabled="False"
+                                     Style="{StaticResource CustomFieldTextBoxStyle}" />
+                            <TextBlock x:Name="tBlkLicence"
+                                       Text="许可证*:"
+                                       Style="{StaticResource CustomTextBlockStyle}" />
+                            <!-- 不能设置 Background 否则会识别不到 MouseHover 事件;默认颜色是 Gainsboro  -->
+                            <Button x:Name="btnUploadLic" 
+                                    Margin="5" 
+                                    Height="30"
+                                    Content="上传许可证"
+                                    Tag="{x:Static utils:RegularFontUtil.Document_Add_24}"
+                                    Template="{StaticResource CustomButtonTemplate}"
+                                    Command="{Binding UploadLicenceCommand}" />
+                            <Button x:Name="btnDownloadLic" 
+                                    Margin="5" 
+                                    Height="30"
+                                    Content="下载许可证"
+                                    Tag="{x:Static utils:RegularFontUtil.Document_Arrow_Down_24}"
+                                    Template="{StaticResource CustomButtonTemplate}"
+                                    Command="{Binding DownloadLicenceCommand}" />
+                            <!--<TextBlock x:Name="tBlkLicIcon"
+                                       Width="auto"
+                                       FontSize="18"
+                                       VerticalAlignment="Center"
+                                       Margin="1"
+                                       FontFamily="{StaticResource FluentSystemIconsRegular}"
+                                       Text="{x:Static utils:RegularFontUtil.Attach_24}" />
+                            <TextBlock x:Name="tBlkLicHint" 
+                                       Width="auto"
+                                       FontSize="12"
+                                       VerticalAlignment="Center"
+                                       Margin="1"
+                                       Text="许可证未上传"
+                                       Foreground="Red" />-->
+                        </WrapPanel>
+                    </Grid>
+
+                </Border>
+
+                <!-- 合同信息 -->
+                <Border Grid.Row="2" 
+                        BorderBrush="Gray" 
+                        BorderThickness="1">
+                    <Grid Background="White">
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="auto" />
+                            <ColumnDefinition Width="*" />
+                        </Grid.ColumnDefinitions>
+
+                        <Label Grid.Column="0" Content="合同信息" 
+                               Style="{StaticResource CustomLabelStyle}" />
+
+                        <WrapPanel Grid.Column="1" 
+                                   Orientation="Horizontal">
+                            <TextBox Tag="合同编号:"
+                                     Text="{Binding CurrDeliveryReceipt.ContractNo}"
+                                     IsEnabled="False"
+                                     Style="{StaticResource CustomFieldTextBoxStyle}" />
+                            <TextBox Tag="采购机构:"
+                                     Text="{Binding CurrDeliveryReceipt.PurchaseCompanyName}"
+                                     IsEnabled="False"
+                                     Style="{StaticResource CustomFieldTextBoxStyle}" />
+                            <TextBox Tag="合同数量:"
+                                     Text="{Binding CurrDeliveryReceipt.ContractQty}"
+                                     IsEnabled="False"
+                                     Style="{StaticResource CustomFieldTextBoxStyle}" />
+                            <TextBox Tag="合同时间:"
+                                     Text="{Binding CurrDeliveryReceipt.ContractSigningDate, StringFormat='yyyy/MM/dd'}"
+                                     IsEnabled="False"
+                                     Style="{StaticResource CustomFieldTextBoxStyle}" />
+
+                        </WrapPanel>
+                    </Grid>
+
+                </Border>
+
+                <!-- 发运信息 -->
+                <Border Grid.Row="3" 
+                        BorderBrush="Gray" 
+                        BorderThickness="1">
+                    <Grid Background="White">
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="auto" />
+                            <ColumnDefinition Width="*" />
+                        </Grid.ColumnDefinitions>
+
+                        <Label Grid.Column="0" Content="发运信息" 
+                               Style="{StaticResource CustomLabelStyle}" />
+
+                        <WrapPanel Grid.Column="1"
+                                   Orientation="Horizontal">
+                            <TextBox Tag="接收单位:"
+                                     Text="{Binding CurrDeliveryReceipt.ReceivedCompanyName}"
+                                     IsEnabled="False"
+                                     Style="{StaticResource CustomFieldTextBoxStyle}" />
+                            <TextBox Tag="发运包数:"
+                                     Text="{Binding CurrDeliveryReceipt.ShippedPackets}"
+                                     IsEnabled="False"
+                                     Style="{StaticResource CustomFieldTextBoxStyle}" />
+                            <TextBox Tag="发运数量:"
+                                     Text="{Binding CurrDeliveryReceipt.ShippedQty}"
+                                     IsEnabled="False"
+                                     Style="{StaticResource CustomFieldTextBoxStyle}" />
+                            <TextBox Tag="发运时间:"
+                                     Text="{Binding CurrDeliveryReceipt.ShippedDate, StringFormat= 'yyyy/MM/dd'}"
+                                     IsEnabled="False"
+                                     Style="{StaticResource CustomFieldTextBoxStyle}" />
+                            <TextBox Tag="发运承办人:"
+                                     Text="{Binding CurrDeliveryReceipt.ShippedMan}"
+                                     IsEnabled="False"
+                                     Style="{StaticResource CustomFieldTextBoxStyle}" />
+                            <TextBox Tag="联系电话:"
+                                     Text="{Binding CurrDeliveryReceipt.ShippedTel}"
+                                     IsEnabled="False"
+                                     Style="{StaticResource CustomFieldTextBoxStyle}" />
+                        </WrapPanel>
+                    </Grid>
+
+                </Border>
+
+                <!-- 接收信息 -->
+                <Border x:Name="borderReceipt"
+                        Grid.Row="4" 
+                        BorderBrush="Gray" 
+                        BorderThickness="1">
+                    <Grid Background="White">
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="auto" />
+                            <ColumnDefinition Width="*" />
+                        </Grid.ColumnDefinitions>
+
+                        <Label Grid.Column="0" Content="接收信息" 
+                               Style="{StaticResource CustomLabelStyle}" />
+
+                        <WrapPanel Grid.Column="1"
+                                   Orientation="Horizontal">
+                            <TextBox Tag="接收包数:"
+                                     Text="{Binding CurrDeliveryReceipt.ReceivedPackets}"
+                                     IsEnabled="False"
+                                     Style="{StaticResource CustomFieldTextBoxStyle}" />
+                            <TextBox Tag="接收数量:"
+                                     Text="{Binding CurrDeliveryReceipt.ReceivedQty}"
+                                     IsEnabled="False"
+                                     Style="{StaticResource CustomFieldTextBoxStyle}" />
+                            <TextBox Tag="接收时间:"
+                                     Text="{Binding CurrDeliveryReceipt.ReceivedDate, StringFormat= 'yyyy/MM/dd'}"
+                                     IsEnabled="False"
+                                     Style="{StaticResource CustomFieldTextBoxStyle}" />
+                            <TextBox Tag="接收承办人:"
+                                     Text="{Binding CurrDeliveryReceipt.ReceivedMan}"
+                                     IsEnabled="False"
+                                     Style="{StaticResource CustomFieldTextBoxStyle}" />
+                            <TextBox Tag="联系电话:"
+                                     Text="{Binding CurrDeliveryReceipt.ReceivedTel}"
+                                     IsEnabled="False"
+                                     Style="{StaticResource CustomFieldTextBoxStyle}" />
+                            <TextBox Tag="收货单状态:"
+                                     Text="{Binding CurrDeliveryReceipt.ReceivedStatus}"
+                                     IsEnabled="False"
+                                     Style="{StaticResource CustomFieldTextBoxStyle}" />
+                        </WrapPanel>
+
+                    </Grid>
+                </Border>
+
+            </Grid>
+        </Border>
+
+        <!-- 明细表工具栏 -->
+        <Border Grid.Row="3" 
+                Grid.Column="1" 
+                BorderBrush="Gray"
+                BorderThickness="1"
+                Height="{Binding Path=ActualHeight,ElementName=toolBarDelivery}">
+            <ToolBar Background="White"
+                     ToolBarTray.IsLocked="True">
+                <Button x:Name="btnUseAll"
+                        Content="全部使用"
+                        Tag="{x:Static utils:RegularFontUtil.Box_Checkmark_24}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
+                        Command="{Binding UseAllCommand}"
+                        CommandParameter="{Binding ElementName=fdgDeliveryDetail}" />
+            </ToolBar>
+        </Border>
+
+        <!-- 明细表 -->
+        <fdg:FilterDataGrid x:Name="fdgDeliveryDetail"
+                            Grid.Row="4" 
+                            Grid.Column="1" 
+                            Background="White"
+                            AutoGenerateColumns="False"
+                            CanUserAddRows="False"
+                            CanUserResizeRows="False"
+                            SelectionMode="Extended"
+                            SelectionUnit="CellOrRowHeader"
+                            HeadersVisibility="All"
+                            GridLinesVisibility="All"
+                            ShowRowsCount="True"
+                            RowHeaderWidth="30"
+                            LoadingRow="DataGrid_LoadingRow"
+                            ColumnHeaderStyle="{StaticResource CustomColumnHeaderStyle}"
+                            RowHeaderStyle="{StaticResource CustomRowHeaderStyle}"
+                            RowStyle="{StaticResource CustomRowStyle}"
+                            CellStyle="{StaticResource CustomCellStyle}"
+                            ItemsSource="{Binding DeliveryReceiptDetails, Mode=TwoWay}">
+
+            <!-- DataGrid 事件 -->
+            <b:Interaction.Triggers>
+                <!-- 选择单元格切换事件 -->
+                <b:EventTrigger EventName="SelectedCellsChanged">
+                    <b:CallMethodAction TargetObject="{Binding }"
+                                        MethodName="FdgDeliveryDetail_SelectedCellsChanged" />
+                </b:EventTrigger>
+            </b:Interaction.Triggers>
+
+            <!-- 数据结构 -->
+            <fdg:FilterDataGrid.Columns>
+                <fdg:FilterDataGridTextColumn Header="材料批号" Width="200"
+                                              ElementStyle="{StaticResource TextColumnElementStyle}"
+                                              Binding="{Binding BatchNo, UpdateSourceTrigger=PropertyChanged}"
+                                              IsReadOnly="True" />
+                <fdg:FilterDataGridTextColumn Header="包号" Width="100"
+                                              ElementStyle="{StaticResource TextColumnElementStyle}"
+                                              Binding="{Binding PacketNo, UpdateSourceTrigger=PropertyChanged}"
+                                              IsReadOnly="True" />
+                <fdg:FilterDataGridTextColumn Header="发运数量" Width="100"
+                                              ElementStyle="{StaticResource TextColumnElementStyle}"
+                                              Binding="{Binding ShippedQuantity, UpdateSourceTrigger=PropertyChanged, StringFormat='#0.0'}"
+                                              IsReadOnly="True" />
+                <fdg:FilterDataGridTextColumn Header="接收数量" Width="100"
+                                              ElementStyle="{StaticResource TextColumnElementStyle}"
+                                              Binding="{Binding ReceiveQuantity, UpdateSourceTrigger=PropertyChanged, StringFormat='#0.0'}"
+                                              IsReadOnly="True" />
+                <fdg:FilterDataGridTextColumn Header="使用数量*" Width="100"
+                                              ElementStyle="{StaticResource TextColumnElementStyle}"
+                                              EditingElementStyle="{StaticResource TextColumnEditingStyle}"
+                                              Binding="{Binding UsageQuantity, UpdateSourceTrigger=PropertyChanged, StringFormat='#0.0'}" />
+                <fdg:FilterDataGridTextColumn Header="使用状态" Width="100"
+                                              ElementStyle="{StaticResource TextColumnElementStyle}"
+                                              Binding="{Binding UsageStatus, UpdateSourceTrigger=PropertyChanged}"
+                                              IsReadOnly="True" />
+                <fdg:FilterDataGridTextColumn Header="备注" Width="200"
+                                              ElementStyle="{StaticResource TextColumnElementStyle}"
+                                              EditingElementStyle="{StaticResource TextColumnEditingStyle}"
+                                              Binding="{Binding ShipNote, UpdateSourceTrigger=PropertyChanged}" />
+            </fdg:FilterDataGrid.Columns>
+        </fdg:FilterDataGrid>
     </Grid>
+
 </UserControl>

+ 22 - 1
UniformMaterialManagementSystem/Views/MaterialUsagePage.xaml.cs

@@ -12,6 +12,7 @@ using System.Windows.Media;
 using System.Windows.Media.Imaging;
 using System.Windows.Navigation;
 using System.Windows.Shapes;
+using Microsoft.Extensions.DependencyInjection;
 using UniformMaterialManagementSystem.ViewModels;
 
 namespace UniformMaterialManagementSystem.Views
@@ -26,7 +27,27 @@ namespace UniformMaterialManagementSystem.Views
             InitializeComponent();
 
             // 展示材料使用单
-            this.currContentControl.Content = new DeliveryReceiptControl(DeliveryPageCategroy.Usage);
+            this.DataContext = App.Current.Services.GetService<MaterialUsageViewModel>();
+
+            this.IsVisibleChanged += MaterialUsagePage_IsVisibleChanged;
+        }
+
+        // 控件属性注册事件
+        private void DataGrid_LoadingRow(object? sender, DataGridRowEventArgs e)
+        {
+            // 设置行标题:行索引+1
+            e.Row.Header = e.Row.GetIndex() + 1;
+        }
+
+        private void MaterialUsagePage_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
+        {
+            bool isVisible = (bool)e.NewValue;
+            if (isVisible)
+            {
+                // 每次打开之前重新取数
+                MaterialUsageViewModel? vm = this.DataContext as MaterialUsageViewModel;
+                vm?.LoadUsageData();
+            }
         }
     }
 }

+ 4 - 4
UniformMaterialManagementSystem/Views/SelectApplyContractDialog.xaml

@@ -15,7 +15,7 @@
 
     <UserControl.Resources>
         <!-- 工具栏按钮模板 -->
-        <ControlTemplate x:Key="CustomToolBarButtomTemplate" TargetType="Button">
+        <ControlTemplate x:Key="CustomToolBarButtonTemplate" TargetType="Button">
             <!-- 按钮文本超出2个字,设置 Width 宽度自适应。再设置 Padding ,增加按钮左右的空白 -->
             <Border Width="auto"
              Background="{TemplateBinding Background}" 
@@ -216,13 +216,13 @@
                 <Button Content="查询"
                         Margin="5 0 5 0"
                         Tag="{x:Static utils:RegularFontUtil.Search_32}"
-                        Template="{StaticResource CustomToolBarButtomTemplate}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
                         Command="{Binding QueryCommand}" >
                 </Button>
                 <Separator />
                 <Button Content="确认选择"
                         Tag="{x:Static utils:RegularFontUtil.Checkmark_32}"
-                        Template="{StaticResource CustomToolBarButtomTemplate}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
                         Command="{Binding ConfirmCommand}"
                         CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}">
                     <!--<Button.CommandParameter>
@@ -234,7 +234,7 @@
                 </Button>
                 <Button Content="取消"
                         Tag="{x:Static utils:RegularFontUtil.Dismiss_32}"
-                        Template="{StaticResource CustomToolBarButtomTemplate}"
+                        Template="{StaticResource CustomToolBarButtonTemplate}"
                         Command="{Binding CancelCommand}"
                         CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" />
 

+ 4 - 4
UniformMaterialManagementSystem/Views/UsersControl.xaml

@@ -16,7 +16,7 @@
 
     <UserControl.Resources>
         <!-- 工具栏按钮模板 -->
-        <ControlTemplate x:Key="CustomToolBarButtomTemplate" TargetType="Button">
+        <ControlTemplate x:Key="CustomToolBarButtonTemplate" TargetType="Button">
             <Border Width="40"
              Background="{TemplateBinding Background}"
              CornerRadius="5">
@@ -166,14 +166,14 @@
                             CommandParameter="{Binding ElementName=dataGridUser}"
                             Content="新增"
                             Tag="{x:Static utils:RegularFontUtil.Add_Circle_32}"
-                            Template="{StaticResource CustomToolBarButtomTemplate}" />
+                            Template="{StaticResource CustomToolBarButtonTemplate}" />
                     <Button x:Name="tsBtnDelete"
                             HorizontalContentAlignment="Center"
                             Command="{Binding DeleteCommand}"
                             CommandParameter="{Binding ElementName=dataGridUser}"
                             Content="删除"
                             Tag="{x:Static utils:RegularFontUtil.Delete_32}"
-                            Template="{StaticResource CustomToolBarButtomTemplate}" />
+                            Template="{StaticResource CustomToolBarButtonTemplate}" />
                     <Separator />
                     <Button x:Name="tsBtnSave"
                             HorizontalContentAlignment="Center"
@@ -181,7 +181,7 @@
                             CommandParameter="{Binding ElementName=dataGridUser}"
                             Content="保存"
                             Tag="{x:Static utils:RegularFontUtil.Save_32}"
-                            Template="{StaticResource CustomToolBarButtomTemplate}" />
+                            Template="{StaticResource CustomToolBarButtonTemplate}" />
                 </ToolBar>
             </ToolBarPanel>
         </Border>