2 Commits b5925be275 ... 3d6c95151b

Author SHA1 Message Date
  刘彬 3d6c95151b Merge branch 'master' of http://49.235.130.76/LN/LPLocalhostMES 2 weeks ago
  刘彬 733ce86c07 关键件导入数据库 2 weeks ago

+ 25 - 0
LocalhostMES/DataBase/DatabaseHelper.cs

@@ -48,6 +48,7 @@ namespace LocalhostMES.DataBase
                 Db.CodeFirst.InitTables<ParkingLot>();
                 Db.CodeFirst.InitTables<WorkOrderInfo>();
                 Db.CodeFirst.InitTables<LocalhostPartInfo>();
+                EnsureLocalhostPartInfoColumns();
                 // 您的数据库操作代码
             }
             catch (AggregateException ex)
@@ -78,6 +79,14 @@ namespace LocalhostMES.DataBase
 
         }
 
+        private static void EnsureLocalhostPartInfoColumns()
+        {
+            Db.Ado.ExecuteCommand("ALTER TABLE `LocalhostPartInfo` ADD COLUMN IF NOT EXISTS `MaterialCode` varchar(128) NULL");
+            Db.Ado.ExecuteCommand("ALTER TABLE `LocalhostPartInfo` ADD COLUMN IF NOT EXISTS `StationCode` varchar(128) NULL");
+            Db.Ado.ExecuteCommand("ALTER TABLE `LocalhostPartInfo` ADD COLUMN IF NOT EXISTS `PositionCode` varchar(128) NULL");
+            Db.Ado.ExecuteCommand("ALTER TABLE `LocalhostPartInfo` ADD COLUMN IF NOT EXISTS `PartInfoName` varchar(200) NULL");
+        }
+
         public static int GetProductProductionRecords(string sn)
         {
             try
@@ -394,6 +403,22 @@ namespace LocalhostMES.DataBase
             return ok;
         }
 
