作者 李壮

add binpicker

@@ -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
@@ -10,7 +10,7 @@ namespace IndustrialControl @@ -10,7 +10,7 @@ namespace IndustrialControl
10 { 10 {
11 var builder = MauiApp.CreateBuilder(); 11 var builder = MauiApp.CreateBuilder();
12 builder 12 builder
13 - .UseMauiApp<App>() 13 + .UseMauiApp<App>()
14 .ConfigureFonts(fonts => 14 .ConfigureFonts(fonts =>
15 { 15 {
16 fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); 16 fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
@@ -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>();
  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>
  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 +}
  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>
  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
@@ -93,6 +95,24 @@ public partial class InboundMaterialPage : ContentPage @@ -93,6 +95,24 @@ public partial class InboundMaterialPage : ContentPage
93 await DisplayAlert("提示", "入库失败,请检查数据", "确定"); 95 await DisplayAlert("提示", "入库失败,请检查数据", "确定");
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 {
  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 +}
  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 +}
  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 // 出库单明细数据模型