作者 李壮

outbound

@@ -12,9 +12,12 @@ public partial class AppShell : Shell @@ -12,9 +12,12 @@ public partial class AppShell : Shell
12 _sp = App.Services ?? throw new InvalidOperationException("Services not ready"); 12 _sp = App.Services ?? throw new InvalidOperationException("Services not ready");
13 Routing.RegisterRoute(nameof(Pages.InboundMaterialSearchPage), typeof(Pages.InboundMaterialSearchPage)); 13 Routing.RegisterRoute(nameof(Pages.InboundMaterialSearchPage), typeof(Pages.InboundMaterialSearchPage));
14 Routing.RegisterRoute(nameof(Pages.InboundMaterialPage), typeof(Pages.InboundMaterialPage)); 14 Routing.RegisterRoute(nameof(Pages.InboundMaterialPage), typeof(Pages.InboundMaterialPage));
  15 + Routing.RegisterRoute(nameof(Pages.InboundProductionSearchPage), typeof(Pages.InboundProductionSearchPage));
15 Routing.RegisterRoute(nameof(Pages.InboundProductionPage), typeof(Pages.InboundProductionPage)); 16 Routing.RegisterRoute(nameof(Pages.InboundProductionPage), typeof(Pages.InboundProductionPage));
  17 + Routing.RegisterRoute(nameof(Pages.OutboundMaterialSearchPage), typeof(Pages.OutboundMaterialSearchPage));
16 Routing.RegisterRoute(nameof(Pages.OutboundMaterialPage), typeof(Pages.OutboundMaterialPage)); 18 Routing.RegisterRoute(nameof(Pages.OutboundMaterialPage), typeof(Pages.OutboundMaterialPage));
17 Routing.RegisterRoute(nameof(Pages.OutboundFinishedPage), typeof(Pages.OutboundFinishedPage)); 19 Routing.RegisterRoute(nameof(Pages.OutboundFinishedPage), typeof(Pages.OutboundFinishedPage));
  20 + Routing.RegisterRoute(nameof(Pages.OutboundFinishedSearchPage), typeof(Pages.OutboundFinishedSearchPage));
18 BuildTabs(authed); 21 BuildTabs(authed);
19 } 22 }
20 23
@@ -44,7 +44,7 @@ @@ -44,7 +44,7 @@
44 <AndroidEnableProfiledAot>false</AndroidEnableProfiledAot> 44 <AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
45 <AndroidLinkMode>None</AndroidLinkMode> 45 <AndroidLinkMode>None</AndroidLinkMode>
46 <!-- 旧设备是32位ARM,强制只打 v7a,避免64位/不匹配ABI引起崩溃 --> 46 <!-- 旧设备是32位ARM,强制只打 v7a,避免64位/不匹配ABI引起崩溃 -->
47 - <AndroidSupportedAbis>armeabi-v7a</AndroidSupportedAbis> 47 + <AndroidSupportedAbis>armeabi-v7a;arm64-v8a;x86_64;x86</AndroidSupportedAbis>
48 </PropertyGroup> 48 </PropertyGroup>
49 49
50 <!-- Release 也先保守一点(便于现场调试稳定),确认稳定再逐步打开 --> 50 <!-- Release 也先保守一点(便于现场调试稳定),确认稳定再逐步打开 -->
@@ -53,7 +53,7 @@ @@ -53,7 +53,7 @@
53 <RunAOTCompilation>false</RunAOTCompilation> 53 <RunAOTCompilation>false</RunAOTCompilation>
54 <AndroidEnableProfiledAot>false</AndroidEnableProfiledAot> 54 <AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
55 <AndroidLinkMode>SdkOnly</AndroidLinkMode> 55 <AndroidLinkMode>SdkOnly</AndroidLinkMode>
56 - <AndroidSupportedAbis>armeabi-v7a</AndroidSupportedAbis> 56 + <AndroidSupportedAbis>armeabi-v7a;arm64-v8a</AndroidSupportedAbis>
57 </PropertyGroup> 57 </PropertyGroup>
58 58
59 <ItemGroup> 59 <ItemGroup>
@@ -88,6 +88,18 @@ @@ -88,6 +88,18 @@
88 </ItemGroup> 88 </ItemGroup>
89 89
90 <ItemGroup> 90 <ItemGroup>
  91 + <Compile Update="Pages\OutboundMaterialSearchPage.xaml.cs">
  92 + <DependentUpon>OutboundMaterialSearchPage.xaml</DependentUpon>
  93 + </Compile>
  94 + <Compile Update="Pages\OutboundFinishedSearchPage.xaml.cs">
  95 + <DependentUpon>OutboundFinishedSearchPage.xaml</DependentUpon>
  96 + </Compile>
  97 + <Compile Update="Pages\InboundProductionSearchPage.xaml.cs">
  98 + <DependentUpon>InboundProductionSearchPage.xaml</DependentUpon>
  99 + </Compile>
  100 + </ItemGroup>
  101 +
  102 + <ItemGroup>
91 <MauiXaml Update="App.xaml"> 103 <MauiXaml Update="App.xaml">
92 <Generator>MSBuild:Compile</Generator> 104 <Generator>MSBuild:Compile</Generator>
93 </MauiXaml> 105 </MauiXaml>
@@ -100,6 +112,15 @@ @@ -100,6 +112,15 @@
100 <MauiXaml Update="Pages\InboundMaterialPage.xaml"> 112 <MauiXaml Update="Pages\InboundMaterialPage.xaml">
101 <Generator>MSBuild:Compile</Generator> 113 <Generator>MSBuild:Compile</Generator>
102 </MauiXaml> 114 </MauiXaml>
  115 + <MauiXaml Update="Pages\OutboundMaterialSearchPage.xaml">
  116 + <Generator>MSBuild:Compile</Generator>
  117 + </MauiXaml>
  118 + <MauiXaml Update="Pages\OutboundFinishedSearchPage.xaml">
  119 + <Generator>MSBuild:Compile</Generator>
  120 + </MauiXaml>
  121 + <MauiXaml Update="Pages\InboundProductionSearchPage.xaml">
  122 + <Generator>MSBuild:Compile</Generator>
  123 + </MauiXaml>
103 <MauiXaml Update="Pages\InboundMaterialSearchPage.xaml"> 124 <MauiXaml Update="Pages\InboundMaterialSearchPage.xaml">
104 <Generator>MSBuild:Compile</Generator> 125 <Generator>MSBuild:Compile</Generator>
105 </MauiXaml> 126 </MauiXaml>
@@ -37,8 +37,11 @@ namespace IndustrialControl @@ -37,8 +37,11 @@ namespace IndustrialControl
37 builder.Services.AddTransient<ViewModels.InboundMaterialSearchViewModel>(); 37 builder.Services.AddTransient<ViewModels.InboundMaterialSearchViewModel>();
38 builder.Services.AddTransient<ViewModels.InboundMaterialViewModel>(); 38 builder.Services.AddTransient<ViewModels.InboundMaterialViewModel>();
39 builder.Services.AddTransient<ViewModels.InboundProductionViewModel>(); 39 builder.Services.AddTransient<ViewModels.InboundProductionViewModel>();
  40 + builder.Services.AddTransient<ViewModels.InboundProductionSearchViewModel>();