+        public static int InsertPartInfos(List<LocalhostPartInfo> infos)
+        {
+            if (infos == null || infos.Count == 0)
+            {
+                return 0;
+            }
+
+            var affected = Db.Storageable(infos).ExecuteCommand();
+            if (affected > 0)
+            {
+                NotifyChanged(MesDataScope.KeyPart);
+            }
+
+            return affected;
+        }
+
         public static bool DeletePartInfo(KeyPartStationType stationType, string partNum)
         {
             var ok = Db.Deleteable<LocalhostPartInfo>()

+ 15 - 8
LocalhostMES/Enums/KeyPartStationType.cs

@@ -1,17 +1,24 @@
 namespace LocalhostMES.Enums
 {
     /// <summary>
-    /// 关键件维护用简化工站类型(与产线详细左右工位枚举区分)
+    /// 关键件维护工站类型:OP10 不分左右,其余工站按左右区分
     /// </summary>
     public enum KeyPartStationType
     {
         OP10,
-        OP20,
-        OP30,
-        OP40,
-        OP50,
-        OP60,
-        OP70,
-        OP80
+        OP20L,
+        OP20R,
+        OP30L,
+        OP30R,
+        OP40L,
+        OP40R,
+        OP50L,
+        OP50R,
+        OP60L,
+        OP60R,
+        OP70L,
+        OP70R,
+        OP80L,
+        OP80R
     }
 }

+ 285 - 0
LocalhostMES/Helpers/KeyPartExcelImporter.cs

@@ -0,0 +1,285 @@
+using LocalhostMES.Enums;
+using LocalhostMES.Models;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Xml.Linq;
+
+namespace LocalhostMES.Helpers
+{
+    public static class KeyPartExcelImporter
+    {
+        private sealed class RowData
+        {
+            public string HandlePosition { get; set; }
+            public string MaterialCode { get; set; }
+            public string WireHarness { get; set; }
+            public string Actuator { get; set; }
+            public string CoverPlate { get; set; }
+        }
+
+        public static List<LocalhostPartInfo> BuildPartInfosFromExcel(string excelPath)
+        {
+            if (string.IsNullOrWhiteSpace(excelPath))
+            {
+                throw new ArgumentException("Excel路径不能为空", nameof(excelPath));
+            }
+            if (!File.Exists(excelPath))
+            {
+                throw new FileNotFoundException("Excel文件不存在", excelPath);
+            }
+
+            var rows = ReadRows(excelPath);
+            var parts = new List<LocalhostPartInfo>();
+            foreach (var row in rows)
+            {
+                if (string.IsNullOrWhiteSpace(row.MaterialCode))
+                {
+                    continue;
+                }
+
+                var isLeft = (row.HandlePosition ?? string.Empty).Contains("左");
+                var side = isLeft ? "L" : "R";
+
+                parts.Add(CreatePart(
+                    KeyPartStationType.OP10,
+                    row.MaterialCode,
+                    row.MaterialCode,
+                    "把手零件号",
+                    string.Empty,
+                    string.Empty,
+                    1m));
+
+                parts.Add(CreatePart(
+                    KeyPartStationType.OP20L,
+                    row.WireHarness,
+                    row.MaterialCode,
+                    "线束",
+                    "HANDLE-ZP01-L-02",
+                    "HANDLE-ZP01-L-02-015",
+                    1m));
+
+                parts.Add(CreatePart(
+                    KeyPartStationType.OP20R,
+                    row.WireHarness,
+                    row.MaterialCode,
+                    "线束",
+                    "HANDLE-ZP01-R-02",
+                    "HANDLE-ZP01-R-02-01",
+                    1m));
+
+                parts.Add(CreatePart(
+                    isLeft ? KeyPartStationType.OP30L : KeyPartStationType.OP30R,
+                    row.MaterialCode,
+                    row.MaterialCode,
+                    string.Empty,
+                    $"HANDLE-ZP01-{side}-03",
+                    $"HANDLE-ZP01-{side}-03-01",
+                    1m));
+
+                parts.Add(CreatePart(
+                    isLeft ? KeyPartStationType.OP40L : KeyPartStationType.OP40R,
+                    row.MaterialCode,
+                    row.MaterialCode,
+                    string.Empty,
+                    $"HANDLE-ZP01-{side}-04",
+                    $"HANDLE-ZP01-{side}-04-01",
+                    1m));
+
+                parts.Add(CreatePart(
+                    isLeft ? KeyPartStationType.OP50L : KeyPartStationType.OP50R,
+                    row.MaterialCode,
+                    row.MaterialCode,
+                    string.Empty,
+                    $"HANDLE-ZP01-{side}-05",
+                    $"HANDLE-ZP01-{side}-05-01",
+                    1m));
+
+                parts.Add(CreatePart(
+                    isLeft ? KeyPartStationType.OP60L : KeyPartStationType.OP60R,
+                    row.CoverPlate,
+                    row.MaterialCode,
+                    "盖板",
+                    $"HANDLE-ZP01-{side}-06",
+                    $"HANDLE-ZP01-{side}-06-01",
+                    1m));
+
+                parts.Add(CreatePart(
+                    isLeft ? KeyPartStationType.OP70L : KeyPartStationType.OP70R,
+                    row.Actuator,
+                    row.MaterialCode,
+                    "执行器",
+                    $"HANDLE-ZP01-{side}-07",
+                    $"HANDLE-ZP01-{side}-07-01",
+                    1m));
+            }
+
+            return parts
+                .Where(p => p != null)
+                .GroupBy(p => new { p.StationType, p.PartNum })
+                .Select(g => g.Last())
+                .ToList();
+        }
+
+        private static LocalhostPartInfo CreatePart(
+            KeyPartStationType stationType,
+            string partNum,
+            string materialCode,
+            string partInfoName,
+            string stationCode,
+            string positionCode,
+            decimal partQty)
+        {
+            var pn = (partNum ?? string.Empty).Trim();
+            var material = (materialCode ?? string.Empty).Trim();
+            if (string.IsNullOrWhiteSpace(pn))
+            {
+                return null;
+            }
+
+            return new LocalhostPartInfo
+            {
+                StationType = stationType,
+                PartNum = pn,
+                MaterialCode = material,
+                PartInfoName = (partInfoName ?? string.Empty).Trim(),
+                StationCode = (stationCode ?? string.Empty).Trim(),
+                PositionCode = (positionCode ?? string.Empty).Trim(),
+                PartQty = partQty
+            };
+        }
+
+        private static List<RowData> ReadRows(string excelPath)
+        {
+            var rows = new List<RowData>();
+            using (var stream = File.OpenRead(excelPath))
+            using (var zip = new ZipArchive(stream, ZipArchiveMode.Read, false))
+            {
+                var sharedStrings = ReadSharedStrings(zip);
+                var sheetEntry = zip.GetEntry("xl/worksheets/sheet1.xml")
+                    ?? zip.Entries.FirstOrDefault(e => e.FullName.StartsWith("xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase));
+                if (sheetEntry == null)
+                {
+                    return rows;
+                }
+
+                XDocument sheetXml;
+                using (var sheetStream = sheetEntry.Open())
+                {
+                    sheetXml = XDocument.Load(sheetStream);
+                }
+
+                XNamespace ns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
+                bool headerFound = false;
+                foreach (var row in sheetXml.Descendants(ns + "row"))
+                {
+                    var cellMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+                    foreach (var c in row.Elements(ns + "c"))
+                    {
+                        var refName = c.Attribute("r")?.Value;
+                        if (string.IsNullOrEmpty(refName))
+                        {
+                            continue;
+                        }
+
+                        var col = GetColumnLetters(refName);
+                        cellMap[col] = ReadCellValue(c, sharedStrings, ns);
+                    }
+
+                    var bVal = GetCell(cellMap, "B");
+                    var dVal = GetCell(cellMap, "D");
+                    if (!headerFound)
+                    {
+                        if ((bVal ?? string.Empty).Contains("手柄位置") || (dVal ?? string.Empty).Contains("把手总成零件号"))
+                        {
+                            headerFound = true;
+                        }
+                        continue;
+                    }
+
+                    if (string.IsNullOrWhiteSpace(dVal))
+                    {
+                        continue;
+                    }
+
+                    rows.Add(new RowData
+                    {
+                        HandlePosition = bVal,
+                        MaterialCode = dVal,
+                        WireHarness = GetCell(cellMap, "F"),
+                        Actuator = GetCell(cellMap, "G"),
+                        CoverPlate = GetCell(cellMap, "H")
+                    });
+                }
+            }
+
+            return rows;
+        }
+
+        private static string GetCell(Dictionary<string, string> row, string column)
+        {
+            return row.TryGetValue(column, out var v) ? v : string.Empty;
+        }
+
+        private static List<string> ReadSharedStrings(ZipArchive zip)
+        {
+            var entry = zip.GetEntry("xl/sharedStrings.xml");
+            if (entry == null)
+            {
+                return new List<string>();
+            }
+
+            XDocument doc;
+            using (var s = entry.Open())
+            {
+                doc = XDocument.Load(s);
+            }
+
+            XNamespace ns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
+            return doc.Descendants(ns + "si")
+                .Select(si =>
+                {
+                    var t = si.Element(ns + "t");
+                    if (t != null)
+                    {
+                        return t.Value;
+                    }
+                    return string.Concat(si.Descendants(ns + "t").Select(x => x.Value));
+                })
+                .ToList();
+        }
+
+        private static string ReadCellValue(XElement c, List<string> sharedStrings, XNamespace ns)
+        {
+            var type = c.Attribute("t")?.Value;
+            if (string.Equals(type, "inlineStr", StringComparison.OrdinalIgnoreCase))
+            {
+                return c.Element(ns + "is")?.Element(ns + "t")?.Value ?? string.Empty;
+            }
+
+            var v = c.Element(ns + "v")?.Value ?? string.Empty;
+            if (string.Equals(type, "s", StringComparison.OrdinalIgnoreCase) && int.TryParse(v, out var idx))
+            {
+                if (idx >= 0 && idx < sharedStrings.Count)
+                {
+                    return sharedStrings[idx];
+                }
+                return string.Empty;
+            }
+
+            return v;
+        }
+
+        private static string GetColumnLetters(string cellReference)
+        {
+            if (string.IsNullOrEmpty(cellReference))
+            {
+                return string.Empty;
+            }
+            var chars = cellReference.TakeWhile(ch => char.IsLetter(ch)).ToArray();
+            return new string(chars);
+        }
+    }
+}

+ 1 - 0
LocalhostMES/LocalhostMES.csproj

@@ -418,6 +418,7 @@
     <Compile Include="Api\Services\MesApiClient.cs" />
     <Compile Include="Api\Services\MesMomHttpEndpoints.cs" />
     <Compile Include="Helpers\DateCodeConverter.cs" />
+    <Compile Include="Helpers\KeyPartExcelImporter.cs" />
     <Compile Include="Infrastructure\PrismDependencyResolver.cs" />
     <Compile Include="Core\LogHelper.cs" />
     <Compile Include="Core\MesDataChangedEvent.cs" />

+ 9 - 1
LocalhostMES/Models/MesModel.cs

@@ -186,13 +186,17 @@ namespace LocalhostMES.Models
         public string positionNo { get; set; }                   // 位置序号
     }
     /// <summary>
-    /// 本地维护的关键件清单(工站类型 + 关键件号 + 数量)。
+    /// 本地维护的关键件清单(工站类型 + 关键件号 + 数量 + 物料/工位/站点/名称)。
     /// </summary>
     public class LocalhostPartInfo : BindableBase
     {
         private KeyPartStationType _stationType;
         private string _partNum;
         private decimal _partQty;
+        private string _materialCode;
+        private string _stationCode;
+        private string _positionCode;
+        private string _partInfoName;
 
         [SqlSugar.SugarColumn(IsPrimaryKey = true)]
         public KeyPartStationType StationType { get => _stationType; set => SetProperty(ref _stationType, value); }
@@ -201,6 +205,10 @@ namespace LocalhostMES.Models
         public string PartNum { get => _partNum; set => SetProperty(ref _partNum, value); }
 
         public decimal PartQty { get => _partQty; set => SetProperty(ref _partQty, value); }
+        public string MaterialCode { get => _materialCode; set => SetProperty(ref _materialCode, value); }
+        public string StationCode { get => _stationCode; set => SetProperty(ref _stationCode, value); }
+        public string PositionCode { get => _positionCode; set => SetProperty(ref _positionCode, value); }
+        public string PartInfoName { get => _partInfoName; set => SetProperty(ref _partInfoName, value); }
     }
     public class BindRecord
     {

+ 58 - 1
LocalhostMES/ViewModels/Tabs/KeyPartManagementViewModel.cs

@@ -1,10 +1,12 @@
 using LocalhostMES.DataBase;
 using LocalhostMES.Enums;
 using LocalhostMES.Core;
+using LocalhostMES.Helpers;
 using LocalhostMES.Models;
 using LocalhostMES.ViewModels.Services;
 using Prism.Commands;
 using Prism.Mvvm;
+using System;
 using System.Collections.ObjectModel;
 using System.Windows;
 
@@ -65,14 +67,49 @@ namespace LocalhostMES.ViewModels.Tabs
         public DelegateCommand<LocalhostPartInfo> DeletePartRowCommand =>
             _deletePartRowCommand ?? (_deletePartRowCommand = new DelegateCommand<LocalhostPartInfo>(DeletePartRow, p => p != null));
 
+        private string _importExcelPath = @"h:\领跑视觉\零件绑定对应表.xlsx";
+        public string ImportExcelPath
+        {
+            get => _importExcelPath;
+            set => SetProperty(ref _importExcelPath, value);
+        }
+
+        private DelegateCommand _importFromExcelCommand;
+        public DelegateCommand ImportFromExcelCommand =>
+            _importFromExcelCommand ?? (_importFromExcelCommand = new DelegateCommand(ImportFromExcel));
+
         private void AddPartInfo()
         {
             var pn = (NewPart.PartNum ?? string.Empty).Trim();
+            var materialCode = (NewPart.MaterialCode ?? string.Empty).Trim();
+            var stationCode = (NewPart.StationCode ?? string.Empty).Trim();
+            var positionCode = (NewPart.PositionCode ?? string.Empty).Trim();
+            var partInfoName = (NewPart.PartInfoName ?? string.Empty).Trim();
             if (string.IsNullOrEmpty(pn))
             {
                 _workspace.ShowStatus("请填写关键件号", true);
                 return;
             }
+            if (string.IsNullOrEmpty(materialCode))
+            {
+                _workspace.ShowStatus("请填写物料编码", true);
+                return;
+            }
+            if (string.IsNullOrEmpty(stationCode))
+            {
+                _workspace.ShowStatus("请填写工位编码", true);
+                return;
+            }
+            if (string.IsNullOrEmpty(positionCode))
+            {
+                _workspace.ShowStatus("请填写站点编码", true);
+                return;
+            }
+            if (string.IsNullOrEmpty(partInfoName))
+            {
+                _workspace.ShowStatus("请填写关键件名称", true);
+                return;
+            }
 
             if (NewPart.PartQty < 0)
             {
@@ -85,7 +122,11 @@ namespace LocalhostMES.ViewModels.Tabs
             {
                 StationType = st,
                 PartNum = pn,
-                PartQty = NewPart.PartQty
+                PartQty = NewPart.PartQty,
+                MaterialCode = materialCode,
+                StationCode = stationCode,
+                PositionCode = positionCode,
+                PartInfoName = partInfoName
             };
 
             if (DatabaseHelper.InsertPartInfo(toSave))
@@ -144,6 +185,22 @@ namespace LocalhostMES.ViewModels.Tabs
             }
         }
 
+        private void ImportFromExcel()
+        {
+            try
+            {
+                var path = (ImportExcelPath ?? string.Empty).Trim();
+                var items = KeyPartExcelImporter.BuildPartInfosFromExcel(path);
+                var affected = DatabaseHelper.InsertPartInfos(items);
+                _workspace.ShowStatus($"导入完成:构建 {items.Count} 条,入库 {affected} 条", false);
+                RefPartInfo();
+            }
+            catch (Exception ex)
+            {
+                _workspace.ShowStatus($"导入失败:{ex.Message}", true);
+            }
+        }
+
         private void OnDataChanged(object sender, MesDataChangedEventArgs e)
         {
             if (!e.Has(MesDataScope.KeyPart))

+ 42 - 3
LocalhostMES/Views/Tabs/KeyPartManagementView.xaml

@@ -13,6 +13,7 @@
             <RowDefinition Height="Auto" />
             <RowDefinition Height="Auto" />
             <RowDefinition Height="Auto" />
+            <RowDefinition Height="Auto" />
             <RowDefinition Height="*" />
             <RowDefinition Height="Auto" />
         </Grid.RowDefinitions>
@@ -35,6 +36,8 @@
                 </Grid.ColumnDefinitions>
                 <Grid.RowDefinitions>
                     <RowDefinition Height="Auto" />
+                    <RowDefinition Height="Auto" />
+                    <RowDefinition Height="Auto" />
                 </Grid.RowDefinitions>
                 <Label Grid.Row="0" Grid.Column="0">工站类型:</Label>
                 <ComboBox Grid.Row="0" Grid.Column="1" Margin="4,0" MinWidth="120"
@@ -46,14 +49,46 @@
                 <Label Grid.Row="0" Grid.Column="4">数量:</Label>
                 <TextBox Grid.Row="0" Grid.Column="5" Margin="4,0"
                          Text="{Binding NewPart.PartQty, UpdateSourceTrigger=PropertyChanged}" />
+
+                <Label Grid.Row="1" Grid.Column="0" Margin="0,8,0,0">物料编码:</Label>
+                <TextBox Grid.Row="1" Grid.Column="1" Margin="4,8,4,0"
+                         Text="{Binding NewPart.MaterialCode, UpdateSourceTrigger=PropertyChanged}" />
+                <Label Grid.Row="1" Grid.Column="2" Margin="0,8,0,0">工位编码:</Label>
+                <TextBox Grid.Row="1" Grid.Column="3" Margin="4,8,4,0"
+                         Text="{Binding NewPart.StationCode, UpdateSourceTrigger=PropertyChanged}" />
+                <Label Grid.Row="1" Grid.Column="4" Margin="0,8,0,0">站点编码:</Label>
+                <TextBox Grid.Row="1" Grid.Column="5" Margin="4,8,4,0"
+                         Text="{Binding NewPart.PositionCode, UpdateSourceTrigger=PropertyChanged}" />
+                <Label Grid.Row="2" Grid.Column="0" Margin="0,8,0,0">关键件名称:</Label>
+                <TextBox Grid.Row="2" Grid.Column="1" Margin="4,8,4,0" Grid.ColumnSpan="5"
+                         Text="{Binding NewPart.PartInfoName, UpdateSourceTrigger=PropertyChanged}" />
+            </Grid>
+        </Border>
+        <Border Grid.Row="2"
+                BorderBrush="{DynamicResource Win11BorderBrush}"
+                BorderThickness="1"
+                CornerRadius="10"
+                Padding="10"
+                Margin="0,0,0,8"
+                Background="{DynamicResource Win11CardBrush}">
+            <Grid>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="Auto" />
+                    <ColumnDefinition Width="*" />
+                    <ColumnDefinition Width="Auto" />
+                </Grid.ColumnDefinitions>
+                <Label Grid.Column="0" VerticalAlignment="Center">Excel路径:</Label>
+                <TextBox Grid.Column="1" Margin="8,0"
+                         Text="{Binding ImportExcelPath, UpdateSourceTrigger=PropertyChanged}" />
+                <Button Grid.Column="2" Content="导入表格" Style="{StaticResource SecondaryButtonStyle}" Command="{Binding ImportFromExcelCommand}" />
             </Grid>
         </Border>
-        <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,0,0,8">
+        <StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,0,0,8">
             <Button Content="添加" Style="{StaticResource SuccessButtonStyle}" Command="{Binding AddPartInfoCommand}" />
             <Button Content="刷新" Style="{StaticResource SecondaryButtonStyle}" Command="{Binding RefPartInfoCommand}" />
             <Button Content="删除所选" Style="{StaticResource DangerButtonStyle}" Command="{Binding DeletePartInfoCommand}" />
         </StackPanel>
-        <DataGrid Grid.Row="3" ItemsSource="{Binding PartInfos}" SelectedItem="{Binding SelectPart}" SelectionMode="Single"
+        <DataGrid Grid.Row="4" ItemsSource="{Binding PartInfos}" SelectedItem="{Binding SelectPart}" SelectionMode="Single"
                   AutoGenerateColumns="False" CanUserAddRows="False" IsReadOnly="True" GridLinesVisibility="None"
                   HeadersVisibility="Column" Margin="0,0,0,8">
             <DataGrid.Resources>
@@ -76,6 +111,10 @@
             <DataGrid.Columns>
                 <DataGridTextColumn Header="工站类型" Binding="{Binding StationType}" Width="100" />
                 <DataGridTextColumn Header="关键件号" Binding="{Binding PartNum}" Width="*" MinWidth="160" />
+                <DataGridTextColumn Header="关键件名称" Binding="{Binding PartInfoName}" Width="120" />
+                <DataGridTextColumn Header="物料编码" Binding="{Binding MaterialCode}" Width="120" />
+                <DataGridTextColumn Header="工位编码" Binding="{Binding StationCode}" Width="120" />
+                <DataGridTextColumn Header="站点编码" Binding="{Binding PositionCode}" Width="120" />
                 <DataGridTextColumn Header="数量" Binding="{Binding PartQty}" Width="80">
                     <DataGridTextColumn.ElementStyle>
                         <Style TargetType="TextBlock">
@@ -95,7 +134,7 @@
                 </DataGridTemplateColumn>
             </DataGrid.Columns>
         </DataGrid>
-        <StatusBar Grid.Row="4">
+        <StatusBar Grid.Row="5">
             <StatusBarItem>
                 <TextBlock>
                     <Run Text="记录总数:" />