正在显示
21 个修改的文件
包含
679 行增加
和
34 行删除
| @@ -106,6 +106,12 @@ | @@ -106,6 +106,12 @@ | ||
| 106 | <MauiXaml Update="Pages\AdminPage.xaml"> | 106 | <MauiXaml Update="Pages\AdminPage.xaml"> |
| 107 | <Generator>MSBuild:Compile</Generator> | 107 | <Generator>MSBuild:Compile</Generator> |
| 108 | </MauiXaml> | 108 | </MauiXaml> |
| 109 | + <MauiXaml Update="Pages\BinListPage.xaml"> | ||
| 110 | + <Generator>MSBuild:Compile</Generator> | ||
| 111 | + </MauiXaml> | ||
| 112 | + <MauiXaml Update="Pages\BinPickerPage.xaml"> | ||
| 113 | + <Generator>MSBuild:Compile</Generator> | ||
| 114 | + </MauiXaml> | ||
| 109 | <MauiXaml Update="Pages\HomePage.xaml"> | 115 | <MauiXaml Update="Pages\HomePage.xaml"> |
| 110 | <Generator>MSBuild:Compile</Generator> | 116 | <Generator>MSBuild:Compile</Generator> |
| 111 | </MauiXaml> | 117 | </MauiXaml> |
| @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 | @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 | ||
| 3 | # Visual Studio Version 17 | 3 | # Visual Studio Version 17 |
| 4 | VisualStudioVersion = 17.0.31903.59 | 4 | VisualStudioVersion = 17.0.31903.59 |
| 5 | MinimumVisualStudioVersion = 10.0.40219.1 | 5 | MinimumVisualStudioVersion = 10.0.40219.1 |
| 6 | -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IndustrialControl", "IndustrialControl.csproj", "{A54387CA-553D-4CE0-B86C-08A45497D703}" | 6 | +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IndustrialControl", "IndustrialControl.csproj", "{A54387CA-553D-4CE0-B86C-08A45497D703}" |
| 7 | EndProject | 7 | EndProject |
| 8 | Global | 8 | Global |
| 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution | 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution |
| @@ -24,6 +24,8 @@ namespace IndustrialControl | @@ -24,6 +24,8 @@ namespace IndustrialControl | ||
| 24 | builder.Services.AddSingleton<IConfigLoader, ConfigLoader>(); | 24 | builder.Services.AddSingleton<IConfigLoader, ConfigLoader>(); |
| 25 | builder.Services.AddSingleton<Services.LogService>(); | 25 | builder.Services.AddSingleton<Services.LogService>(); |
| 26 | builder.Services.AddSingleton<IWarehouseDataService, MockWarehouseDataService>(); | 26 | builder.Services.AddSingleton<IWarehouseDataService, MockWarehouseDataService>(); |
| 27 | + builder.Services.AddSingleton<IDialogService, DialogService>(); | ||
| 28 | + builder.Services.AddTransient<IndustrialControl.ViewModels.BinPickerViewModel>(); | ||
| 27 | 29 | ||
| 28 | // 扫码服务 | 30 | // 扫码服务 |
| 29 | builder.Services.AddSingleton<ScanService>(); | 31 | builder.Services.AddSingleton<ScanService>(); |
Pages/BinListPage.xaml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | +<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" | ||
| 3 | + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | ||
| 4 | + xmlns:srv="clr-namespace:IndustrialControl.Services" | ||
| 5 | + x:Class="IndustrialControl.Pages.BinListPage" | ||
| 6 | + Title="选择库位" | ||
| 7 | + BackgroundColor="White"> | ||
| 8 | + | ||
| 9 | + <Grid RowDefinitions="Auto,Auto,*" Padding="12"> | ||
| 10 | + <!-- 标题行 --> | ||
| 11 | + <Grid ColumnDefinitions="*,Auto" Padding="0,0,0,8"> | ||
| 12 | + <Label Text="选择库位" FontSize="18" FontAttributes="Bold" /> | ||
| 13 | + <Button Text="关闭" Clicked="OnCloseClicked" BackgroundColor="Transparent" /> | ||
| 14 | + </Grid> | ||
| 15 | + | ||
| 16 | + <!-- 表头 --> | ||
| 17 | + <Grid BackgroundColor="#F5F6F7" Padding="8" ColumnDefinitions="*,*,*,*,*,*,80"> | ||
| 18 | + <Label Text="库位编号" FontAttributes="Bold"/> | ||
| 19 | + <Label Grid.Column="1" Text="仓库名称" FontAttributes="Bold"/> | ||
| 20 | + <Label Grid.Column="2" Text="仓库编码" FontAttributes="Bold"/> | ||
| 21 | + <Label Grid.Column="3" Text="层数" FontAttributes="Bold"/> | ||
| 22 | + <Label Grid.Column="4" Text="货架" FontAttributes="Bold"/> | ||
| 23 | + <Label Grid.Column="5" Text="分区" FontAttributes="Bold"/> | ||
| 24 | + <Label Grid.Column="6" Text="操作" FontAttributes="Bold" HorizontalTextAlignment="Center"/> | ||
| 25 | + </Grid> | ||
| 26 | + | ||
| 27 | + <!-- 列表 --> | ||
| 28 | + <CollectionView Grid.Row="2" ItemsSource="{Binding Bins}" SelectionMode="None"> | ||
| 29 | + <CollectionView.EmptyView> | ||
| 30 | + <Label Text="该货架下暂无库位" TextColor="#999" Margin="12"/> | ||
| 31 | + </CollectionView.EmptyView> | ||
| 32 | + <CollectionView.ItemTemplate> | ||
| 33 | + <DataTemplate x:DataType="srv:BinInfo"> | ||
| 34 | + <Grid Padding="8" ColumnDefinitions="*,*,*,*,*,*,80" BackgroundColor="White"> | ||
| 35 | + <Label Text="{Binding BinCode}" /> | ||
| 36 | + <Label Grid.Column="1" Text="{Binding WarehouseName}" /> | ||
| 37 | + <Label Grid.Column="2" Text="{Binding WarehouseCode}" /> | ||
| 38 | + <Label Grid.Column="3" Text="{Binding Layer}" /> | ||
| 39 | + <Label Grid.Column="4" Text="{Binding Shelf}" /> | ||
| 40 | + <Label Grid.Column="5" Text="{Binding Zone}" /> | ||
| 41 | + <Button Grid.Column="6" Text="选择" Clicked="OnPickClicked" /> | ||
| 42 | + </Grid> | ||
| 43 | + </DataTemplate> | ||
| 44 | + </CollectionView.ItemTemplate> | ||
| 45 | + </CollectionView> | ||
| 46 | + </Grid> | ||
| 47 | +</ContentPage> |
Pages/BinListPage.xaml.cs
0 → 100644
| 1 | +using System.Collections.ObjectModel; | ||
| 2 | +using IndustrialControl.Services; | ||
| 3 | + | ||
| 4 | +namespace IndustrialControl.Pages; | ||
| 5 | + | ||
| 6 | +public partial class BinListPage : ContentPage | ||
| 7 | +{ | ||
| 8 | + public ObservableCollection<BinInfo> Bins { get; } = new(); | ||
| 9 | + private readonly TaskCompletionSource<BinInfo?> _tcs = new(); | ||
| 10 | + private readonly bool _closeParent; | ||
| 11 | + | ||
| 12 | + public BinListPage(IEnumerable<BinInfo> bins, bool closeParent) | ||
| 13 | + { | ||
| 14 | + InitializeComponent(); | ||
| 15 | + BindingContext = this; | ||
| 16 | + _closeParent = closeParent; | ||
| 17 | + foreach (var b in bins) Bins.Add(b); | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + public static async Task<BinInfo?> ShowAsync(IEnumerable<BinInfo> bins, bool closeParent = false) | ||
| 21 | + { | ||
| 22 | + var page = new BinListPage(bins, closeParent); | ||
| 23 | + var nav = Shell.Current?.Navigation ?? Application.Current?.MainPage?.Navigation | ||
| 24 | + ?? throw new InvalidOperationException("Navigation not ready"); | ||
| 25 | + | ||
| 26 | + await nav.PushModalAsync(page); | ||
| 27 | + return await page._tcs.Task; | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + private async void OnCloseClicked(object? sender, EventArgs e) | ||
| 32 | + { | ||
| 33 | + _tcs.TrySetResult(null); | ||
| 34 | + var nav = Shell.Current?.Navigation ?? Application.Current?.MainPage?.Navigation; | ||
| 35 | + if (nav != null) await nav.PopModalAsync(); // 仅关自己 | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + private async void OnPickClicked(object? sender, EventArgs e) | ||
| 39 | + { | ||
| 40 | + if (sender is BindableObject bo && bo.BindingContext is BinInfo bin) | ||
| 41 | + { | ||
| 42 | + // 先把选择结果回传,解除 ShowAsync 的 await | ||
| 43 | + _tcs.TrySetResult(bin); | ||
| 44 | + | ||
| 45 | + var nav = Shell.Current?.Navigation ?? Application.Current?.MainPage?.Navigation; | ||
| 46 | + if (nav == null) return; | ||
| 47 | + | ||
| 48 | + // 1) 关闭自己(BinListPage) | ||
| 49 | + await nav.PopModalAsync(); | ||
| 50 | + | ||
| 51 | + // 2) 如需要,再关闭父页(BinPickerPage) | ||
| 52 | + if (_closeParent && nav.ModalStack.Count > 0 && nav.ModalStack.Last() is BinPickerPage) | ||
| 53 | + await nav.PopModalAsync(); | ||
| 54 | + } | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + | ||
| 58 | +} |
Pages/BinPickerPage.xaml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8" ?> | ||
| 2 | +<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" | ||
| 3 | + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | ||
| 4 | + xmlns:vm="clr-namespace:IndustrialControl.ViewModels" | ||
| 5 | + x:Class="IndustrialControl.Pages.BinPickerPage" | ||
| 6 | + x:Name="Root" | ||
| 7 | + Title="选择库位" | ||
| 8 | + BackgroundColor="White"> | ||
| 9 | + | ||
| 10 | + <Grid Padding="12" RowDefinitions="Auto,*"> | ||
| 11 | + <Grid ColumnDefinitions="*,Auto" Padding="0,0,0,8"> | ||
| 12 | + <Label Text="选择库位" FontSize="18" FontAttributes="Bold" /> | ||
| 13 | + <Button Text="关闭" BackgroundColor="Transparent" Clicked="OnCloseClicked"/> | ||
| 14 | + </Grid> | ||
| 15 | + | ||
| 16 | + <ScrollView Grid.Row="1"> | ||
| 17 | + <!-- ★ 绑定可见树 --> | ||
| 18 | + <CollectionView ItemsSource="{Binding VisibleTree}" SelectionMode="None"> | ||
| 19 | + <CollectionView.ItemTemplate> | ||
| 20 | + <DataTemplate x:DataType="vm:BinTreeNode"> | ||
| 21 | + <Grid ColumnDefinitions="24,*" Padding="4" Margin="{Binding Indent}"> | ||
| 22 | + <Label Grid.Column="0" Text="{Binding ToggleIcon}" | ||
| 23 | + HorizontalTextAlignment="Center" VerticalTextAlignment="Center"> | ||
| 24 | + <Label.GestureRecognizers> | ||
| 25 | + <TapGestureRecognizer | ||
| 26 | + Command="{Binding BindingContext.ToggleCommand, Source={x:Reference Root}}" | ||
| 27 | + CommandParameter="{Binding .}" /> | ||
| 28 | + </Label.GestureRecognizers> | ||
| 29 | + </Label> | ||
| 30 | + <Label Grid.Column="1" Text="{Binding Name}" VerticalTextAlignment="Center"> | ||
| 31 | + <Label.GestureRecognizers> | ||
| 32 | + <TapGestureRecognizer | ||
| 33 | + Command="{Binding BindingContext.SelectNodeCommand, Source={x:Reference Root}}" | ||
| 34 | + CommandParameter="{Binding .}" /> | ||
| 35 | + </Label.GestureRecognizers> | ||
| 36 | + </Label> | ||
| 37 | + </Grid> | ||
| 38 | + </DataTemplate> | ||
| 39 | + </CollectionView.ItemTemplate> | ||
| 40 | + </CollectionView> | ||
| 41 | + </ScrollView> | ||
| 42 | + </Grid> | ||
| 43 | +</ContentPage> |
Pages/BinPickerPage.xaml.cs
0 → 100644
| 1 | +using IndustrialControl.Services; | ||
| 2 | +using IndustrialControl.ViewModels; | ||
| 3 | + | ||
| 4 | +namespace IndustrialControl.Pages; | ||
| 5 | + | ||
| 6 | +public partial class BinPickerPage : ContentPage | ||
| 7 | +{ | ||
| 8 | + private readonly TaskCompletionSource<BinInfo?> _tcs = new(); | ||
| 9 | + | ||
| 10 | + public BinPickerPage(string? preselectBinCode, BinPickerViewModel vm) | ||
| 11 | + { | ||
| 12 | + InitializeComponent(); | ||
| 13 | + BindingContext = vm; | ||
| 14 | + vm.Initialize(preselectBinCode); | ||
| 15 | + } | ||
| 16 | + | ||
| 17 | + public static async Task<BinInfo?> ShowAsync(string? preselectBinCode) | ||
| 18 | + { | ||
| 19 | + var sp = Application.Current!.Handler!.MauiContext!.Services!; | ||
| 20 | + var vm = sp.GetRequiredService<BinPickerViewModel>(); | ||
| 21 | + var page = new BinPickerPage(preselectBinCode, vm); | ||
| 22 | + | ||
| 23 | + var nav = Shell.Current?.Navigation ?? Application.Current?.MainPage?.Navigation | ||
| 24 | + ?? throw new InvalidOperationException("Navigation not ready"); | ||
| 25 | + | ||
| 26 | + vm.SetCloseHandlers( | ||
| 27 | + onPicked: async (bin) => | ||
| 28 | + { | ||
| 29 | + page._tcs.TrySetResult(bin); | ||
| 30 | + await MainThread.InvokeOnMainThreadAsync(async () => | ||
| 31 | + { | ||
| 32 | + var nav = Shell.Current?.Navigation ?? Application.Current?.MainPage?.Navigation; | ||
| 33 | + if (nav != null && nav.ModalStack.Count > 0 && nav.ModalStack.Last() is BinPickerPage) | ||
| 34 | + await nav.PopModalAsync(); | ||
| 35 | + }); | ||
| 36 | + }, | ||
| 37 | + onCanceled: async () => | ||
| 38 | + { | ||
| 39 | + page._tcs.TrySetResult(null); | ||
| 40 | + await MainThread.InvokeOnMainThreadAsync(async () => | ||
| 41 | + { | ||
| 42 | + var nav = Shell.Current?.Navigation ?? Application.Current?.MainPage?.Navigation; | ||
| 43 | + if (nav != null && nav.ModalStack.Count > 0 && nav.ModalStack.Last() is BinPickerPage) | ||
| 44 | + await nav.PopModalAsync(); | ||
| 45 | + }); | ||
| 46 | + }); | ||
| 47 | + | ||
| 48 | + | ||
| 49 | + await nav.PushModalAsync(page); | ||
| 50 | + return await page._tcs.Task; | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + | ||
| 54 | + | ||
| 55 | + private async void OnCloseClicked(object? sender, EventArgs e) | ||
| 56 | + { | ||
| 57 | + if (BindingContext is BinPickerViewModel vm) | ||
| 58 | + await vm.CancelAsync(); | ||
| 59 | + } | ||
| 60 | +} |
| @@ -168,11 +168,16 @@ | @@ -168,11 +168,16 @@ | ||
| 168 | <Label Grid.Column="2" Text="{Binding Name}" /> | 168 | <Label Grid.Column="2" Text="{Binding Name}" /> |
| 169 | <Label Grid.Column="3" Text="{Binding Spec}" /> | 169 | <Label Grid.Column="3" Text="{Binding Spec}" /> |
| 170 | 170 | ||
| 171 | - <!-- 预入库库位:改为下拉,可选 VM.AvailableBins,双向绑到 item.Bin --> | ||
| 172 | - <Picker Grid.Column="4" | ||
| 173 | - Title="选择库位" | ||
| 174 | - ItemsSource="{Binding Source={x:Reference Page}, Path=BindingContext.AvailableBins}" | ||
| 175 | - SelectedItem="{Binding Bin, Mode=TwoWay}" /> | 171 | + <!-- 4 入库库位(可点击) --> |
| 172 | + <Label Grid.Column="4" | ||
| 173 | + Text="{Binding Bin}" | ||
| 174 | + HorizontalTextAlignment="Center" | ||
| 175 | + VerticalTextAlignment="Center" | ||
| 176 | + TextColor="#007BFF" FontAttributes="Bold"> | ||
| 177 | + <Label.GestureRecognizers> | ||
| 178 | + <TapGestureRecognizer Tapped="OnBinTapped" /> | ||
| 179 | + </Label.GestureRecognizers> | ||
| 180 | + </Label> | ||
| 176 | 181 | ||
| 177 | <!-- 已扫描数量:可手动改,用数字键盘;建议加转换器容错 --> | 182 | <!-- 已扫描数量:可手动改,用数字键盘;建议加转换器容错 --> |
| 178 | <Entry Grid.Column="5" | 183 | <Entry Grid.Column="5" |
| @@ -11,13 +11,15 @@ public partial class InboundMaterialPage : ContentPage | @@ -11,13 +11,15 @@ public partial class InboundMaterialPage : ContentPage | ||
| 11 | private readonly InboundMaterialViewModel _vm; | 11 | private readonly InboundMaterialViewModel _vm; |
| 12 | // NEW: 由 Shell 设置 | 12 | // NEW: 由 Shell 设置 |
| 13 | public string? OrderNo { get; set; } | 13 | public string? OrderNo { get; set; } |
| 14 | + private readonly IDialogService _dialogs; | ||
| 14 | 15 | ||
| 15 | - public InboundMaterialPage(InboundMaterialViewModel vm, ScanService scanSvc) | 16 | + public InboundMaterialPage(InboundMaterialViewModel vm, ScanService scanSvc, IDialogService dialogs) |
| 16 | { | 17 | { |
| 17 | InitializeComponent(); | 18 | InitializeComponent(); |
| 18 | BindingContext = vm; | 19 | BindingContext = vm; |
| 19 | _scanSvc = scanSvc; | 20 | _scanSvc = scanSvc; |
| 20 | _vm = vm; | 21 | _vm = vm; |
| 22 | + _dialogs = dialogs; | ||
| 21 | // 可选:配置前后缀与防抖 | 23 | // 可选:配置前后缀与防抖 |
| 22 | _scanSvc.Prefix = null; // 例如 "}q" 之类的前缀;没有就留 null | 24 | _scanSvc.Prefix = null; // 例如 "}q" 之类的前缀;没有就留 null |
| 23 | // _scanSvc.Suffix = "\n"; // 如果设备会附带换行,可去掉;没有就设 null | 25 | // _scanSvc.Suffix = "\n"; // 如果设备会附带换行,可去掉;没有就设 null |
| @@ -94,5 +96,23 @@ public partial class InboundMaterialPage : ContentPage | @@ -94,5 +96,23 @@ public partial class InboundMaterialPage : ContentPage | ||
| 94 | } | 96 | } |
| 95 | } | 97 | } |
| 96 | 98 | ||
| 99 | + private async void OnBinTapped(object? sender, TappedEventArgs e) | ||
| 100 | + { | ||
| 101 | + var bindable = sender as BindableObject; | ||
| 102 | + var row = bindable?.BindingContext; | ||
| 103 | + if (row == null) return; | ||
| 104 | + | ||
| 105 | + var type = row.GetType(); | ||
| 106 | + var currentBin = type.GetProperty("Bin")?.GetValue(row)?.ToString(); | ||
| 107 | + | ||
| 108 | + var picked = await _dialogs.SelectBinAsync(currentBin); | ||
| 109 | + if (picked == null) return; | ||
| 110 | + | ||
| 111 | + var binProp = type.GetProperty("Bin"); | ||
| 112 | + if (binProp is { CanWrite: true }) | ||
| 113 | + binProp.SetValue(row, picked.BinCode); | ||
| 114 | + else | ||
| 115 | + _vm.SetItemBin(row, picked.BinCode); | ||
| 116 | + } | ||
| 97 | 117 | ||
| 98 | } | 118 | } |
| @@ -2,9 +2,11 @@ | @@ -2,9 +2,11 @@ | ||
| 2 | <ContentPage x:Name="Page" | 2 | <ContentPage x:Name="Page" |
| 3 | xmlns="http://schemas.microsoft.com/dotnet/2021/maui" | 3 | xmlns="http://schemas.microsoft.com/dotnet/2021/maui" |
| 4 | xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | 4 | xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" |
| 5 | + xmlns:zxing="clr-namespace:ZXing.Net.Maui.Controls;assembly=ZXing.Net.MAUI" | ||
| 5 | x:Class="IndustrialControl.Pages.InboundMaterialSearchPage" | 6 | x:Class="IndustrialControl.Pages.InboundMaterialSearchPage" |
| 6 | xmlns:conv="clr-namespace:IndustrialControl.Converters" | 7 | xmlns:conv="clr-namespace:IndustrialControl.Converters" |
| 7 | Title="仓储管理系统"> | 8 | Title="仓储管理系统"> |
| 9 | + | ||
| 8 | <ContentPage.Resources> | 10 | <ContentPage.Resources> |
| 9 | <ResourceDictionary> | 11 | <ResourceDictionary> |
| 10 | <!-- 空/非空转布尔:非空 => true(按钮可用) --> | 12 | <!-- 空/非空转布尔:非空 => true(按钮可用) --> |
| @@ -24,7 +26,6 @@ | @@ -24,7 +26,6 @@ | ||
| 24 | BackgroundColor="White" | 26 | BackgroundColor="White" |
| 25 | Text="{Binding SearchOrderNo}" /> | 27 | Text="{Binding SearchOrderNo}" /> |
| 26 | 28 | ||
| 27 | - | ||
| 28 | <DatePicker Grid.Row="1" Grid.Column="0" | 29 | <DatePicker Grid.Row="1" Grid.Column="0" |
| 29 | Date="{Binding CreatedDate}" | 30 | Date="{Binding CreatedDate}" |
| 30 | MinimumDate="2000-01-01" /> | 31 | MinimumDate="2000-01-01" /> |
| 1 | -using System.Threading; | ||
| 2 | -using IndustrialControl.Services; | 1 | +using IndustrialControl.Services; |
| 3 | using IndustrialControl.ViewModels; | 2 | using IndustrialControl.ViewModels; |
| 4 | namespace IndustrialControl.Pages; | 3 | namespace IndustrialControl.Pages; |
| 5 | public partial class InboundMaterialSearchPage : ContentPage | 4 | public partial class InboundMaterialSearchPage : ContentPage |
| @@ -14,26 +13,28 @@ public partial class InboundMaterialSearchPage : ContentPage | @@ -14,26 +13,28 @@ public partial class InboundMaterialSearchPage : ContentPage | ||
| 14 | BindingContext = vm; | 13 | BindingContext = vm; |
| 15 | _scanSvc = scanSvc; | 14 | _scanSvc = scanSvc; |
| 16 | InitializeComponent(); | 15 | InitializeComponent(); |
| 17 | - // 可选:配置前后缀与防抖 | ||
| 18 | - _scanSvc.Prefix = null; // 例如 "}q" 之类的前缀;没有就留 null | ||
| 19 | - // _scanSvc.Suffix = "\n"; // 如果设备会附带换行,可去掉;没有就设 null | 16 | + // 可选:配置前后缀与防抖 |
| 17 | + _scanSvc.Prefix = null; // 例如 "}q" 之类的前缀;没有就留 null | ||
| 18 | + // _scanSvc.Suffix = "\n"; // 如果设备会附带换行,可去掉;没有就设 null | ||
| 20 | //_scanSvc.DebounceMs = 250; | 19 | //_scanSvc.DebounceMs = 250; |
| 21 | - _scanSvc.Suffix = null; // 先关掉 | ||
| 22 | - _scanSvc.DebounceMs = 0; // 先关掉 | 20 | + _scanSvc.Suffix = null; // 先关掉 |
| 21 | + _scanSvc.DebounceMs = 0; // 先关掉 | ||
| 23 | } | 22 | } |
| 24 | protected override async void OnAppearing() | 23 | protected override async void OnAppearing() |
| 25 | { | 24 | { |
| 26 | base.OnAppearing(); | 25 | base.OnAppearing(); |
| 27 | - // 动态注册广播接收器(只在当前页面前台时生效) | 26 | + // 动态注册广播接收器(只在当前页面前台时生效) |
| 28 | _scanSvc.Scanned += OnScanned; | 27 | _scanSvc.Scanned += OnScanned; |
| 29 | _scanSvc.StartListening(); | 28 | _scanSvc.StartListening(); |
| 30 | - //键盘输入 | 29 | + //键盘输入 |
| 31 | _scanSvc.Attach(OrderEntry); | 30 | _scanSvc.Attach(OrderEntry); |
| 32 | OrderEntry.Focus(); | 31 | OrderEntry.Focus(); |
| 32 | + | ||
| 33 | + | ||
| 33 | } | 34 | } |
| 34 | 35 | ||
| 35 | /// <summary> | 36 | /// <summary> |
| 36 | - /// 清空扫描记录 | 37 | + /// 清空扫描记录 |
| 37 | /// </summary> | 38 | /// </summary> |
| 38 | void OnClearClicked(object sender, EventArgs e) | 39 | void OnClearClicked(object sender, EventArgs e) |
| 39 | { | 40 | { |
| @@ -43,7 +44,7 @@ public partial class InboundMaterialSearchPage : ContentPage | @@ -43,7 +44,7 @@ public partial class InboundMaterialSearchPage : ContentPage | ||
| 43 | 44 | ||
| 44 | protected override void OnDisappearing() | 45 | protected override void OnDisappearing() |
| 45 | { | 46 | { |
| 46 | - // 退出页面即注销(防止多个程序/页面抢处理) | 47 | + // 退出页面即注销(防止多个程序/页面抢处理) |
| 47 | _scanSvc.Scanned -= OnScanned; | 48 | _scanSvc.Scanned -= OnScanned; |
| 48 | _scanSvc.StopListening(); | 49 | _scanSvc.StopListening(); |
| 49 | 50 | ||
| @@ -54,7 +55,7 @@ public partial class InboundMaterialSearchPage : ContentPage | @@ -54,7 +55,7 @@ public partial class InboundMaterialSearchPage : ContentPage | ||
| 54 | { | 55 | { |
| 55 | MainThread.BeginInvokeOnMainThread(async () => | 56 | MainThread.BeginInvokeOnMainThread(async () => |
| 56 | { | 57 | { |
| 57 | - // 常见处理:自动填入单号/条码并触发查询或加入明细 | 58 | + // 常见处理:自动填入单号/条码并触发查询或加入明细 |
| 58 | _vm.SearchOrderNo = data; | 59 | _vm.SearchOrderNo = data; |
| 59 | }); | 60 | }); |
| 60 | } | 61 | } |
| @@ -63,13 +64,15 @@ public partial class InboundMaterialSearchPage : ContentPage | @@ -63,13 +64,15 @@ public partial class InboundMaterialSearchPage : ContentPage | ||
| 63 | var item = e.CurrentSelection?.FirstOrDefault() as InboundOrderSummary; | 64 | var item = e.CurrentSelection?.FirstOrDefault() as InboundOrderSummary; |
| 64 | if (item is null) return; | 65 | if (item is null) return; |
| 65 | 66 | ||
| 66 | - // 二选一:A) 点选跳转到入库页 | 67 | + // 二选一:A) 点选跳转到入库页 |
| 67 | await Shell.Current.GoToAsync( | 68 | await Shell.Current.GoToAsync( |
| 68 | $"{nameof(InboundMaterialPage)}?orderNo={Uri.EscapeDataString(item.OrderNo)}"); | 69 | $"{nameof(InboundMaterialPage)}?orderNo={Uri.EscapeDataString(item.OrderNo)}"); |
| 69 | 70 | ||
| 70 | - // 或 B) 只把单号写到输入框/VM(不跳转) | 71 | + // 或 B) 只把单号写到输入框/VM(不跳转) |
| 71 | // if (BindingContext is InboundMaterialSearchViewModel vm) vm.SearchOrderNo = item.OrderNo; | 72 | // if (BindingContext is InboundMaterialSearchViewModel vm) vm.SearchOrderNo = item.OrderNo; |
| 72 | 73 | ||
| 73 | - ((CollectionView)sender).SelectedItem = null; // 清除选中高亮 | 74 | + ((CollectionView)sender).SelectedItem = null; // 清除选中高亮 |
| 74 | } | 75 | } |
| 76 | + | ||
| 77 | + | ||
| 75 | } | 78 | } |
| @@ -3,10 +3,12 @@ using IndustrialControl.ViewModels; | @@ -3,10 +3,12 @@ using IndustrialControl.ViewModels; | ||
| 3 | 3 | ||
| 4 | namespace IndustrialControl.Pages | 4 | namespace IndustrialControl.Pages |
| 5 | { | 5 | { |
| 6 | + [QueryProperty(nameof(OrderNo), "orderNo")] | ||
| 6 | public partial class InboundProductionPage : ContentPage | 7 | public partial class InboundProductionPage : ContentPage |
| 7 | { | 8 | { |
| 8 | private readonly ScanService _scanSvc; | 9 | private readonly ScanService _scanSvc; |
| 9 | private readonly InboundProductionViewModel _vm; | 10 | private readonly InboundProductionViewModel _vm; |
| 11 | + public string? OrderNo { get; set; } | ||
| 10 | 12 | ||
| 11 | public InboundProductionPage(InboundProductionViewModel vm, ScanService scanSvc) | 13 | public InboundProductionPage(InboundProductionViewModel vm, ScanService scanSvc) |
| 12 | { | 14 | { |
| @@ -14,14 +16,49 @@ namespace IndustrialControl.Pages | @@ -14,14 +16,49 @@ namespace IndustrialControl.Pages | ||
| 14 | BindingContext = vm; | 16 | BindingContext = vm; |
| 15 | _scanSvc = scanSvc; | 17 | _scanSvc = scanSvc; |
| 16 | _vm = vm; | 18 | _vm = vm; |
| 19 | + _scanSvc.Prefix = null; // 例如 "}q" 之类的前缀;没有就留 null | ||
| 20 | + // _scanSvc.Suffix = "\n"; // 如果设备会附带换行,可去掉;没有就设 null | ||
| 21 | + //_scanSvc.DebounceMs = 250; | ||
| 22 | + _scanSvc.Suffix = null; // 先关掉 | ||
| 23 | + _scanSvc.DebounceMs = 0; // 先关掉 | ||
| 17 | } | 24 | } |
| 18 | 25 | ||
| 19 | - protected override void OnAppearing() | 26 | + protected override async void OnAppearing() |
| 20 | { | 27 | { |
| 21 | base.OnAppearing(); | 28 | base.OnAppearing(); |
| 29 | + | ||
| 30 | + // 如果带有单号参数,则加载其明细,进入图2/图3的流程 | ||
| 31 | + if (!string.IsNullOrWhiteSpace(OrderNo)) | ||
| 32 | + { | ||
| 33 | + await _vm.LoadOrderAsync(OrderNo); | ||
| 34 | + } | ||
| 35 | + // 动态注册广播接收器(只在当前页面前台时生效) | ||
| 36 | + _scanSvc.Scanned += OnScanned; | ||
| 37 | + _scanSvc.StartListening(); | ||
| 38 | + //键盘输入 | ||
| 22 | _scanSvc.Attach(ScanEntry); | 39 | _scanSvc.Attach(ScanEntry); |
| 23 | ScanEntry.Focus(); | 40 | ScanEntry.Focus(); |
| 24 | } | 41 | } |
| 42 | + protected override void OnDisappearing() | ||
| 43 | + { | ||
| 44 | + // 退出页面即注销(防止多个程序/页面抢处理) | ||
| 45 | + _scanSvc.Scanned -= OnScanned; | ||
| 46 | + _scanSvc.StopListening(); | ||
| 47 | + | ||
| 48 | + base.OnDisappearing(); | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + private void OnScanned(string data, string type) | ||
| 52 | + { | ||
| 53 | + MainThread.BeginInvokeOnMainThread(async () => | ||
| 54 | + { | ||
| 55 | + // 常见处理:自动填入单号/条码并触发查询或加入明细 | ||
| 56 | + _vm.ScanCode = data; | ||
| 57 | + | ||
| 58 | + // 你原本的逻辑:若识别到是订单号 → 查询;若是包装码 → 加入列表等 | ||
| 59 | + await _vm.HandleScannedAsync(data, type); | ||
| 60 | + }); | ||
| 61 | + } | ||
| 25 | 62 | ||
| 26 | /// <summary> | 63 | /// <summary> |
| 27 | /// 清空扫描记录 | 64 | /// 清空扫描记录 |
| @@ -73,5 +110,7 @@ namespace IndustrialControl.Pages | @@ -73,5 +110,7 @@ namespace IndustrialControl.Pages | ||
| 73 | await DisplayAlert("提示", "入库失败,请检查数据", "确定"); | 110 | await DisplayAlert("提示", "入库失败,请检查数据", "确定"); |
| 74 | } | 111 | } |
| 75 | } | 112 | } |
| 113 | + | ||
| 114 | + | ||
| 76 | } | 115 | } |
| 77 | } | 116 | } |
| @@ -3,10 +3,12 @@ using IndustrialControl.ViewModels; | @@ -3,10 +3,12 @@ using IndustrialControl.ViewModels; | ||
| 3 | 3 | ||
| 4 | namespace IndustrialControl.Pages | 4 | namespace IndustrialControl.Pages |
| 5 | { | 5 | { |
| 6 | + [QueryProperty(nameof(OrderNo), "orderNo")] | ||
| 6 | public partial class OutboundFinishedPage : ContentPage | 7 | public partial class OutboundFinishedPage : ContentPage |
| 7 | { | 8 | { |
| 8 | private readonly ScanService _scanSvc; | 9 | private readonly ScanService _scanSvc; |
| 9 | private readonly OutboundFinishedViewModel _vm; | 10 | private readonly OutboundFinishedViewModel _vm; |
| 11 | + public string? OrderNo { get; set; } | ||
| 10 | 12 | ||
| 11 | public OutboundFinishedPage(OutboundFinishedViewModel vm, ScanService scanSvc) | 13 | public OutboundFinishedPage(OutboundFinishedViewModel vm, ScanService scanSvc) |
| 12 | { | 14 | { |
| @@ -14,15 +16,51 @@ namespace IndustrialControl.Pages | @@ -14,15 +16,51 @@ namespace IndustrialControl.Pages | ||
| 14 | BindingContext = vm; | 16 | BindingContext = vm; |
| 15 | _scanSvc = scanSvc; | 17 | _scanSvc = scanSvc; |
| 16 | _vm = vm; | 18 | _vm = vm; |
| 19 | + _scanSvc.Prefix = null; // 例如 "}q" 之类的前缀;没有就留 null | ||
| 20 | + // _scanSvc.Suffix = "\n"; // 如果设备会附带换行,可去掉;没有就设 null | ||
| 21 | + //_scanSvc.DebounceMs = 250; | ||
| 22 | + _scanSvc.Suffix = null; // 先关掉 | ||
| 23 | + _scanSvc.DebounceMs = 0; // 先关掉 | ||
| 17 | } | 24 | } |
| 18 | 25 | ||
| 19 | - protected override void OnAppearing() | 26 | + protected override async void OnAppearing() |
| 20 | { | 27 | { |
| 21 | base.OnAppearing(); | 28 | base.OnAppearing(); |
| 29 | + | ||
| 30 | + // 如果带有单号参数,则加载其明细,进入图2/图3的流程 | ||
| 31 | + if (!string.IsNullOrWhiteSpace(OrderNo)) | ||
| 32 | + { | ||
| 33 | + await _vm.LoadOrderAsync(OrderNo); | ||
| 34 | + } | ||
| 35 | + // 动态注册广播接收器(只在当前页面前台时生效) | ||
| 36 | + _scanSvc.Scanned += OnScanned; | ||
| 37 | + _scanSvc.StartListening(); | ||
| 38 | + //键盘输入 | ||
| 22 | _scanSvc.Attach(ScanEntry); | 39 | _scanSvc.Attach(ScanEntry); |
| 23 | ScanEntry.Focus(); | 40 | ScanEntry.Focus(); |
| 24 | } | 41 | } |
| 25 | 42 | ||
| 43 | + protected override void OnDisappearing() | ||
| 44 | + { | ||
| 45 | + // 退出页面即注销(防止多个程序/页面抢处理) | ||
| 46 | + _scanSvc.Scanned -= OnScanned; | ||
| 47 | + _scanSvc.StopListening(); | ||
| 48 | + | ||
| 49 | + base.OnDisappearing(); | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + private void OnScanned(string data, string type) | ||
| 53 | + { | ||
| 54 | + MainThread.BeginInvokeOnMainThread(async () => | ||
| 55 | + { | ||
| 56 | + // 常见处理:自动填入单号/条码并触发查询或加入明细 | ||
| 57 | + _vm.ScanCode = data; | ||
| 58 | + | ||
| 59 | + // 你原本的逻辑:若识别到是订单号 → 查询;若是包装码 → 加入列表等 | ||
| 60 | + await _vm.HandleScannedAsync(data, type); | ||
| 61 | + }); | ||
| 62 | + } | ||
| 63 | + | ||
| 26 | /// <summary> | 64 | /// <summary> |
| 27 | /// 清空扫描记录 | 65 | /// 清空扫描记录 |
| 28 | /// </summary> | 66 | /// </summary> |
| @@ -3,10 +3,12 @@ using IndustrialControl.ViewModels; | @@ -3,10 +3,12 @@ using IndustrialControl.ViewModels; | ||
| 3 | 3 | ||
| 4 | namespace IndustrialControl.Pages | 4 | namespace IndustrialControl.Pages |
| 5 | { | 5 | { |
| 6 | + [QueryProperty(nameof(OrderNo), "orderNo")] | ||
| 6 | public partial class OutboundMaterialPage : ContentPage | 7 | public partial class OutboundMaterialPage : ContentPage |
| 7 | { | 8 | { |
| 8 | private readonly ScanService _scanSvc; | 9 | private readonly ScanService _scanSvc; |
| 9 | private readonly OutboundMaterialViewModel _vm; | 10 | private readonly OutboundMaterialViewModel _vm; |
| 11 | + public string? OrderNo { get; set; } | ||
| 10 | 12 | ||
| 11 | public OutboundMaterialPage(OutboundMaterialViewModel vm, ScanService scanSvc) | 13 | public OutboundMaterialPage(OutboundMaterialViewModel vm, ScanService scanSvc) |
| 12 | { | 14 | { |
| @@ -14,15 +16,49 @@ namespace IndustrialControl.Pages | @@ -14,15 +16,49 @@ namespace IndustrialControl.Pages | ||
| 14 | BindingContext = vm; | 16 | BindingContext = vm; |
| 15 | _scanSvc = scanSvc; | 17 | _scanSvc = scanSvc; |
| 16 | _vm = vm; | 18 | _vm = vm; |
| 19 | + _scanSvc.Prefix = null; // 例如 "}q" 之类的前缀;没有就留 null | ||
| 20 | + // _scanSvc.Suffix = "\n"; // 如果设备会附带换行,可去掉;没有就设 null | ||
| 21 | + //_scanSvc.DebounceMs = 250; | ||
| 22 | + _scanSvc.Suffix = null; // 先关掉 | ||
| 23 | + _scanSvc.DebounceMs = 0; // 先关掉 | ||
| 17 | } | 24 | } |
| 18 | 25 | ||
| 19 | - protected override void OnAppearing() | 26 | + protected override async void OnAppearing() |
| 20 | { | 27 | { |
| 21 | base.OnAppearing(); | 28 | base.OnAppearing(); |
| 29 | + | ||
| 30 | + // 如果带有单号参数,则加载其明细,进入图2/图3的流程 | ||
| 31 | + if (!string.IsNullOrWhiteSpace(OrderNo)) | ||
| 32 | + { | ||
| 33 | + await _vm.LoadOrderAsync(OrderNo); | ||
| 34 | + } | ||
| 35 | + // 动态注册广播接收器(只在当前页面前台时生效) | ||
| 36 | + _scanSvc.Scanned += OnScanned; | ||
| 37 | + _scanSvc.StartListening(); | ||
| 38 | + //键盘输入 | ||
| 22 | _scanSvc.Attach(ScanEntry); | 39 | _scanSvc.Attach(ScanEntry); |
| 23 | ScanEntry.Focus(); | 40 | ScanEntry.Focus(); |
| 24 | } | 41 | } |
| 42 | + protected override void OnDisappearing() | ||
| 43 | + { | ||
| 44 | + // 退出页面即注销(防止多个程序/页面抢处理) | ||
| 45 | + _scanSvc.Scanned -= OnScanned; | ||
| 46 | + _scanSvc.StopListening(); | ||
| 47 | + | ||
| 48 | + base.OnDisappearing(); | ||
| 49 | + } | ||
| 25 | 50 | ||
| 51 | + private void OnScanned(string data, string type) | ||
| 52 | + { | ||
| 53 | + MainThread.BeginInvokeOnMainThread(async () => | ||
| 54 | + { | ||
| 55 | + // 常见处理:自动填入单号/条码并触发查询或加入明细 | ||
| 56 | + _vm.ScanCode = data; | ||
| 57 | + | ||
| 58 | + // 你原本的逻辑:若识别到是订单号 → 查询;若是包装码 → 加入列表等 | ||
| 59 | + await _vm.HandleScannedAsync(data, type); | ||
| 60 | + }); | ||
| 61 | + } | ||
| 26 | // Tab切换 | 62 | // Tab切换 |
| 27 | void OnPendingTabClicked(object sender, EventArgs e) | 63 | void OnPendingTabClicked(object sender, EventArgs e) |
| 28 | { | 64 | { |
Services/DialogService.cs
0 → 100644
| 1 | +using Microsoft.Maui.Dispatching; | ||
| 2 | +using IndustrialControl.Pages; | ||
| 3 | + | ||
| 4 | +namespace IndustrialControl.Services; | ||
| 5 | + | ||
| 6 | +public class DialogService : IDialogService | ||
| 7 | +{ | ||
| 8 | + private static Page? CurrentPage => Application.Current?.MainPage; | ||
| 9 | + | ||
| 10 | + public Task AlertAsync(string title, string message, string accept = "确定") => | ||
| 11 | + MainThread.InvokeOnMainThreadAsync(() => | ||
| 12 | + CurrentPage?.DisplayAlert(title, message, accept) ?? Task.CompletedTask); | ||
| 13 | + | ||
| 14 | + public Task<bool> ConfirmAsync(string title, string message, string accept = "确定", string cancel = "取消") => | ||
| 15 | + MainThread.InvokeOnMainThreadAsync(() => | ||
| 16 | + CurrentPage?.DisplayAlert(title, message, accept, cancel) ?? Task.FromResult(false)); | ||
| 17 | + | ||
| 18 | + public Task<string?> PromptAsync(string title, string message, string? initial = null, string? placeholder = null, | ||
| 19 | + int? maxLength = null, Keyboard? keyboard = null) => | ||
| 20 | + MainThread.InvokeOnMainThreadAsync(() => | ||
| 21 | + CurrentPage?.DisplayPromptAsync(title, message, "确定", "取消", placeholder, | ||
| 22 | + maxLength ?? -1, keyboard ?? Keyboard.Text, initial ?? string.Empty) ?? Task.FromResult<string?>(null)); | ||
| 23 | + | ||
| 24 | + // ★ 新增:库位选择弹窗 | ||
| 25 | + public Task<BinInfo?> SelectBinAsync(string? preselectBinCode = null) | ||
| 26 | + => BinPickerPage.ShowAsync(preselectBinCode); | ||
| 27 | +} |
Services/IDialogService.cs
0 → 100644
| 1 | +namespace IndustrialControl.Services; | ||
| 2 | + | ||
| 3 | +public interface IDialogService | ||
| 4 | +{ | ||
| 5 | + Task AlertAsync(string title, string message, string accept = "确定"); | ||
| 6 | + Task<bool> ConfirmAsync(string title, string message, string accept = "确定", string cancel = "取消"); | ||
| 7 | + Task<string?> PromptAsync(string title, string message, string? initial = null, string? placeholder = null, | ||
| 8 | + int? maxLength = null, Keyboard? keyboard = null); | ||
| 9 | + | ||
| 10 | + /// 选择库位(返回选中的库位信息;取消返回 null) | ||
| 11 | + Task<BinInfo?> SelectBinAsync(string? preselectBinCode = null); | ||
| 12 | +} | ||
| 13 | + | ||
| 14 | +/// 表格右侧每行的数据模型(和你的截图列对应) | ||
| 15 | +public class BinInfo | ||
| 16 | +{ | ||
| 17 | + public string BinCode { get; set; } = ""; // 库位编号 A1-101 | ||
| 18 | + public string WarehouseName { get; set; } = ""; // 仓库名称 半成品库 | ||
| 19 | + public string WarehouseCode { get; set; } = ""; // 仓库编码 FBCPK | ||
| 20 | + public string Layer { get; set; } = ""; // 层数 货架层A1-1 | ||
| 21 | + public string Shelf { get; set; } = ""; // 货架 货架A1 | ||
| 22 | + public string Zone { get; set; } = ""; // 分区 区域A | ||
| 23 | + public string Status { get; set; } = "-"; // 存货状态 | ||
| 24 | + public string ShelfPath { get; set; } = ""; | ||
| 25 | +} |
ViewModels/BinPickerViewModel.cs
0 → 100644
| 1 | +using CommunityToolkit.Mvvm.ComponentModel; | ||
| 2 | +using CommunityToolkit.Mvvm.Input; | ||
| 3 | +using IndustrialControl.Services; | ||
| 4 | +using IndustrialControl.Pages; | ||
| 5 | +using System.Collections.ObjectModel; | ||
| 6 | + | ||
| 7 | +namespace IndustrialControl.ViewModels; | ||
| 8 | + | ||
| 9 | +public partial class BinPickerViewModel : ObservableObject | ||
| 10 | +{ | ||
| 11 | + public ObservableCollection<BinTreeNode> Tree { get; } = new(); | ||
| 12 | + public ObservableCollection<BinInfo> AllBins { get; } = new(); | ||
| 13 | + public ObservableCollection<BinTreeNode> VisibleTree { get; } = new(); | ||
| 14 | + | ||
| 15 | + private Func<BinInfo, Task>? _onPicked; | ||
| 16 | + private Func<Task>? _onCanceled; | ||
| 17 | + | ||
| 18 | + public void SetCloseHandlers(Func<BinInfo, Task> onPicked, Func<Task> onCanceled) | ||
| 19 | + { | ||
| 20 | + _onPicked = onPicked; | ||
| 21 | + _onCanceled = onCanceled; | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + public void Initialize(string? preselectBinCode) | ||
| 25 | + { | ||
| 26 | + BuildMockTree(); | ||
| 27 | + BuildMockBins(); | ||
| 28 | + // 根/区域默认展开,这样有下级 | ||
| 29 | + foreach (var root in Tree) root.IsExpanded = true; | ||
| 30 | + RebuildVisible(); // ★ 初始化可见列表 | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + // 展开/收起 | ||
| 34 | + [RelayCommand] | ||
| 35 | + private void Toggle(BinTreeNode node) | ||
| 36 | + { | ||
| 37 | + node.IsExpanded = !node.IsExpanded; | ||
| 38 | + RebuildVisible(); // ★ 每次切换都重建 | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + // 选中树节点:Level==2 表示“货架” → 弹出库位列表;否则仅展开 | ||
| 42 | + [RelayCommand] | ||
| 43 | + private async Task SelectNode(BinTreeNode node) | ||
| 44 | + { | ||
| 45 | + if (node.Level != 2) | ||
| 46 | + { | ||
| 47 | + node.IsExpanded = !node.IsExpanded; | ||
| 48 | + RebuildVisible(); | ||
| 49 | + return; | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + var list = AllBins.Where(b => b.ShelfPath.Equals(node.Path, StringComparison.OrdinalIgnoreCase)).ToList(); | ||
| 53 | + | ||
| 54 | + // ★ 让 BinListPage 在选择后连关自己和父页 | ||
| 55 | + var picked = await BinListPage.ShowAsync(list, closeParent: true); | ||
| 56 | + if (picked == null) return; | ||
| 57 | + | ||
| 58 | + // 仍把结果回传,完成 BinPickerPage.ShowAsync 的 Task | ||
| 59 | + if (_onPicked != null) | ||
| 60 | + await _onPicked.Invoke(picked); | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + | ||
| 64 | + | ||
| 65 | + // ★ 递归把“当前应可见”的节点平铺出来 | ||
| 66 | + private void RebuildVisible() | ||
| 67 | + { | ||
| 68 | + VisibleTree.Clear(); | ||
| 69 | + foreach (var root in Tree) | ||
| 70 | + AddVisible(root); | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + private void AddVisible(BinTreeNode n) | ||
| 74 | + { | ||
| 75 | + VisibleTree.Add(n); | ||
| 76 | + if (!n.IsExpanded) return; | ||
| 77 | + foreach (var c in n.Children) | ||
| 78 | + AddVisible(c); | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + public Task CancelAsync() => _onCanceled?.Invoke() ?? Task.CompletedTask; | ||
| 82 | + | ||
| 83 | + // ====== Mock(保持不变,只展示必要部分)====== | ||
| 84 | + private void BuildMockTree() | ||
| 85 | + { | ||
| 86 | + Tree.Clear(); | ||
| 87 | + var semi = new BinTreeNode("半成品库", 0, "半成品库") { IsExpanded = true }; | ||
| 88 | + var semiAreaA = new BinTreeNode("区域A", 1, "半成品库/区域A") { IsExpanded = true }; | ||
| 89 | + semiAreaA.Children.Add(new BinTreeNode("货架A1", 2, "半成品库/区域A/货架A1")); | ||
| 90 | + semi.Children.Add(semiAreaA); | ||
| 91 | + Tree.Add(semi); | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + private void BuildMockBins() | ||
| 95 | + { | ||
| 96 | + AllBins.Clear(); | ||
| 97 | + AllBins.Add(new BinInfo | ||
| 98 | + { | ||
| 99 | + BinCode = "A1-101", | ||
| 100 | + WarehouseName = "半成品库", | ||
| 101 | + WarehouseCode = "FBCPK", | ||
| 102 | + Layer = "货架层A1-1", | ||
| 103 | + Shelf = "货架A1", | ||
| 104 | + Zone = "区域A", | ||
| 105 | + Status = "-", | ||
| 106 | + ShelfPath = "半成品库/区域A/货架A1" | ||
| 107 | + }); | ||
| 108 | + AllBins.Add(new BinInfo | ||
| 109 | + { | ||
| 110 | + BinCode = "A1-102", | ||
| 111 | + WarehouseName = "半成品库", | ||
| 112 | + WarehouseCode = "FBCPK", | ||
| 113 | + Layer = "货架层A1-1", | ||
| 114 | + Shelf = "货架A1", | ||
| 115 | + Zone = "区域A", | ||
| 116 | + Status = "-", | ||
| 117 | + ShelfPath = "半成品库/区域A/货架A1" | ||
| 118 | + }); | ||
| 119 | + } | ||
| 120 | +} | ||
| 121 | + | ||
| 122 | +// 树节点 | ||
| 123 | +public partial class BinTreeNode : ObservableObject | ||
| 124 | +{ | ||
| 125 | + public BinTreeNode(string name, int level, string path) | ||
| 126 | + { Name = name; Level = level; Path = path; Indent = new Thickness(level * 16, 0, 0, 0); } | ||
| 127 | + | ||
| 128 | + [ObservableProperty] private string name; | ||
| 129 | + [ObservableProperty] private bool isExpanded; | ||
| 130 | + public int Level { get; } | ||
| 131 | + public string Path { get; } | ||
| 132 | + public Thickness Indent { get; } | ||
| 133 | + public ObservableCollection<BinTreeNode> Children { get; } = new(); | ||
| 134 | + public string ToggleIcon => Children.Count == 0 ? "" : (IsExpanded ? "▾" : "▸"); | ||
| 135 | +} |
| @@ -242,6 +242,17 @@ namespace IndustrialControl.ViewModels | @@ -242,6 +242,17 @@ namespace IndustrialControl.ViewModels | ||
| 242 | SwitchTab(true); | 242 | SwitchTab(true); |
| 243 | } | 243 | } |
| 244 | 244 | ||
| 245 | + public void SetItemBin(object row, string bin) | ||
| 246 | + { | ||
| 247 | + if (row is OutScannedItem item) { item.Bin = bin; return; } | ||
| 248 | + var barcode = row?.GetType().GetProperty("Barcode")?.GetValue(row)?.ToString(); | ||
| 249 | + if (!string.IsNullOrWhiteSpace(barcode)) | ||
| 250 | + { | ||
| 251 | + var target = ScannedList.FirstOrDefault(x => x.Barcode == barcode); | ||
| 252 | + if (target != null) target.Bin = bin; | ||
| 253 | + } | ||
| 254 | + } | ||
| 255 | + | ||
| 245 | 256 | ||
| 246 | 257 | ||
| 247 | } | 258 | } |
| 1 | using CommunityToolkit.Mvvm.ComponentModel; | 1 | using CommunityToolkit.Mvvm.ComponentModel; |
| 2 | using CommunityToolkit.Mvvm.Input; | 2 | using CommunityToolkit.Mvvm.Input; |
| 3 | -using System.Collections.ObjectModel; | ||
| 4 | using IndustrialControl.Services; | 3 | using IndustrialControl.Services; |
| 5 | -using System.Threading.Tasks; | 4 | +using System.Collections.ObjectModel; |
| 6 | 5 | ||
| 7 | namespace IndustrialControl.ViewModels | 6 | namespace IndustrialControl.ViewModels |
| 8 | { | 7 | { |
| 9 | public partial class InboundProductionViewModel : ObservableObject | 8 | public partial class InboundProductionViewModel : ObservableObject |
| 10 | { | 9 | { |
| 10 | + [ObservableProperty] private string? scanCode; | ||
| 11 | private readonly IWarehouseDataService _warehouseSvc; | 11 | private readonly IWarehouseDataService _warehouseSvc; |
| 12 | 12 | ||
| 13 | public InboundProductionViewModel(IWarehouseDataService warehouseSvc) | 13 | public InboundProductionViewModel(IWarehouseDataService warehouseSvc) |
| @@ -84,6 +84,37 @@ namespace IndustrialControl.ViewModels | @@ -84,6 +84,37 @@ namespace IndustrialControl.ViewModels | ||
| 84 | 84 | ||
| 85 | return result.Succeeded; | 85 | return result.Succeeded; |
| 86 | } | 86 | } |
| 87 | + | ||
| 88 | + public async Task HandleScannedAsync(string data, string symbology) | ||
| 89 | + { | ||
| 90 | + // TODO: 你的规则(判断是订单号/物料码/包装码等) | ||
| 91 | + // 然后:查询接口 / 加入列表 / 自动提交 | ||
| 92 | + await Task.CompletedTask; | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + public async Task LoadOrderAsync(string orderNo) | ||
| 96 | + { | ||
| 97 | + // 0) 清空旧数据 | ||
| 98 | + ClearAll(); | ||
| 99 | + | ||
| 100 | + // 1) 基础信息 | ||
| 101 | + var order = await _warehouseSvc.GetInboundOrderAsync(orderNo); | ||
| 102 | + OrderNo = order.OrderNo; | ||
| 103 | + try | ||
| 104 | + { | ||
| 105 | + var bins = await _warehouseSvc.ListInboundBinsAsync(orderNo); | ||
| 106 | + AvailableBins.Clear(); | ||
| 107 | + foreach (var b in bins) AvailableBins.Add(b); | ||
| 108 | + } | ||
| 109 | + catch | ||
| 110 | + { | ||
| 111 | + // 兜底:本地 mock,避免空集合导致 Picker 无选项 | ||
| 112 | + AvailableBins.Clear(); | ||
| 113 | + foreach (var b in new[] { "CK1_A201", "CK1_A202", "CK1_A203", "CK1_A204" }) | ||
| 114 | + AvailableBins.Add(b); | ||
| 115 | + } | ||
| 116 | + var defaultBin = AvailableBins.FirstOrDefault() ?? "CK1_A201"; | ||
| 117 | + } | ||
| 87 | } | 118 | } |
| 88 | 119 | ||
| 89 | public class ProductionScanItem | 120 | public class ProductionScanItem |
| @@ -13,6 +13,7 @@ namespace IndustrialControl.ViewModels | @@ -13,6 +13,7 @@ namespace IndustrialControl.ViewModels | ||
| 13 | [ObservableProperty] private string deliveryNo; | 13 | [ObservableProperty] private string deliveryNo; |
| 14 | [ObservableProperty] private string deliveryTime; | 14 | [ObservableProperty] private string deliveryTime; |
| 15 | [ObservableProperty] private string remark; | 15 | [ObservableProperty] private string remark; |
| 16 | + [ObservableProperty] private string? scanCode; | ||
| 16 | #endregion | 17 | #endregion |
| 17 | 18 | ||
| 18 | #region Tab 状态 | 19 | #region Tab 状态 |
| @@ -121,6 +122,38 @@ namespace IndustrialControl.ViewModels | @@ -121,6 +122,38 @@ namespace IndustrialControl.ViewModels | ||
| 121 | } | 122 | } |
| 122 | 123 | ||
| 123 | #endregion | 124 | #endregion |
| 125 | + | ||
| 126 | + public async Task HandleScannedAsync(string data, string symbology) | ||
| 127 | + { | ||
| 128 | + // TODO: 你的规则(判断是订单号/物料码/包装码等) | ||
| 129 | + // 然后:查询接口 / 加入列表 / 自动提交 | ||
| 130 | + await Task.CompletedTask; | ||
| 131 | + } | ||
| 132 | + | ||
| 133 | + public async Task LoadOrderAsync(string orderNo) | ||
| 134 | + { | ||
| 135 | + // 0) 清空旧数据 | ||
| 136 | + ClearAll(); | ||
| 137 | + | ||
| 138 | + // 1) 基础信息 | ||
| 139 | + //var order = await _warehouseSvc.GetInboundOrderAsync(orderNo); | ||
| 140 | + //OrderNo = order.OrderNo; | ||
| 141 | + //try | ||
| 142 | + //{ | ||
| 143 | + // var bins = await _warehouseSvc.ListInboundBinsAsync(orderNo); | ||
| 144 | + // AvailableBins.Clear(); | ||
| 145 | + // foreach (var b in bins) AvailableBins.Add(b); | ||
| 146 | + //} | ||
| 147 | + //catch | ||
| 148 | + //{ | ||
| 149 | + // // 兜底:本地 mock,避免空集合导致 Picker 无选项 | ||
| 150 | + // AvailableBins.Clear(); | ||
| 151 | + // foreach (var b in new[] { "CK1_A201", "CK1_A202", "CK1_A203", "CK1_A204" }) | ||
| 152 | + // AvailableBins.Add(b); | ||
| 153 | + //} | ||
| 154 | + //var defaultBin = AvailableBins.FirstOrDefault() ?? "CK1_A201"; | ||
| 155 | + } | ||
| 156 | + | ||
| 124 | } | 157 | } |
| 125 | 158 | ||
| 126 | public class OutPendingItem | 159 | public class OutPendingItem |
| 1 | using CommunityToolkit.Mvvm.ComponentModel; | 1 | using CommunityToolkit.Mvvm.ComponentModel; |
| 2 | -using CommunityToolkit.Mvvm.Input; | ||
| 3 | using System.Collections.ObjectModel; | 2 | using System.Collections.ObjectModel; |
| 4 | -using System.Threading.Tasks; | ||
| 5 | -using System.Linq; | ||
| 6 | -using System.Windows.Input; | ||
| 7 | -using Microsoft.Maui.Graphics; | ||
| 8 | 3 | ||
| 9 | namespace IndustrialControl.ViewModels | 4 | namespace IndustrialControl.ViewModels |
| 10 | { | 5 | { |
| @@ -26,13 +21,13 @@ namespace IndustrialControl.ViewModels | @@ -26,13 +21,13 @@ namespace IndustrialControl.ViewModels | ||
| 26 | [ObservableProperty] private Color pendingTextColor = Colors.White; | 21 | [ObservableProperty] private Color pendingTextColor = Colors.White; |
| 27 | [ObservableProperty] private Color scannedTabColor = Colors.White; | 22 | [ObservableProperty] private Color scannedTabColor = Colors.White; |
| 28 | [ObservableProperty] private Color scannedTextColor = Colors.Black; | 23 | [ObservableProperty] private Color scannedTextColor = Colors.Black; |
| 24 | + [ObservableProperty] private string? scanCode; | ||
| 29 | 25 | ||
| 30 | // ===== 列表数据 ===== | 26 | // ===== 列表数据 ===== |
| 31 | public ObservableCollection<OutboundPendingItem> PendingList { get; set; } = new(); | 27 | public ObservableCollection<OutboundPendingItem> PendingList { get; set; } = new(); |
| 32 | public ObservableCollection<ScannedItem> ScannedList { get; set; } = new(); | 28 | public ObservableCollection<ScannedItem> ScannedList { get; set; } = new(); |
| 33 | 29 | ||
| 34 | // ===== 扫码输入 ===== | 30 | // ===== 扫码输入 ===== |
| 35 | - [ObservableProperty] private string scanCode; | ||
| 36 | 31 | ||
| 37 | public OutboundMaterialViewModel() | 32 | public OutboundMaterialViewModel() |
| 38 | { | 33 | { |
| @@ -95,6 +90,36 @@ namespace IndustrialControl.ViewModels | @@ -95,6 +90,36 @@ namespace IndustrialControl.ViewModels | ||
| 95 | await Task.Delay(500); // 模拟网络延迟 | 90 | await Task.Delay(500); // 模拟网络延迟 |
| 96 | return true; | 91 | return true; |
| 97 | } | 92 | } |
| 93 | + public async Task HandleScannedAsync(string data, string symbology) | ||
| 94 | + { | ||
| 95 | + // TODO: 你的规则(判断是订单号/物料码/包装码等) | ||
| 96 | + // 然后:查询接口 / 加入列表 / 自动提交 | ||
| 97 | + await Task.CompletedTask; | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + public async Task LoadOrderAsync(string orderNo) | ||
| 101 | + { | ||
| 102 | + // 0) 清空旧数据 | ||
| 103 | + ClearAll(); | ||
| 104 | + | ||
| 105 | + // 1) 基础信息 | ||
| 106 | + //var order = await _warehouseSvc.GetInboundOrderAsync(orderNo); | ||
| 107 | + //OrderNo = order.OrderNo; | ||
| 108 | + //try | ||
| 109 | + //{ | ||
| 110 | + // var bins = await _warehouseSvc.ListInboundBinsAsync(orderNo); | ||
| 111 | + // AvailableBins.Clear(); | ||
| 112 | + // foreach (var b in bins) AvailableBins.Add(b); | ||
| 113 | + //} | ||
| 114 | + //catch | ||
| 115 | + //{ | ||
| 116 | + // // 兜底:本地 mock,避免空集合导致 Picker 无选项 | ||
| 117 | + // AvailableBins.Clear(); | ||
| 118 | + // foreach (var b in new[] { "CK1_A201", "CK1_A202", "CK1_A203", "CK1_A204" }) | ||
| 119 | + // AvailableBins.Add(b); | ||
| 120 | + //} | ||
| 121 | + //var defaultBin = AvailableBins.FirstOrDefault() ?? "CK1_A201"; | ||
| 122 | + } | ||
| 98 | } | 123 | } |
| 99 | 124 | ||
| 100 | // 出库单明细数据模型 | 125 | // 出库单明细数据模型 |
-
请 注册 或 登录 后发表评论