40 builder.Services.AddTransient<ViewModels.OutboundMaterialViewModel>(); 41 builder.Services.AddTransient<ViewModels.OutboundMaterialViewModel>();
  42 + builder.Services.AddTransient<ViewModels.OutboundMaterialSearchViewModel>();
41 builder.Services.AddTransient<ViewModels.OutboundFinishedViewModel>(); 43 builder.Services.AddTransient<ViewModels.OutboundFinishedViewModel>();
  44 + builder.Services.AddTransient<ViewModels.OutboundFinishedSearchViewModel>();
42 45
43 // ===== 注册 Pages(DI 创建)===== 46 // ===== 注册 Pages(DI 创建)=====
44 builder.Services.AddTransient<Pages.LoginPage>(); 47 builder.Services.AddTransient<Pages.LoginPage>();
@@ -50,8 +53,11 @@ namespace IndustrialControl @@ -50,8 +53,11 @@ namespace IndustrialControl
50 builder.Services.AddTransient<Pages.InboundMaterialSearchPage>(); 53 builder.Services.AddTransient<Pages.InboundMaterialSearchPage>();
51 builder.Services.AddTransient<Pages.InboundMaterialPage>(); 54 builder.Services.AddTransient<Pages.InboundMaterialPage>();
52 builder.Services.AddTransient<Pages.InboundProductionPage>(); 55 builder.Services.AddTransient<Pages.InboundProductionPage>();
  56 + builder.Services.AddTransient<Pages.InboundProductionSearchPage>();
53 builder.Services.AddTransient<Pages.OutboundMaterialPage>(); 57 builder.Services.AddTransient<Pages.OutboundMaterialPage>();
  58 + builder.Services.AddTransient<Pages.OutboundMaterialSearchPage>();
54 builder.Services.AddTransient<Pages.OutboundFinishedPage>(); 59 builder.Services.AddTransient<Pages.OutboundFinishedPage>();
  60 + builder.Services.AddTransient<Pages.OutboundFinishedSearchPage>();
55 var app = builder.Build(); 61 var app = builder.Build();
56 App.Services = app.Services; 62 App.Services = app.Services;
57 return app; 63 return app;
@@ -14,17 +14,17 @@ @@ -14,17 +14,17 @@
14 14
15 private async void OnInProd(object sender, EventArgs e) 15 private async void OnInProd(object sender, EventArgs e)
16 { 16 {
17 - await Shell.Current.GoToAsync(nameof(InboundProductionPage)); 17 + await Shell.Current.GoToAsync(nameof(InboundProductionSearchPage));
18 } 18 }
19 19
20 private async void OnOutMat(object sender, EventArgs e) 20 private async void OnOutMat(object sender, EventArgs e)
21 { 21 {
22 - await Shell.Current.GoToAsync(nameof(OutboundMaterialPage)); 22 + await Shell.Current.GoToAsync(nameof(OutboundMaterialSearchPage));
23 } 23 }
24 24
25 private async void OnOutFinished(object sender, EventArgs e) 25 private async void OnOutFinished(object sender, EventArgs e)
26 { 26 {
27 - await Shell.Current.GoToAsync(nameof(OutboundFinishedPage)); 27 + await Shell.Current.GoToAsync(nameof(OutboundFinishedSearchPage));
28 } 28 }
29 29
30 // 新增:退出登录 30 // 新增:退出登录
@@ -193,8 +193,9 @@ @@ -193,8 +193,9 @@
193 193
194 <!-- 扫描通过 --> 194 <!-- 扫描通过 -->
195 <Grid BackgroundColor="#4CAF50" 195 <Grid BackgroundColor="#4CAF50"
196 - HorizontalOptions="Fill"  
197 - VerticalOptions="Fill"> 196 + HorizontalOptions="Fill"
  197 + VerticalOptions="Fill"
  198 + HeightRequest="50">
198 <Grid.GestureRecognizers> 199 <Grid.GestureRecognizers>
199 <TapGestureRecognizer Command="{Binding PassScanCommand}" /> 200 <TapGestureRecognizer Command="{Binding PassScanCommand}" />
200 </Grid.GestureRecognizers> 201 </Grid.GestureRecognizers>
@@ -211,9 +212,10 @@ @@ -211,9 +212,10 @@
211 212
212 <!-- 取消扫描 --> 213 <!-- 取消扫描 -->
213 <Grid Grid.Column="1" 214 <Grid Grid.Column="1"
214 - BackgroundColor="#F44336"  
215 - HorizontalOptions="Fill"  
216 - VerticalOptions="Fill"> 215 + BackgroundColor="#F44336"
  216 + HorizontalOptions="Fill"
  217 + VerticalOptions="Fill"
  218 + HeightRequest="50">
217 <Grid.GestureRecognizers> 219 <Grid.GestureRecognizers>
218 <TapGestureRecognizer Command="{Binding CancelScanCommand}" /> 220 <TapGestureRecognizer Command="{Binding CancelScanCommand}" />
219 </Grid.GestureRecognizers> 221 </Grid.GestureRecognizers>
@@ -230,9 +232,10 @@ @@ -230,9 +232,10 @@
230 232
231 <!-- 确认入库 --> 233 <!-- 确认入库 -->
232 <Grid Grid.Column="2" 234 <Grid Grid.Column="2"
233 - BackgroundColor="#2196F3"  
234 - HorizontalOptions="Fill"  
235 - VerticalOptions="Fill"> 235 + BackgroundColor="#2196F3"
  236 + HorizontalOptions="Fill"
  237 + VerticalOptions="Fill"
  238 + HeightRequest="50">
236 <Grid.GestureRecognizers> 239 <Grid.GestureRecognizers>
237 <TapGestureRecognizer Command="{Binding ConfirmCommand}" /> 240 <TapGestureRecognizer Command="{Binding ConfirmCommand}" />
238 </Grid.GestureRecognizers> 241 </Grid.GestureRecognizers>
@@ -71,10 +71,6 @@ @@ -71,10 +71,6 @@
71 <Grid Grid.Row="2" ColumnDefinitions="*,Auto" Padding="0,8,0,0"> 71 <Grid Grid.Row="2" ColumnDefinitions="*,Auto" Padding="0,8,0,0">
72 <Label Text="{Binding Orders.Count, StringFormat='共 {0} 条'}" 72 <Label Text="{Binding Orders.Count, StringFormat='共 {0} 条'}"
73 VerticalTextAlignment="Center" /> 73 VerticalTextAlignment="Center" />
74 - <Button Grid.Column="1"  
75 - Text="进入入库"  
76 - Command="{Binding GoInboundCommand}"  
77 - IsEnabled="{Binding SelectedOrder, Converter={StaticResource NullToBoolConverter}}"/>  
78 </Grid> 74 </Grid>
79 </Grid> 75 </Grid>
80 </ContentPage> 76 </ContentPage>
@@ -17,7 +17,7 @@ @@ -17,7 +17,7 @@
17 <!-- 入库单/条码扫描 --> 17 <!-- 入库单/条码扫描 -->
18 <Grid Grid.Row="1" ColumnDefinitions="*,60" Padding="16,8"> 18 <Grid Grid.Row="1" ColumnDefinitions="*,60" Padding="16,8">
19 <Entry x:Name="ScanEntry" 19 <Entry x:Name="ScanEntry"
20 - Placeholder="请扫描入库单/产品/包装条码" 20 + Placeholder="请扫描产品/包装条码"
21 FontSize="14" 21 FontSize="14"
22 VerticalOptions="Center" 22 VerticalOptions="Center"
23 BackgroundColor="White" 23 BackgroundColor="White"
  1 +<?xml version="1.0" encoding="utf-8" ?>
  2 +<ContentPage x:Name="Page"
  3 + xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
  4 + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  5 + x:Class="IndustrialControl.Pages.InboundProductionSearchPage"
  6 + xmlns:conv="clr-namespace:IndustrialControl.Converters"
  7 + Title="仓储管理系统">
  8 + <ContentPage.Resources>
  9 + <ResourceDictionary>
  10 + <!-- 空/非空转布尔:非空 => true(按钮可用) -->
  11 + <conv:NullToBoolConverter x:Key="NullToBoolConverter" />
  12 + </ResourceDictionary>
  13 + </ContentPage.Resources>
  14 +
  15 + <Grid RowDefinitions="Auto,*,Auto" Padding="16" BackgroundColor="#F6F7FB">
  16 +
  17 + <!-- 顶部:输入条件(第0行) -->
  18 + <VerticalStackLayout Grid.Row="0" Spacing="10">
  19 + <Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,Auto" ColumnSpacing="8" RowSpacing="8">
  20 + <Entry x:Name="OrderEntry"
  21 + Grid.Row="0" Grid.Column="0"
  22 + Placeholder="请输入入库单号/包裹条码"
  23 + VerticalOptions="Center"
  24 + BackgroundColor="White"
  25 + Text="{Binding SearchOrderNo}" />
  26 +
  27 +
  28 + <DatePicker Grid.Row="1" Grid.Column="0"
  29 + Date="{Binding CreatedDate}"
  30 + MinimumDate="2000-01-01" />
  31 + <Button Grid.Row="1" Grid.Column="1"
  32 + Text="查询"
  33 + Command="{Binding SearchCommand}" />
  34 + </Grid>
  35 + </VerticalStackLayout>
  36 +
  37 + <!-- 中部:结果列表(第1行) -->
  38 + <CollectionView Grid.Row="1"
  39 + ItemsSource="{Binding Orders}"
  40 + SelectionMode="Single"
  41 + SelectionChanged="OnOrderSelected">
  42 + <CollectionView.ItemTemplate>
  43 + <DataTemplate>
  44 + <Frame Margin="0,8,0,0" Padding="12" HasShadow="True" CornerRadius="10">
  45 + <!-- ⭐ 点击整卡片触发命令 -->
  46 + <Frame.GestureRecognizers>
  47 + <TapGestureRecognizer
  48 + Command="{Binding BindingContext.OpenItemCommand, Source={x:Reference Page}}"
  49 + CommandParameter="{Binding .}" />
  50 + </Frame.GestureRecognizers>
  51 + <Grid RowDefinitions="Auto,Auto,Auto,Auto"
  52 + ColumnDefinitions="Auto,*" ColumnSpacing="8">
  53 + <Label Grid.Row="0" Grid.Column="0" Text="入库单号:" FontAttributes="Bold"/>
  54 + <Label Grid.Row="0" Grid.Column="1" Text="{Binding OrderNo}"/>
  55 +
  56 + <Label Grid.Row="1" Grid.Column="0" Text="入库类型:" FontAttributes="Bold"/>
  57 + <Label Grid.Row="1" Grid.Column="1" Text="{Binding InboundType}" />
  58 +
  59 + <Label Grid.Row="2" Grid.Column="0" Text="供应商:" FontAttributes="Bold"/>
  60 + <Label Grid.Row="2" Grid.Column="1" Text="{Binding Supplier}" />
  61 +
  62 + <Label Grid.Row="3" Grid.Column="0" Text="创建日期:" FontAttributes="Bold"/>
  63 + <Label Grid.Row="3" Grid.Column="1" Text="{Binding CreatedAt, StringFormat='{0:yyyy-M-d}'}"/>
  64 + </Grid>
  65 + </Frame>
  66 + </DataTemplate>
  67 + </CollectionView.ItemTemplate>
  68 + </CollectionView>
  69 +
  70 + <!-- 底部:操作(第2行) -->
  71 + <Grid Grid.Row="2" ColumnDefinitions="*,Auto" Padding="0,8,0,0">
  72 + <Label Text="{Binding Orders.Count, StringFormat='共 {0} 条'}"
  73 + VerticalTextAlignment="Center" />
  74 + </Grid>
  75 + </Grid>
  76 +</ContentPage>
  1 +using System.Threading;
  2 +using IndustrialControl.Services;
  3 +using IndustrialControl.ViewModels;
  4 +namespace IndustrialControl.Pages;
  5 +public partial class InboundProductionSearchPage : ContentPage
  6 +{
  7 +
  8 + private readonly ScanService _scanSvc;
  9 + private readonly InboundProductionSearchViewModel _vm;
  10 + public InboundProductionSearchPage(InboundProductionSearchViewModel vm, ScanService scanSvc)
  11 + {
  12 + _vm = vm;
  13 +
  14 + BindingContext = vm;
  15 + _scanSvc = scanSvc;
  16 + InitializeComponent();
  17 + // 可选:配置前后缀与防抖
  18 + _scanSvc.Prefix = null; // 例如 "}q" 之类的前缀;没有就留 null
  19 + // _scanSvc.Suffix = "\n"; // 如果设备会附带换行,可去掉;没有就设 null
  20 + //_scanSvc.DebounceMs = 250;
  21 + _scanSvc.Suffix = null; // 先关掉
  22 + _scanSvc.DebounceMs = 0; // 先关掉
  23 + }
  24 + protected override async void OnAppearing()
  25 + {
  26 + base.OnAppearing();
  27 + // 动态注册广播接收器(只在当前页面前台时生效)
  28 + _scanSvc.Scanned += OnScanned;
  29 + _scanSvc.StartListening();
  30 + //键盘输入
  31 + _scanSvc.Attach(OrderEntry);
  32 + OrderEntry.Focus();
  33 + }
  34 +
  35 + /// <summary>
  36 + /// 清空扫描记录
  37 + /// </summary>
  38 + void OnClearClicked(object sender, EventArgs e)
  39 + {
  40 + OrderEntry.Text = string.Empty;
  41 + OrderEntry.Focus();
  42 + }
  43 +
  44 + protected override void OnDisappearing()
  45 + {
  46 + // 退出页面即注销(防止多个程序/页面抢处理)
  47 + _scanSvc.Scanned -= OnScanned;
  48 + _scanSvc.StopListening();
  49 +
  50 + base.OnDisappearing();
  51 + }
  52 +
  53 + private void OnScanned(string data, string type)
  54 + {
  55 + MainThread.BeginInvokeOnMainThread(async () =>
  56 + {
  57 + // 常见处理:自动填入单号/条码并触发查询或加入明细
  58 + _vm.SearchOrderNo = data;
  59 + });
  60 + }
  61 + private async void OnOrderSelected(object sender, SelectionChangedEventArgs e)
  62 + {
  63 + var item = e.CurrentSelection?.FirstOrDefault() as InboundOrderSummary;
  64 + if (item is null) return;
  65 +
  66 + // 二选一:A) 点选跳转到入库页
  67 + await Shell.Current.GoToAsync(
  68 + $"{nameof(InboundMaterialPage)}?orderNo={Uri.EscapeDataString(item.OrderNo)}");
  69 +
  70 + // 或 B) 只把单号写到输入框/VM(不跳转)
  71 + // if (BindingContext is InboundMaterialSearchViewModel vm) vm.SearchOrderNo = item.OrderNo;
  72 +
  73 + ((CollectionView)sender).SelectedItem = null; // 清除选中高亮
  74 + }
  75 +}
  1 +<?xml version="1.0" encoding="utf-8" ?>
  2 +<ContentPage x:Name="Page"
  3 + xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
  4 + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  5 + x:Class="IndustrialControl.Pages.OutboundFinishedSearchPage"
  6 + xmlns:conv="clr-namespace:IndustrialControl.Converters"
  7 + Title="仓储管理系统">
  8 + <ContentPage.Resources>
  9 + <ResourceDictionary>
  10 + <!-- 空/非空转布尔:非空 => true(按钮可用) -->
  11 + <conv:NullToBoolConverter x:Key="NullToBoolConverter" />
  12 + </ResourceDictionary>
  13 + </ContentPage.Resources>
  14 +
  15 + <Grid RowDefinitions="Auto,*,Auto" Padding="16" BackgroundColor="#F6F7FB">
  16 +
  17 + <!-- 顶部:输入条件(第0行) -->
  18 + <VerticalStackLayout Grid.Row="0" Spacing="10">
  19 + <Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,Auto" ColumnSpacing="8" RowSpacing="8">
  20 + <Entry x:Name="OrderEntry"
  21 + Grid.Row="0" Grid.Column="0"
  22 + Placeholder="请输入出库单条码"
  23 + VerticalOptions="Center"
  24 + BackgroundColor="White"
  25 + Text="{Binding SearchOrderNo}" />
  26 +
  27 +
  28 + <DatePicker Grid.Row="1" Grid.Column="0"
  29 + Date="{Binding CreatedDate}"
  30 + MinimumDate="2000-01-01" />
  31 + <Button Grid.Row="1" Grid.Column="1"
  32 + Text="查询"
  33 + Command="{Binding SearchCommand}" />
  34 + </Grid>
  35 + </VerticalStackLayout>
  36 +
  37 + <!-- 中部:结果列表(第1行) -->
  38 + <CollectionView Grid.Row="1"
  39 + ItemsSource="{Binding Orders}"
  40 + SelectionMode="Single"
  41 + SelectionChanged="OnOrderSelected">
  42 + <CollectionView.ItemTemplate>
  43 + <DataTemplate>
  44 + <Frame Margin="0,8,0,0" Padding="12" HasShadow="True" CornerRadius="10">
  45 + <!-- ⭐ 点击整卡片触发命令 -->
  46 + <Frame.GestureRecognizers>
  47 + <TapGestureRecognizer
  48 + Command="{Binding BindingContext.OpenItemCommand, Source={x:Reference Page}}"
  49 + CommandParameter="{Binding .}" />
  50 + </Frame.GestureRecognizers>
  51 + <Grid RowDefinitions="Auto,Auto,Auto,Auto"
  52 + ColumnDefinitions="Auto,*" ColumnSpacing="8">
  53 + <Label Grid.Row="0" Grid.Column="0" Text="入库单号:" FontAttributes="Bold"/>
  54 + <Label Grid.Row="0" Grid.Column="1" Text="{Binding OrderNo}"/>
  55 +
  56 + <Label Grid.Row="1" Grid.Column="0" Text="入库类型:" FontAttributes="Bold"/>
  57 + <Label Grid.Row="1" Grid.Column="1" Text="{Binding InboundType}" />
  58 +
  59 + <Label Grid.Row="2" Grid.Column="0" Text="供应商:" FontAttributes="Bold"/>
  60 + <Label Grid.Row="2" Grid.Column="1" Text="{Binding Supplier}" />
  61 +
  62 + <Label Grid.Row="3" Grid.Column="0" Text="创建日期:" FontAttributes="Bold"/>
  63 + <Label Grid.Row="3" Grid.Column="1" Text="{Binding CreatedAt, StringFormat='{0:yyyy-M-d}'}"/>
  64 + </Grid>
  65 + </Frame>
  66 + </DataTemplate>
  67 + </CollectionView.ItemTemplate>
  68 + </CollectionView>
  69 +
  70 + <!-- 底部:操作(第2行) -->
  71 + <Grid Grid.Row="2" ColumnDefinitions="*,Auto" Padding="0,8,0,0">
  72 + <Label Text="{Binding Orders.Count, StringFormat='共 {0} 条'}"
  73 + VerticalTextAlignment="Center" />
  74 + </Grid>
  75 + </Grid>
  76 +</ContentPage>
  1 +using System.Threading;
  2 +using IndustrialControl.Services;
  3 +using IndustrialControl.ViewModels;
  4 +namespace IndustrialControl.Pages;
  5 +public partial class OutboundFinishedSearchPage : ContentPage
  6 +{
  7 +
  8 + private readonly ScanService _scanSvc;
  9 + private readonly OutboundFinishedSearchViewModel _vm;
  10 + public OutboundFinishedSearchPage(OutboundFinishedSearchViewModel vm, ScanService scanSvc)
  11 + {
  12 + _vm = vm;
  13 +
  14 + BindingContext = vm;
  15 + _scanSvc = scanSvc;
  16 + InitializeComponent();
  17 + // 可选:配置前后缀与防抖
  18 + _scanSvc.Prefix = null; // 例如 "}q" 之类的前缀;没有就留 null
  19 + // _scanSvc.Suffix = "\n"; // 如果设备会附带换行,可去掉;没有就设 null
  20 + //_scanSvc.DebounceMs = 250;
  21 + _scanSvc.Suffix = null; // 先关掉
  22 + _scanSvc.DebounceMs = 0; // 先关掉
  23 + }
  24 + protected override async void OnAppearing()
  25 + {
  26 + base.OnAppearing();
  27 + // 动态注册广播接收器(只在当前页面前台时生效)
  28 + _scanSvc.Scanned += OnScanned;
  29 + _scanSvc.StartListening();
  30 + //键盘输入
  31 + _scanSvc.Attach(OrderEntry);
  32 + OrderEntry.Focus();
  33 + }
  34 +
  35 + /// <summary>
  36 + /// 清空扫描记录
  37 + /// </summary>
  38 + void OnClearClicked(object sender, EventArgs e)
  39 + {
  40 + OrderEntry.Text = string.Empty;
  41 + OrderEntry.Focus();
  42 + }
  43 +
  44 + protected override void OnDisappearing()
  45 + {
  46 + // 退出页面即注销(防止多个程序/页面抢处理)
  47 + _scanSvc.Scanned -= OnScanned;
  48 + _scanSvc.StopListening();
  49 +
  50 + base.OnDisappearing();
  51 + }
  52 +
  53 + private void OnScanned(string data, string type)
  54 + {
  55 + MainThread.BeginInvokeOnMainThread(async () =>
  56 + {
  57 + // 常见处理:自动填入单号/条码并触发查询或加入明细
  58 + _vm.SearchOrderNo = data;
  59 + });
  60 + }
  61 + private async void OnOrderSelected(object sender, SelectionChangedEventArgs e)
  62 + {
  63 + var item = e.CurrentSelection?.FirstOrDefault() as InboundOrderSummary;
  64 + if (item is null) return;
  65 +
  66 + // 二选一:A) 点选跳转到入库页
  67 + await Shell.Current.GoToAsync(
  68 + $"{nameof(OutboundFinishedPage)}?orderNo={Uri.EscapeDataString(item.OrderNo)}");
  69 +
  70 + // 或 B) 只把单号写到输入框/VM(不跳转)
  71 + // if (BindingContext is InboundMaterialSearchViewModel vm) vm.SearchOrderNo = item.OrderNo;
  72 +
  73 + ((CollectionView)sender).SelectedItem = null; // 清除选中高亮
  74 + }
  75 +}
  1 +<?xml version="1.0" encoding="utf-8" ?>
  2 +<ContentPage x:Name="Page"
  3 + xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
  4 + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  5 + x:Class="IndustrialControl.Pages.OutboundMaterialSearchPage"
  6 + xmlns:conv="clr-namespace:IndustrialControl.Converters"
  7 + Title="仓储管理系统">
  8 + <ContentPage.Resources>
  9 + <ResourceDictionary>
  10 + <!-- 空/非空转布尔:非空 => true(按钮可用) -->
  11 + <conv:NullToBoolConverter x:Key="NullToBoolConverter" />
  12 + </ResourceDictionary>
  13 + </ContentPage.Resources>
  14 +
  15 + <Grid RowDefinitions="Auto,*,Auto" Padding="16" BackgroundColor="#F6F7FB">
  16 +
  17 + <!-- 顶部:输入条件(第0行) -->
  18 + <VerticalStackLayout Grid.Row="0" Spacing="10">
  19 + <Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,Auto" ColumnSpacing="8" RowSpacing="8">
  20 + <Entry x:Name="OrderEntry"
  21 + Grid.Row="0" Grid.Column="0"
  22 + Placeholder="请输入出库单条码"
  23 + VerticalOptions="Center"
  24 + BackgroundColor="White"
  25 + Text="{Binding SearchOrderNo}" />
  26 +
  27 +
  28 + <DatePicker Grid.Row="1" Grid.Column="0"
  29 + Date="{Binding CreatedDate}"
  30 + MinimumDate="2000-01-01" />
  31 + <Button Grid.Row="1" Grid.Column="1"
  32 + Text="查询"
  33 + Command="{Binding SearchCommand}" />
  34 + </Grid>
  35 + </VerticalStackLayout>
  36 +
  37 + <!-- 中部:结果列表(第1行) -->
  38 + <CollectionView Grid.Row="1"
  39 + ItemsSource="{Binding Orders}"
  40 + SelectionMode="Single"
  41 + SelectionChanged="OnOrderSelected">
  42 + <CollectionView.ItemTemplate>
  43 + <DataTemplate>
  44 + <Frame Margin="0,8,0,0" Padding="12" HasShadow="True" CornerRadius="10">
  45 + <!-- ⭐ 点击整卡片触发命令 -->
  46 + <Frame.GestureRecognizers>
  47 + <TapGestureRecognizer
  48 + Command="{Binding BindingContext.OpenItemCommand, Source={x:Reference Page}}"
  49 + CommandParameter="{Binding .}" />
  50 + </Frame.GestureRecognizers>
  51 + <Grid RowDefinitions="Auto,Auto,Auto,Auto"
  52 + ColumnDefinitions="Auto,*" ColumnSpacing="8">
  53 + <Label Grid.Row="0" Grid.Column="0" Text="入库单号:" FontAttributes="Bold"/>
  54 + <Label Grid.Row="0" Grid.Column="1" Text="{Binding OrderNo}"/>
  55 +
  56 + <Label Grid.Row="1" Grid.Column="0" Text="入库类型:" FontAttributes="Bold"/>
  57 + <Label Grid.Row="1" Grid.Column="1" Text="{Binding InboundType}" />
  58 +
  59 + <Label Grid.Row="2" Grid.Column="0" Text="供应商:" FontAttributes="Bold"/>
  60 + <Label Grid.Row="2" Grid.Column="1" Text="{Binding Supplier}" />
  61 +
  62 + <Label Grid.Row="3" Grid.Column="0" Text="创建日期:" FontAttributes="Bold"/>
  63 + <Label Grid.Row="3" Grid.Column="1" Text="{Binding CreatedAt, StringFormat='{0:yyyy-M-d}'}"/>
  64 + </Grid>
  65 + </Frame>
  66 + </DataTemplate>
  67 + </CollectionView.ItemTemplate>
  68 + </CollectionView>
  69 +
  70 + <!-- 底部:操作(第2行) -->
  71 + <Grid Grid.Row="2" ColumnDefinitions="*,Auto" Padding="0,8,0,0">
  72 + <Label Text="{Binding Orders.Count, StringFormat='共 {0} 条'}"
  73 + VerticalTextAlignment="Center" />
  74 + </Grid>
  75 + </Grid>
  76 +</ContentPage>
  1 +using System.Threading;
  2 +using IndustrialControl.Services;
  3 +using IndustrialControl.ViewModels;
  4 +namespace IndustrialControl.Pages;
  5 +public partial class OutboundMaterialSearchPage : ContentPage
  6 +{
  7 +
  8 + private readonly ScanService _scanSvc;
  9 + private readonly OutboundMaterialSearchViewModel _vm;
  10 + public OutboundMaterialSearchPage(OutboundMaterialSearchViewModel vm, ScanService scanSvc)
  11 + {
  12 + _vm = vm;
  13 +
  14 + BindingContext = vm;
  15 + _scanSvc = scanSvc;
  16 + InitializeComponent();
  17 + // 可选:配置前后缀与防抖
  18 + _scanSvc.Prefix = null; // 例如 "}q" 之类的前缀;没有就留 null
  19 + // _scanSvc.Suffix = "\n"; // 如果设备会附带换行,可去掉;没有就设 null
  20 + //_scanSvc.DebounceMs = 250;
  21 + _scanSvc.Suffix = null; // 先关掉
  22 + _scanSvc.DebounceMs = 0; // 先关掉
  23 + }
  24 + protected override async void OnAppearing()
  25 + {
  26 + base.OnAppearing();
  27 + // 动态注册广播接收器(只在当前页面前台时生效)
  28 + _scanSvc.Scanned += OnScanned;
  29 + _scanSvc.StartListening();
  30 + //键盘输入
  31 + _scanSvc.Attach(OrderEntry);
  32 + OrderEntry.Focus();
  33 + }
  34 +
  35 + /// <summary>
  36 + /// 清空扫描记录
  37 + /// </summary>
  38 + void OnClearClicked(object sender, EventArgs e)
  39 + {
  40 + OrderEntry.Text = string.Empty;
  41 + OrderEntry.Focus();
  42 + }
  43 +
  44 + protected override void OnDisappearing()
  45 + {
  46 + // 退出页面即注销(防止多个程序/页面抢处理)
  47 + _scanSvc.Scanned -= OnScanned;
  48 + _scanSvc.StopListening();
  49 +
  50 + base.OnDisappearing();
  51 + }
  52 +
  53 + private void OnScanned(string data, string type)
  54 + {
  55 + MainThread.BeginInvokeOnMainThread(async () =>
  56 + {
  57 + // 常见处理:自动填入单号/条码并触发查询或加入明细
  58 + _vm.SearchOrderNo = data;
  59 + });
  60 + }
  61 + private async void OnOrderSelected(object sender, SelectionChangedEventArgs e)
  62 + {
  63 + var item = e.CurrentSelection?.FirstOrDefault() as InboundOrderSummary;
  64 + if (item is null) return;
  65 +
  66 + // 二选一:A) 点选跳转到入库页
  67 + await Shell.Current.GoToAsync(
  68 + $"{nameof(OutboundMaterialPage)}?orderNo={Uri.EscapeDataString(item.OrderNo)}");
  69 +
  70 + // 或 B) 只把单号写到输入框/VM(不跳转)
  71 + // if (BindingContext is InboundMaterialSearchViewModel vm) vm.SearchOrderNo = item.OrderNo;
  72 +
  73 + ((CollectionView)sender).SelectedItem = null; // 清除选中高亮
  74 + }
  75 +}
1 -<?xml version="1.0" encoding="UTF-8" standalone="no"?>  
2 -<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg">  
3 - <rect x="0" y="0" width="456" height="456" fill="#512BD4" />  
4 -</svg>  
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
  3 + <!-- 背景随便一个形状,前景会由 appiconfg.svg 提供 -->
  4 + <rect x="0" y="0" width="512" height="512" rx="96" ry="96" fill="#FFFFFF"/>
  5 +</svg>
1 -<?xml version="1.0" encoding="UTF-8" standalone="no"?>  
2 -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">  
3 -<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">  
4 - <path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />  
5 - <path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />  
6 - <path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />  
7 - <path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />  
8 -</svg>  
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
  3 + <!-- 简单一个“IC”字母标识,你可替换成自己的LOGO路径 -->
  4 + <g transform="translate(256,256)" font-family="Segoe UI, Arial" font-size="200" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF">
  5 + <text>IC</text>
  6 + </g>
  7 +</svg>
@@ -5,18 +5,25 @@ namespace IndustrialControl.Services; @@ -5,18 +5,25 @@ namespace IndustrialControl.Services;
5 public interface IWarehouseDataService 5 public interface IWarehouseDataService
6 { 6 {
7 Task<InboundOrder> GetInboundOrderAsync(string orderNo); 7 Task<InboundOrder> GetInboundOrderAsync(string orderNo);
  8 + Task<OutboundOrder> GetOutboundOrderAsync(string orderNo);
8 Task<SimpleOk> ConfirmInboundAsync(string orderNo, IEnumerable<ScanItem> items); 9 Task<SimpleOk> ConfirmInboundAsync(string orderNo, IEnumerable<ScanItem> items);
9 10
10 Task<SimpleOk> ConfirmInboundProductionAsync(string orderNo, IEnumerable<ScanItem> items); 11 Task<SimpleOk> ConfirmInboundProductionAsync(string orderNo, IEnumerable<ScanItem> items);
  12 + Task<SimpleOk> ConfirmOutboundProductionAsync(string orderNo, IEnumerable<ScanItem> items);
11 Task<SimpleOk> ConfirmOutboundMaterialAsync(string orderNo, IEnumerable<ScanItem> items); 13 Task<SimpleOk> ConfirmOutboundMaterialAsync(string orderNo, IEnumerable<ScanItem> items);
12 Task<SimpleOk> ConfirmOutboundFinishedAsync(string orderNo, IEnumerable<ScanItem> items); 14 Task<SimpleOk> ConfirmOutboundFinishedAsync(string orderNo, IEnumerable<ScanItem> items);
13 Task<IEnumerable<string>> ListInboundBinsAsync(string orderNo); 15 Task<IEnumerable<string>> ListInboundBinsAsync(string orderNo);
  16 + Task<IEnumerable<string>> ListOutboundBinsAsync(string orderNo);
14 17
15 // NEW: 查询列表(图1) 18 // NEW: 查询列表(图1)
16 Task<IEnumerable<InboundOrderSummary>> ListInboundOrdersAsync(string? orderNoOrBarcode, DateTime createdDate); 19 Task<IEnumerable<InboundOrderSummary>> ListInboundOrdersAsync(string? orderNoOrBarcode, DateTime createdDate);
  20 + // NEW: 查询列表(图1)
  21 + Task<IEnumerable<OutboundOrderSummary>> ListOutboundOrdersAsync(string? orderNoOrBarcode, DateTime createdDate);
17 } 22 }
18 23
19 public record InboundOrder(string OrderNo, string Supplier, string LinkedNo, int ExpectedQty); 24 public record InboundOrder(string OrderNo, string Supplier, string LinkedNo, int ExpectedQty);
  25 +
  26 +public record OutboundOrder(string OrderNo, string Supplier, string LinkedNo, int ExpectedQty);
20 public record ScanItem(int Index, string Barcode, string? Bin, int Qty); 27 public record ScanItem(int Index, string Barcode, string? Bin, int Qty);
21 public record SimpleOk(bool Succeeded, string? Message = null); 28 public record SimpleOk(bool Succeeded, string? Message = null);
22 29
@@ -27,12 +34,19 @@ public class MockWarehouseDataService : IWarehouseDataService @@ -27,12 +34,19 @@ public class MockWarehouseDataService : IWarehouseDataService
27 public Task<InboundOrder> GetInboundOrderAsync(string orderNo) 34 public Task<InboundOrder> GetInboundOrderAsync(string orderNo)
28 => Task.FromResult(new InboundOrder(orderNo, "XXXX", "DHD_23326", _rand.Next(10, 80))); 35 => Task.FromResult(new InboundOrder(orderNo, "XXXX", "DHD_23326", _rand.Next(10, 80)));
29 36
  37 + public Task<OutboundOrder> GetOutboundOrderAsync(string orderNo)
  38 + => Task.FromResult(new OutboundOrder(orderNo, "XXXX", "DHD_23326", _rand.Next(10, 80)));
  39 +
30 public Task<SimpleOk> ConfirmInboundAsync(string orderNo, IEnumerable<ScanItem> items) 40 public Task<SimpleOk> ConfirmInboundAsync(string orderNo, IEnumerable<ScanItem> items)
31 => Task.FromResult(new SimpleOk(true, $"入库成功:{items.Count()} 条")); 41 => Task.FromResult(new SimpleOk(true, $"入库成功:{items.Count()} 条"));
32 42
  43 + public Task<SimpleOk> ConfirmOutboundAsync(string orderNo, IEnumerable<ScanItem> items)
  44 + => Task.FromResult(new SimpleOk(true, $"出库库成功:{items.Count()} 条"));
  45 +
33 public Task<SimpleOk> ConfirmInboundProductionAsync(string orderNo, IEnumerable<ScanItem> items) 46 public Task<SimpleOk> ConfirmInboundProductionAsync(string orderNo, IEnumerable<ScanItem> items)
34 => Task.FromResult(new SimpleOk(true, $"生产入库成功:{items.Count()} 条")); 47 => Task.FromResult(new SimpleOk(true, $"生产入库成功:{items.Count()} 条"));
35 - 48 + public Task<SimpleOk> ConfirmOutboundProductionAsync(string orderNo, IEnumerable<ScanItem> items)
  49 + => Task.FromResult(new SimpleOk(true, $"生产出库成功:{items.Count()} 条"));
36 public Task<SimpleOk> ConfirmOutboundMaterialAsync(string orderNo, IEnumerable<ScanItem> items) 50 public Task<SimpleOk> ConfirmOutboundMaterialAsync(string orderNo, IEnumerable<ScanItem> items)
37 => Task.FromResult(new SimpleOk(true, $"物料出库成功:{items.Count()} 条")); 51 => Task.FromResult(new SimpleOk(true, $"物料出库成功:{items.Count()} 条"));
38 52
@@ -60,6 +74,28 @@ public class MockWarehouseDataService : IWarehouseDataService @@ -60,6 +74,28 @@ public class MockWarehouseDataService : IWarehouseDataService
60 74
61 return Task.FromResult(filtered); 75 return Task.FromResult(filtered);
62 } 76 }
  77 + public Task<IEnumerable<OutboundOrderSummary>> ListOutboundOrdersAsync(string? orderNoOrBarcode, DateTime createdDate)
  78 + {
  79 + // 简单过滤逻辑(按需改造)
  80 + var today = DateTime.Today;
  81 + var all = Enumerable.Range(1, 8).Select(i =>
  82 + new OutboundOrderSummary(
  83 + OrderNo: $"CGD{today:yyyyMMdd}-{i:000}",
  84 + InboundType: (i % 2 == 0) ? "采购入库" : "生产入库",
  85 + Supplier: $"供应商{i}",
  86 + CreatedAt: today.AddDays(-i)
  87 + ));
  88 +
  89 + IEnumerable<OutboundOrderSummary> filtered = all;
  90 +
  91 + //if (!string.IsNullOrWhiteSpace(orderNoOrBarcode))
  92 + // filtered = filtered.Where(x => x.OrderNo.Contains(orderNoOrBarcode, StringComparison.OrdinalIgnoreCase));
  93 +
  94 + //// 示例:按创建日期“同一天”过滤(你可以换成 >= 起始 && < 次日)
  95 + //filtered = filtered.Where(x => x.CreatedAt.Date == createdDate.Date);
  96 +
  97 + return Task.FromResult(filtered);
  98 + }
63 99
64 public Task<IEnumerable<string>> ListInboundBinsAsync(string orderNo) 100 public Task<IEnumerable<string>> ListInboundBinsAsync(string orderNo)
65 { 101 {
@@ -67,5 +103,9 @@ public class MockWarehouseDataService : IWarehouseDataService @@ -67,5 +103,9 @@ public class MockWarehouseDataService : IWarehouseDataService
67 return Task.FromResult<IEnumerable<string>>(bins); 103 return Task.FromResult<IEnumerable<string>>(bins);
68 } 104 }
69 105
70 - 106 + public Task<IEnumerable<string>> ListOutboundBinsAsync(string orderNo)
  107 + {
  108 + var bins = new[] { "CK1_A201", "CK1_A202", "CK1_A203", "CK1_A204", "CK1_B101" };
  109 + return Task.FromResult<IEnumerable<string>>(bins);
  110 + }
71 } 111 }
  1 +using CommunityToolkit.Mvvm.ComponentModel;
  2 +using CommunityToolkit.Mvvm.Input;
  3 +using IndustrialControl.Services;
  4 +using System.Collections.ObjectModel;
  5 +using System;
  6 +using IndustrialControl.Pages;
  7 +
  8 +namespace IndustrialControl.ViewModels;
  9 +
  10 +public partial class InboundProductionSearchViewModel : ObservableObject
  11 +{
  12 + private readonly IWarehouseDataService _dataSvc;
  13 + [ObservableProperty] private string? searchOrderNo;
  14 + [ObservableProperty] private DateTime _createdDate = DateTime.Today;
  15 + [ObservableProperty] private InboundOrderSummary? selectedOrder;
  16 +
  17 + public InboundProductionSearchViewModel(IWarehouseDataService dataSvc)
  18 + {
  19 + _dataSvc = dataSvc;
  20 + Orders = new ObservableCollection<InboundOrderSummary>();
  21 + }
  22 +
  23 +
  24 +
  25 + public ObservableCollection<InboundOrderSummary> Orders { get; }
  26 +
  27 + [RelayCommand]
  28 + private async Task SearchAsync()
  29 + {
  30 + try
  31 + {
  32 + var list = await _dataSvc.ListInboundOrdersAsync(SearchOrderNo, CreatedDate);
  33 +
  34 + // ★ 在主线程更新 ObservableCollection,避免看起来“没刷新”
  35 + await MainThread.InvokeOnMainThreadAsync(() =>
  36 + {
  37 + Orders.Clear();
  38 + if (list != null)
  39 + {
  40 + foreach (var o in list)
  41 + Orders.Add(o);
  42 + }
  43 + });
  44 +
  45 + // (排查辅助)无数据时提示一下,确认命令确实执行了
  46 + if (list == null || !list.Any())
  47 + await Shell.Current.DisplayAlert("提示", "未查询到任何入库单", "确定");
  48 + }
  49 + catch (Exception ex)
  50 + {
  51 + await Shell.Current.DisplayAlert("查询失败", ex.Message, "确定");
  52 + }
  53 + }
  54 +
  55 + // 打开明细(携带 orderNo 导航)
  56 + [RelayCommand]
  57 + private async Task OpenItemAsync(InboundOrderSummary item)
  58 + {
  59 + if (item is null) return;
  60 + await Shell.Current.GoToAsync(
  61 + $"{nameof(InboundProductionPage)}?orderNo={Uri.EscapeDataString(item.OrderNo)}");
  62 + }
  63 +
  64 + [RelayCommand(CanExecute = nameof(CanGoInbound))]
  65 + private async Task GoInboundAsync()
  66 + {
  67 + if (SelectedOrder == null) return;
  68 + // 导航到原“入库明细/扫描”页面,并传入 orderNo
  69 + await Shell.Current.GoToAsync($"//InboundProduction?orderNo={Uri.EscapeDataString(SelectedOrder.OrderNo)}");
  70 + }
  71 + public async Task LoadOrderAsync(string orderNo)
  72 + {
  73 + var order = await _dataSvc.GetInboundOrderAsync(orderNo);
  74 + // TODO: 将 order 映射到页面“基础信息”“待入库明细”等绑定源,保持你现有绑定字段/集合不变
  75 + }
  76 + private bool CanGoInbound() => SelectedOrder != null;
  77 +}
  78 +
  1 +using CommunityToolkit.Mvvm.ComponentModel;
  2 +using CommunityToolkit.Mvvm.Input;
  3 +using IndustrialControl.Services;
  4 +using System.Collections.ObjectModel;
  5 +using System;
  6 +using IndustrialControl.Pages;
  7 +
  8 +namespace IndustrialControl.ViewModels;
  9 +
  10 +public partial class OutboundFinishedSearchViewModel : ObservableObject
  11 +{
  12 + private readonly IWarehouseDataService _dataSvc;
  13 + [ObservableProperty] private string? searchOrderNo;
  14 + [ObservableProperty] private DateTime _createdDate = DateTime.Today;
  15 + [ObservableProperty] private OutboundOrderSummary? selectedOrder;
  16 +
  17 + public OutboundFinishedSearchViewModel(IWarehouseDataService dataSvc)
  18 + {
  19 + _dataSvc = dataSvc;
  20 + Orders = new ObservableCollection<OutboundOrderSummary>();
  21 + }
  22 +
  23 +
  24 +
  25 + public ObservableCollection<OutboundOrderSummary> Orders { get; }
  26 +
  27 + [RelayCommand]
  28 + private async Task SearchAsync()
  29 + {
  30 + try
  31 + {
  32 + var list = await _dataSvc.ListOutboundOrdersAsync(SearchOrderNo, CreatedDate);
  33 +
  34 + // ★ 在主线程更新 ObservableCollection,避免看起来“没刷新”
  35 + await MainThread.InvokeOnMainThreadAsync(() =>
  36 + {
  37 + Orders.Clear();
  38 + if (list != null)
  39 + {
  40 + foreach (var o in list)
  41 + Orders.Add(o);
  42 + }
  43 + });
  44 +
  45 + // (排查辅助)无数据时提示一下,确认命令确实执行了
  46 + if (list == null || !list.Any())
  47 + await Shell.Current.DisplayAlert("提示", "未查询到任何入库单", "确定");
  48 + }
  49 + catch (Exception ex)
  50 + {
  51 + await Shell.Current.DisplayAlert("查询失败", ex.Message, "确定");
  52 + }
  53 + }
  54 +
  55 + // 打开明细(携带 orderNo 导航)
  56 + [RelayCommand]
  57 + private async Task OpenItemAsync(OutboundOrderSummary item)
  58 + {
  59 + if (item is null) return;
  60 + await Shell.Current.GoToAsync(
  61 + $"{nameof(OutboundMaterialPage)}?orderNo={Uri.EscapeDataString(item.OrderNo)}");
  62 + }
  63 +
  64 + [RelayCommand(CanExecute = nameof(CanGoOutbound))]
  65 + private async Task GoOutboundAsync()
  66 + {
  67 + if (SelectedOrder == null) return;
  68 + // 导航到原“入库明细/扫描”页面,并传入 orderNo
  69 + await Shell.Current.GoToAsync($"//OutboundFinished?orderNo={Uri.EscapeDataString(SelectedOrder.OrderNo)}");
  70 + }
  71 + public async Task LoadOrderAsync(string orderNo)
  72 + {
  73 + var order = await _dataSvc.GetOutboundOrderAsync(orderNo);
  74 + // TODO: 将 order 映射到页面“基础信息”“待入库明细”等绑定源,保持你现有绑定字段/集合不变
  75 + }
  76 + private bool CanGoOutbound() => SelectedOrder != null;
  77 +}
  78 +
  1 +using CommunityToolkit.Mvvm.ComponentModel;
  2 +using CommunityToolkit.Mvvm.Input;
  3 +using IndustrialControl.Services;
  4 +using System.Collections.ObjectModel;
  5 +using System;
  6 +using IndustrialControl.Pages;
  7 +
  8 +namespace IndustrialControl.ViewModels;
  9 +
  10 +public partial class OutboundMaterialSearchViewModel : ObservableObject
  11 +{
  12 + private readonly IWarehouseDataService _dataSvc;
  13 + [ObservableProperty] private string? searchOrderNo;
  14 + [ObservableProperty] private DateTime _createdDate = DateTime.Today;
  15 + [ObservableProperty] private OutboundOrderSummary? selectedOrder;
  16 +
  17 + public OutboundMaterialSearchViewModel(IWarehouseDataService dataSvc)
  18 + {
  19 + _dataSvc = dataSvc;
  20 + Orders = new ObservableCollection<OutboundOrderSummary>();
  21 + }
  22 +
  23 +
  24 +
  25 + public ObservableCollection<OutboundOrderSummary> Orders { get; }
  26 +
  27 + [RelayCommand]
  28 + private async Task SearchAsync()
  29 + {
  30 + try
  31 + {
  32 + var list = await _dataSvc.ListOutboundOrdersAsync(SearchOrderNo, CreatedDate);
  33 +
  34 + // ★ 在主线程更新 ObservableCollection,避免看起来“没刷新”
  35 + await MainThread.InvokeOnMainThreadAsync(() =>
  36 + {
  37 + Orders.Clear();
  38 + if (list != null)
  39 + {
  40 + foreach (var o in list)
  41 + Orders.Add(o);
  42 + }
  43 + });
  44 +
  45 + // (排查辅助)无数据时提示一下,确认命令确实执行了
  46 + if (list == null || !list.Any())
  47 + await Shell.Current.DisplayAlert("提示", "未查询到任何入库单", "确定");
  48 + }
  49 + catch (Exception ex)
  50 + {
  51 + await Shell.Current.DisplayAlert("查询失败", ex.Message, "确定");
  52 + }
  53 + }
  54 +
  55 + // 打开明细(携带 orderNo 导航)
  56 + [RelayCommand]
  57 + private async Task OpenItemAsync(OutboundOrderSummary item)
  58 + {
  59 + if (item is null) return;
  60 + await Shell.Current.GoToAsync(
  61 + $"{nameof(OutboundMaterialPage)}?orderNo={Uri.EscapeDataString(item.OrderNo)}");
  62 + }
  63 +
  64 + [RelayCommand(CanExecute = nameof(CanGoOutbound))]
  65 + private async Task GoOutboundAsync()
  66 + {
  67 + if (SelectedOrder == null) return;
  68 + // 导航到原“入库明细/扫描”页面,并传入 orderNo
  69 + await Shell.Current.GoToAsync($"//OutboundMaterial?orderNo={Uri.EscapeDataString(SelectedOrder.OrderNo)}");
  70 + }
  71 + public async Task LoadOrderAsync(string orderNo)
  72 + {
  73 + var order = await _dataSvc.GetOutboundOrderAsync(orderNo);
  74 + // TODO: 将 order 映射到页面“基础信息”“待入库明细”等绑定源,保持你现有绑定字段/集合不变
  75 + }
  76 + private bool CanGoOutbound() => SelectedOrder != null;
  77 +}
  78 +public record OutboundOrderSummary(
  79 + string OrderNo,
  80 + string InboundType,
  81 + string Supplier,
  82 + DateTime CreatedAt
  83 +);
  84 +