作者 李壮

outbound

... ... @@ -12,9 +12,12 @@ public partial class AppShell : Shell
_sp = App.Services ?? throw new InvalidOperationException("Services not ready");
Routing.RegisterRoute(nameof(Pages.InboundMaterialSearchPage), typeof(Pages.InboundMaterialSearchPage));
Routing.RegisterRoute(nameof(Pages.InboundMaterialPage), typeof(Pages.InboundMaterialPage));
Routing.RegisterRoute(nameof(Pages.InboundProductionSearchPage), typeof(Pages.InboundProductionSearchPage));
Routing.RegisterRoute(nameof(Pages.InboundProductionPage), typeof(Pages.InboundProductionPage));
Routing.RegisterRoute(nameof(Pages.OutboundMaterialSearchPage), typeof(Pages.OutboundMaterialSearchPage));
Routing.RegisterRoute(nameof(Pages.OutboundMaterialPage), typeof(Pages.OutboundMaterialPage));
Routing.RegisterRoute(nameof(Pages.OutboundFinishedPage), typeof(Pages.OutboundFinishedPage));
Routing.RegisterRoute(nameof(Pages.OutboundFinishedSearchPage), typeof(Pages.OutboundFinishedSearchPage));
BuildTabs(authed);
}
... ...
... ... @@ -44,7 +44,7 @@
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
<AndroidLinkMode>None</AndroidLinkMode>
<!-- 旧设备是32位ARM,强制只打 v7a,避免64位/不匹配ABI引起崩溃 -->
<AndroidSupportedAbis>armeabi-v7a</AndroidSupportedAbis>
<AndroidSupportedAbis>armeabi-v7a;arm64-v8a;x86_64;x86</AndroidSupportedAbis>
</PropertyGroup>
<!-- Release 也先保守一点(便于现场调试稳定),确认稳定再逐步打开 -->
... ... @@ -53,7 +53,7 @@
<RunAOTCompilation>false</RunAOTCompilation>
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
<AndroidSupportedAbis>armeabi-v7a</AndroidSupportedAbis>
<AndroidSupportedAbis>armeabi-v7a;arm64-v8a</AndroidSupportedAbis>
</PropertyGroup>
<ItemGroup>
... ... @@ -88,6 +88,18 @@
</ItemGroup>
<ItemGroup>
<Compile Update="Pages\OutboundMaterialSearchPage.xaml.cs">
<DependentUpon>OutboundMaterialSearchPage.xaml</DependentUpon>
</Compile>
<Compile Update="Pages\OutboundFinishedSearchPage.xaml.cs">
<DependentUpon>OutboundFinishedSearchPage.xaml</DependentUpon>
</Compile>
<Compile Update="Pages\InboundProductionSearchPage.xaml.cs">
<DependentUpon>InboundProductionSearchPage.xaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<MauiXaml Update="App.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
... ... @@ -100,6 +112,15 @@
<MauiXaml Update="Pages\InboundMaterialPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Pages\OutboundMaterialSearchPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Pages\OutboundFinishedSearchPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Pages\InboundProductionSearchPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Pages\InboundMaterialSearchPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
... ...
... ... @@ -37,8 +37,11 @@ namespace IndustrialControl
builder.Services.AddTransient<ViewModels.InboundMaterialSearchViewModel>();
builder.Services.AddTransient<ViewModels.InboundMaterialViewModel>();
builder.Services.AddTransient<ViewModels.InboundProductionViewModel>();
builder.Services.AddTransient<ViewModels.InboundProductionSearchViewModel>();
builder.Services.AddTransient<ViewModels.OutboundMaterialViewModel>();
builder.Services.AddTransient<ViewModels.OutboundMaterialSearchViewModel>();
builder.Services.AddTransient<ViewModels.OutboundFinishedViewModel>();
builder.Services.AddTransient<ViewModels.OutboundFinishedSearchViewModel>();
// ===== 注册 Pages(DI 创建)=====
builder.Services.AddTransient<Pages.LoginPage>();
... ... @@ -50,8 +53,11 @@ namespace IndustrialControl
builder.Services.AddTransient<Pages.InboundMaterialSearchPage>();
builder.Services.AddTransient<Pages.InboundMaterialPage>();
builder.Services.AddTransient<Pages.InboundProductionPage>();
builder.Services.AddTransient<Pages.InboundProductionSearchPage>();
builder.Services.AddTransient<Pages.OutboundMaterialPage>();
builder.Services.AddTransient<Pages.OutboundMaterialSearchPage>();
builder.Services.AddTransient<Pages.OutboundFinishedPage>();
builder.Services.AddTransient<Pages.OutboundFinishedSearchPage>();
var app = builder.Build();
App.Services = app.Services;
return app;
... ...
... ... @@ -14,17 +14,17 @@
private async void OnInProd(object sender, EventArgs e)
{
await Shell.Current.GoToAsync(nameof(InboundProductionPage));
await Shell.Current.GoToAsync(nameof(InboundProductionSearchPage));
}
private async void OnOutMat(object sender, EventArgs e)
{
await Shell.Current.GoToAsync(nameof(OutboundMaterialPage));
await Shell.Current.GoToAsync(nameof(OutboundMaterialSearchPage));
}
private async void OnOutFinished(object sender, EventArgs e)
{
await Shell.Current.GoToAsync(nameof(OutboundFinishedPage));
await Shell.Current.GoToAsync(nameof(OutboundFinishedSearchPage));
}
// 新增:退出登录
... ...
... ... @@ -193,8 +193,9 @@
<!-- 扫描通过 -->
<Grid BackgroundColor="#4CAF50"
HorizontalOptions="Fill"
VerticalOptions="Fill">
HorizontalOptions="Fill"
VerticalOptions="Fill"
HeightRequest="50">
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding PassScanCommand}" />
</Grid.GestureRecognizers>
... ... @@ -211,9 +212,10 @@
<!-- 取消扫描 -->
<Grid Grid.Column="1"
BackgroundColor="#F44336"
HorizontalOptions="Fill"
VerticalOptions="Fill">
BackgroundColor="#F44336"
HorizontalOptions="Fill"
VerticalOptions="Fill"
HeightRequest="50">
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding CancelScanCommand}" />
</Grid.GestureRecognizers>
... ... @@ -230,9 +232,10 @@
<!-- 确认入库 -->
<Grid Grid.Column="2"
BackgroundColor="#2196F3"
HorizontalOptions="Fill"
VerticalOptions="Fill">
BackgroundColor="#2196F3"
HorizontalOptions="Fill"
VerticalOptions="Fill"
HeightRequest="50">
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ConfirmCommand}" />
</Grid.GestureRecognizers>
... ...
... ... @@ -71,10 +71,6 @@
<Grid Grid.Row="2" ColumnDefinitions="*,Auto" Padding="0,8,0,0">
<Label Text="{Binding Orders.Count, StringFormat='共 {0} 条'}"
VerticalTextAlignment="Center" />
<Button Grid.Column="1"
Text="进入入库"
Command="{Binding GoInboundCommand}"
IsEnabled="{Binding SelectedOrder, Converter={StaticResource NullToBoolConverter}}"/>
</Grid>
</Grid>
</ContentPage>
... ...
... ... @@ -17,7 +17,7 @@
<!-- 入库单/条码扫描 -->
<Grid Grid.Row="1" ColumnDefinitions="*,60" Padding="16,8">
<Entry x:Name="ScanEntry"
Placeholder="请扫描入库单/产品/包装条码"
Placeholder="请扫描产品/包装条码"
FontSize="14"
VerticalOptions="Center"
BackgroundColor="White"
... ...
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Name="Page"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="IndustrialControl.Pages.InboundProductionSearchPage"
xmlns:conv="clr-namespace:IndustrialControl.Converters"
Title="仓储管理系统">
<ContentPage.Resources>
<ResourceDictionary>
<!-- 空/非空转布尔:非空 => true(按钮可用) -->
<conv:NullToBoolConverter x:Key="NullToBoolConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<Grid RowDefinitions="Auto,*,Auto" Padding="16" BackgroundColor="#F6F7FB">
<!-- 顶部:输入条件(第0行) -->
<VerticalStackLayout Grid.Row="0" Spacing="10">
<Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,Auto" ColumnSpacing="8" RowSpacing="8">
<Entry x:Name="OrderEntry"
Grid.Row="0" Grid.Column="0"
Placeholder="请输入入库单号/包裹条码"
VerticalOptions="Center"
BackgroundColor="White"
Text="{Binding SearchOrderNo}" />
<DatePicker Grid.Row="1" Grid.Column="0"
Date="{Binding CreatedDate}"
MinimumDate="2000-01-01" />
<Button Grid.Row="1" Grid.Column="1"
Text="查询"
Command="{Binding SearchCommand}" />
</Grid>
</VerticalStackLayout>
<!-- 中部:结果列表(第1行) -->
<CollectionView Grid.Row="1"
ItemsSource="{Binding Orders}"
SelectionMode="Single"
SelectionChanged="OnOrderSelected">
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame Margin="0,8,0,0" Padding="12" HasShadow="True" CornerRadius="10">
<!-- ⭐ 点击整卡片触发命令 -->
<Frame.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding BindingContext.OpenItemCommand, Source={x:Reference Page}}"
CommandParameter="{Binding .}" />
</Frame.GestureRecognizers>
<Grid RowDefinitions="Auto,Auto,Auto,Auto"
ColumnDefinitions="Auto,*" ColumnSpacing="8">
<Label Grid.Row="0" Grid.Column="0" Text="入库单号:" FontAttributes="Bold"/>
<Label Grid.Row="0" Grid.Column="1" Text="{Binding OrderNo}"/>
<Label Grid.Row="1" Grid.Column="0" Text="入库类型:" FontAttributes="Bold"/>
<Label Grid.Row="1" Grid.Column="1" Text="{Binding InboundType}" />
<Label Grid.Row="2" Grid.Column="0" Text="供应商:" FontAttributes="Bold"/>
<Label Grid.Row="2" Grid.Column="1" Text="{Binding Supplier}" />
<Label Grid.Row="3" Grid.Column="0" Text="创建日期:" FontAttributes="Bold"/>
<Label Grid.Row="3" Grid.Column="1" Text="{Binding CreatedAt, StringFormat='{0:yyyy-M-d}'}"/>
</Grid>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<!-- 底部:操作(第2行) -->
<Grid Grid.Row="2" ColumnDefinitions="*,Auto" Padding="0,8,0,0">
<Label Text="{Binding Orders.Count, StringFormat='共 {0} 条'}"
VerticalTextAlignment="Center" />
</Grid>
</Grid>
</ContentPage>
... ...
using System.Threading;
using IndustrialControl.Services;
using IndustrialControl.ViewModels;
namespace IndustrialControl.Pages;
public partial class InboundProductionSearchPage : ContentPage
{
private readonly ScanService _scanSvc;
private readonly InboundProductionSearchViewModel _vm;
public InboundProductionSearchPage(InboundProductionSearchViewModel vm, ScanService scanSvc)
{
_vm = vm;
BindingContext = vm;
_scanSvc = scanSvc;
InitializeComponent();
// 可选:配置前后缀与防抖
_scanSvc.Prefix = null; // 例如 "}q" 之类的前缀;没有就留 null
// _scanSvc.Suffix = "\n"; // 如果设备会附带换行,可去掉;没有就设 null
//_scanSvc.DebounceMs = 250;
_scanSvc.Suffix = null; // 先关掉
_scanSvc.DebounceMs = 0; // 先关掉
}
protected override async void OnAppearing()
{
base.OnAppearing();
// 动态注册广播接收器(只在当前页面前台时生效)
_scanSvc.Scanned += OnScanned;
_scanSvc.StartListening();
//键盘输入
_scanSvc.Attach(OrderEntry);
OrderEntry.Focus();
}
/// <summary>
/// 清空扫描记录
/// </summary>
void OnClearClicked(object sender, EventArgs e)
{
OrderEntry.Text = string.Empty;
OrderEntry.Focus();
}
protected override void OnDisappearing()
{
// 退出页面即注销(防止多个程序/页面抢处理)
_scanSvc.Scanned -= OnScanned;
_scanSvc.StopListening();
base.OnDisappearing();
}
private void OnScanned(string data, string type)
{
MainThread.BeginInvokeOnMainThread(async () =>
{
// 常见处理:自动填入单号/条码并触发查询或加入明细
_vm.SearchOrderNo = data;
});
}
private async void OnOrderSelected(object sender, SelectionChangedEventArgs e)
{
var item = e.CurrentSelection?.FirstOrDefault() as InboundOrderSummary;
if (item is null) return;
// 二选一:A) 点选跳转到入库页
await Shell.Current.GoToAsync(
$"{nameof(InboundMaterialPage)}?orderNo={Uri.EscapeDataString(item.OrderNo)}");
// 或 B) 只把单号写到输入框/VM(不跳转)
// if (BindingContext is InboundMaterialSearchViewModel vm) vm.SearchOrderNo = item.OrderNo;
((CollectionView)sender).SelectedItem = null; // 清除选中高亮
}
}
... ...
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Name="Page"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="IndustrialControl.Pages.OutboundFinishedSearchPage"
xmlns:conv="clr-namespace:IndustrialControl.Converters"
Title="仓储管理系统">
<ContentPage.Resources>
<ResourceDictionary>
<!-- 空/非空转布尔:非空 => true(按钮可用) -->
<conv:NullToBoolConverter x:Key="NullToBoolConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<Grid RowDefinitions="Auto,*,Auto" Padding="16" BackgroundColor="#F6F7FB">
<!-- 顶部:输入条件(第0行) -->
<VerticalStackLayout Grid.Row="0" Spacing="10">
<Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,Auto" ColumnSpacing="8" RowSpacing="8">
<Entry x:Name="OrderEntry"
Grid.Row="0" Grid.Column="0"
Placeholder="请输入出库单条码"
VerticalOptions="Center"
BackgroundColor="White"
Text="{Binding SearchOrderNo}" />
<DatePicker Grid.Row="1" Grid.Column="0"
Date="{Binding CreatedDate}"
MinimumDate="2000-01-01" />
<Button Grid.Row="1" Grid.Column="1"
Text="查询"
Command="{Binding SearchCommand}" />
</Grid>
</VerticalStackLayout>
<!-- 中部:结果列表(第1行) -->
<CollectionView Grid.Row="1"
ItemsSource="{Binding Orders}"
SelectionMode="Single"
SelectionChanged="OnOrderSelected">
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame Margin="0,8,0,0" Padding="12" HasShadow="True" CornerRadius="10">
<!-- ⭐ 点击整卡片触发命令 -->
<Frame.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding BindingContext.OpenItemCommand, Source={x:Reference Page}}"
CommandParameter="{Binding .}" />
</Frame.GestureRecognizers>
<Grid RowDefinitions="Auto,Auto,Auto,Auto"
ColumnDefinitions="Auto,*" ColumnSpacing="8">
<Label Grid.Row="0" Grid.Column="0" Text="入库单号:" FontAttributes="Bold"/>
<Label Grid.Row="0" Grid.Column="1" Text="{Binding OrderNo}"/>
<Label Grid.Row="1" Grid.Column="0" Text="入库类型:" FontAttributes="Bold"/>
<Label Grid.Row="1" Grid.Column="1" Text="{Binding InboundType}" />
<Label Grid.Row="2" Grid.Column="0" Text="供应商:" FontAttributes="Bold"/>
<Label Grid.Row="2" Grid.Column="1" Text="{Binding Supplier}" />
<Label Grid.Row="3" Grid.Column="0" Text="创建日期:" FontAttributes="Bold"/>
<Label Grid.Row="3" Grid.Column="1" Text="{Binding CreatedAt, StringFormat='{0:yyyy-M-d}'}"/>
</Grid>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<!-- 底部:操作(第2行) -->
<Grid Grid.Row="2" ColumnDefinitions="*,Auto" Padding="0,8,0,0">
<Label Text="{Binding Orders.Count, StringFormat='共 {0} 条'}"
VerticalTextAlignment="Center" />
</Grid>
</Grid>
</ContentPage>
... ...
using System.Threading;
using IndustrialControl.Services;
using IndustrialControl.ViewModels;
namespace IndustrialControl.Pages;
public partial class OutboundFinishedSearchPage : ContentPage
{
private readonly ScanService _scanSvc;
private readonly OutboundFinishedSearchViewModel _vm;
public OutboundFinishedSearchPage(OutboundFinishedSearchViewModel vm, ScanService scanSvc)
{
_vm = vm;
BindingContext = vm;
_scanSvc = scanSvc;
InitializeComponent();
// 可选:配置前后缀与防抖
_scanSvc.Prefix = null; // 例如 "}q" 之类的前缀;没有就留 null
// _scanSvc.Suffix = "\n"; // 如果设备会附带换行,可去掉;没有就设 null
//_scanSvc.DebounceMs = 250;
_scanSvc.Suffix = null; // 先关掉
_scanSvc.DebounceMs = 0; // 先关掉
}
protected override async void OnAppearing()
{
base.OnAppearing();
// 动态注册广播接收器(只在当前页面前台时生效)
_scanSvc.Scanned += OnScanned;
_scanSvc.StartListening();
//键盘输入
_scanSvc.Attach(OrderEntry);
OrderEntry.Focus();
}
/// <summary>
/// 清空扫描记录
/// </summary>
void OnClearClicked(object sender, EventArgs e)
{
OrderEntry.Text = string.Empty;
OrderEntry.Focus();
}
protected override void OnDisappearing()
{
// 退出页面即注销(防止多个程序/页面抢处理)
_scanSvc.Scanned -= OnScanned;
_scanSvc.StopListening();
base.OnDisappearing();
}
private void OnScanned(string data, string type)
{
MainThread.BeginInvokeOnMainThread(async () =>
{
// 常见处理:自动填入单号/条码并触发查询或加入明细
_vm.SearchOrderNo = data;
});
}
private async void OnOrderSelected(object sender, SelectionChangedEventArgs e)
{
var item = e.CurrentSelection?.FirstOrDefault() as InboundOrderSummary;
if (item is null) return;
// 二选一:A) 点选跳转到入库页
await Shell.Current.GoToAsync(
$"{nameof(OutboundFinishedPage)}?orderNo={Uri.EscapeDataString(item.OrderNo)}");
// 或 B) 只把单号写到输入框/VM(不跳转)
// if (BindingContext is InboundMaterialSearchViewModel vm) vm.SearchOrderNo = item.OrderNo;
((CollectionView)sender).SelectedItem = null; // 清除选中高亮
}
}
... ...
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Name="Page"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="IndustrialControl.Pages.OutboundMaterialSearchPage"
xmlns:conv="clr-namespace:IndustrialControl.Converters"
Title="仓储管理系统">
<ContentPage.Resources>
<ResourceDictionary>
<!-- 空/非空转布尔:非空 => true(按钮可用) -->
<conv:NullToBoolConverter x:Key="NullToBoolConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<Grid RowDefinitions="Auto,*,Auto" Padding="16" BackgroundColor="#F6F7FB">
<!-- 顶部:输入条件(第0行) -->
<VerticalStackLayout Grid.Row="0" Spacing="10">
<Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,Auto" ColumnSpacing="8" RowSpacing="8">
<Entry x:Name="OrderEntry"
Grid.Row="0" Grid.Column="0"
Placeholder="请输入出库单条码"
VerticalOptions="Center"
BackgroundColor="White"
Text="{Binding SearchOrderNo}" />
<DatePicker Grid.Row="1" Grid.Column="0"
Date="{Binding CreatedDate}"
MinimumDate="2000-01-01" />
<Button Grid.Row="1" Grid.Column="1"
Text="查询"
Command="{Binding SearchCommand}" />
</Grid>
</VerticalStackLayout>
<!-- 中部:结果列表(第1行) -->
<CollectionView Grid.Row="1"
ItemsSource="{Binding Orders}"
SelectionMode="Single"
SelectionChanged="OnOrderSelected">
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame Margin="0,8,0,0" Padding="12" HasShadow="True" CornerRadius="10">
<!-- ⭐ 点击整卡片触发命令 -->
<Frame.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding BindingContext.OpenItemCommand, Source={x:Reference Page}}"
CommandParameter="{Binding .}" />
</Frame.GestureRecognizers>
<Grid RowDefinitions="Auto,Auto,Auto,Auto"
ColumnDefinitions="Auto,*" ColumnSpacing="8">
<Label Grid.Row="0" Grid.Column="0" Text="入库单号:" FontAttributes="Bold"/>
<Label Grid.Row="0" Grid.Column="1" Text="{Binding OrderNo}"/>
<Label Grid.Row="1" Grid.Column="0" Text="入库类型:" FontAttributes="Bold"/>
<Label Grid.Row="1" Grid.Column="1" Text="{Binding InboundType}" />
<Label Grid.Row="2" Grid.Column="0" Text="供应商:" FontAttributes="Bold"/>
<Label Grid.Row="2" Grid.Column="1" Text="{Binding Supplier}" />
<Label Grid.Row="3" Grid.Column="0" Text="创建日期:" FontAttributes="Bold"/>
<Label Grid.Row="3" Grid.Column="1" Text="{Binding CreatedAt, StringFormat='{0:yyyy-M-d}'}"/>
</Grid>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<!-- 底部:操作(第2行) -->
<Grid Grid.Row="2" ColumnDefinitions="*,Auto" Padding="0,8,0,0">
<Label Text="{Binding Orders.Count, StringFormat='共 {0} 条'}"
VerticalTextAlignment="Center" />
</Grid>
</Grid>
</ContentPage>
... ...
using System.Threading;
using IndustrialControl.Services;
using IndustrialControl.ViewModels;
namespace IndustrialControl.Pages;
public partial class OutboundMaterialSearchPage : ContentPage
{
private readonly ScanService _scanSvc;
private readonly OutboundMaterialSearchViewModel _vm;
public OutboundMaterialSearchPage(OutboundMaterialSearchViewModel vm, ScanService scanSvc)
{
_vm = vm;
BindingContext = vm;
_scanSvc = scanSvc;
InitializeComponent();
// 可选:配置前后缀与防抖
_scanSvc.Prefix = null; // 例如 "}q" 之类的前缀;没有就留 null
// _scanSvc.Suffix = "\n"; // 如果设备会附带换行,可去掉;没有就设 null
//_scanSvc.DebounceMs = 250;
_scanSvc.Suffix = null; // 先关掉
_scanSvc.DebounceMs = 0; // 先关掉
}
protected override async void OnAppearing()
{
base.OnAppearing();
// 动态注册广播接收器(只在当前页面前台时生效)
_scanSvc.Scanned += OnScanned;
_scanSvc.StartListening();
//键盘输入
_scanSvc.Attach(OrderEntry);
OrderEntry.Focus();
}
/// <summary>
/// 清空扫描记录
/// </summary>
void OnClearClicked(object sender, EventArgs e)
{
OrderEntry.Text = string.Empty;
OrderEntry.Focus();
}
protected override void OnDisappearing()
{
// 退出页面即注销(防止多个程序/页面抢处理)
_scanSvc.Scanned -= OnScanned;
_scanSvc.StopListening();
base.OnDisappearing();
}
private void OnScanned(string data, string type)
{
MainThread.BeginInvokeOnMainThread(async () =>
{
// 常见处理:自动填入单号/条码并触发查询或加入明细
_vm.SearchOrderNo = data;
});
}
private async void OnOrderSelected(object sender, SelectionChangedEventArgs e)
{
var item = e.CurrentSelection?.FirstOrDefault() as InboundOrderSummary;
if (item is null) return;
// 二选一:A) 点选跳转到入库页
await Shell.Current.GoToAsync(
$"{nameof(OutboundMaterialPage)}?orderNo={Uri.EscapeDataString(item.OrderNo)}");
// 或 B) 只把单号写到输入框/VM(不跳转)
// if (BindingContext is InboundMaterialSearchViewModel vm) vm.SearchOrderNo = item.OrderNo;
((CollectionView)sender).SelectedItem = null; // 清除选中高亮
}
}
... ...
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="456" height="456" fill="#512BD4" />
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<!-- 背景随便一个形状,前景会由 appiconfg.svg 提供 -->
<rect x="0" y="0" width="512" height="512" rx="96" ry="96" fill="#FFFFFF"/>
</svg>
... ...
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<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;">
<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" />
<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" />
<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" />
<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" />
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<!-- 简单一个“IC”字母标识,你可替换成自己的LOGO路径 -->
<g transform="translate(256,256)" font-family="Segoe UI, Arial" font-size="200" text-anchor="middle" dominant-baseline="central" fill="#FFFFFF">
<text>IC</text>
</g>
</svg>
... ...
... ... @@ -5,18 +5,25 @@ namespace IndustrialControl.Services;
public interface IWarehouseDataService
{
Task<InboundOrder> GetInboundOrderAsync(string orderNo);
Task<OutboundOrder> GetOutboundOrderAsync(string orderNo);
Task<SimpleOk> ConfirmInboundAsync(string orderNo, IEnumerable<ScanItem> items);
Task<SimpleOk> ConfirmInboundProductionAsync(string orderNo, IEnumerable<ScanItem> items);
Task<SimpleOk> ConfirmOutboundProductionAsync(string orderNo, IEnumerable<ScanItem> items);
Task<SimpleOk> ConfirmOutboundMaterialAsync(string orderNo, IEnumerable<ScanItem> items);
Task<SimpleOk> ConfirmOutboundFinishedAsync(string orderNo, IEnumerable<ScanItem> items);
Task<IEnumerable<string>> ListInboundBinsAsync(string orderNo);
Task<IEnumerable<string>> ListOutboundBinsAsync(string orderNo);
// NEW: 查询列表(图1)
Task<IEnumerable<InboundOrderSummary>> ListInboundOrdersAsync(string? orderNoOrBarcode, DateTime createdDate);
// NEW: 查询列表(图1)
Task<IEnumerable<OutboundOrderSummary>> ListOutboundOrdersAsync(string? orderNoOrBarcode, DateTime createdDate);
}
public record InboundOrder(string OrderNo, string Supplier, string LinkedNo, int ExpectedQty);
public record OutboundOrder(string OrderNo, string Supplier, string LinkedNo, int ExpectedQty);
public record ScanItem(int Index, string Barcode, string? Bin, int Qty);
public record SimpleOk(bool Succeeded, string? Message = null);
... ... @@ -27,12 +34,19 @@ public class MockWarehouseDataService : IWarehouseDataService
public Task<InboundOrder> GetInboundOrderAsync(string orderNo)
=> Task.FromResult(new InboundOrder(orderNo, "XXXX", "DHD_23326", _rand.Next(10, 80)));
public Task<OutboundOrder> GetOutboundOrderAsync(string orderNo)
=> Task.FromResult(new OutboundOrder(orderNo, "XXXX", "DHD_23326", _rand.Next(10, 80)));
public Task<SimpleOk> ConfirmInboundAsync(string orderNo, IEnumerable<ScanItem> items)
=> Task.FromResult(new SimpleOk(true, $"入库成功:{items.Count()} 条"));
public Task<SimpleOk> ConfirmOutboundAsync(string orderNo, IEnumerable<ScanItem> items)
=> Task.FromResult(new SimpleOk(true, $"出库库成功:{items.Count()} 条"));
public Task<SimpleOk> ConfirmInboundProductionAsync(string orderNo, IEnumerable<ScanItem> items)
=> Task.FromResult(new SimpleOk(true, $"生产入库成功:{items.Count()} 条"));
public Task<SimpleOk> ConfirmOutboundProductionAsync(string orderNo, IEnumerable<ScanItem> items)
=> Task.FromResult(new SimpleOk(true, $"生产出库成功:{items.Count()} 条"));
public Task<SimpleOk> ConfirmOutboundMaterialAsync(string orderNo, IEnumerable<ScanItem> items)
=> Task.FromResult(new SimpleOk(true, $"物料出库成功:{items.Count()} 条"));
... ... @@ -60,6 +74,28 @@ public class MockWarehouseDataService : IWarehouseDataService
return Task.FromResult(filtered);
}
public Task<IEnumerable<OutboundOrderSummary>> ListOutboundOrdersAsync(string? orderNoOrBarcode, DateTime createdDate)
{
// 简单过滤逻辑(按需改造)
var today = DateTime.Today;
var all = Enumerable.Range(1, 8).Select(i =>
new OutboundOrderSummary(
OrderNo: $"CGD{today:yyyyMMdd}-{i:000}",
InboundType: (i % 2 == 0) ? "采购入库" : "生产入库",
Supplier: $"供应商{i}",
CreatedAt: today.AddDays(-i)
));
IEnumerable<OutboundOrderSummary> filtered = all;
//if (!string.IsNullOrWhiteSpace(orderNoOrBarcode))
// filtered = filtered.Where(x => x.OrderNo.Contains(orderNoOrBarcode, StringComparison.OrdinalIgnoreCase));
//// 示例:按创建日期“同一天”过滤(你可以换成 >= 起始 && < 次日)
//filtered = filtered.Where(x => x.CreatedAt.Date == createdDate.Date);
return Task.FromResult(filtered);
}
public Task<IEnumerable<string>> ListInboundBinsAsync(string orderNo)
{
... ... @@ -67,5 +103,9 @@ public class MockWarehouseDataService : IWarehouseDataService
return Task.FromResult<IEnumerable<string>>(bins);
}
public Task<IEnumerable<string>> ListOutboundBinsAsync(string orderNo)
{
var bins = new[] { "CK1_A201", "CK1_A202", "CK1_A203", "CK1_A204", "CK1_B101" };
return Task.FromResult<IEnumerable<string>>(bins);
}
}
... ...
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using IndustrialControl.Services;
using System.Collections.ObjectModel;
using System;
using IndustrialControl.Pages;
namespace IndustrialControl.ViewModels;
public partial class InboundProductionSearchViewModel : ObservableObject
{
private readonly IWarehouseDataService _dataSvc;
[ObservableProperty] private string? searchOrderNo;
[ObservableProperty] private DateTime _createdDate = DateTime.Today;
[ObservableProperty] private InboundOrderSummary? selectedOrder;
public InboundProductionSearchViewModel(IWarehouseDataService dataSvc)
{
_dataSvc = dataSvc;
Orders = new ObservableCollection<InboundOrderSummary>();
}
public ObservableCollection<InboundOrderSummary> Orders { get; }
[RelayCommand]
private async Task SearchAsync()
{
try
{
var list = await _dataSvc.ListInboundOrdersAsync(SearchOrderNo, CreatedDate);
// ★ 在主线程更新 ObservableCollection,避免看起来“没刷新”
await MainThread.InvokeOnMainThreadAsync(() =>
{
Orders.Clear();
if (list != null)
{
foreach (var o in list)
Orders.Add(o);
}
});
// (排查辅助)无数据时提示一下,确认命令确实执行了
if (list == null || !list.Any())
await Shell.Current.DisplayAlert("提示", "未查询到任何入库单", "确定");
}
catch (Exception ex)
{
await Shell.Current.DisplayAlert("查询失败", ex.Message, "确定");
}
}
// 打开明细(携带 orderNo 导航)
[RelayCommand]
private async Task OpenItemAsync(InboundOrderSummary item)
{
if (item is null) return;
await Shell.Current.GoToAsync(
$"{nameof(InboundProductionPage)}?orderNo={Uri.EscapeDataString(item.OrderNo)}");
}
[RelayCommand(CanExecute = nameof(CanGoInbound))]
private async Task GoInboundAsync()
{
if (SelectedOrder == null) return;
// 导航到原“入库明细/扫描”页面,并传入 orderNo
await Shell.Current.GoToAsync($"//InboundProduction?orderNo={Uri.EscapeDataString(SelectedOrder.OrderNo)}");
}
public async Task LoadOrderAsync(string orderNo)
{
var order = await _dataSvc.GetInboundOrderAsync(orderNo);
// TODO: 将 order 映射到页面“基础信息”“待入库明细”等绑定源,保持你现有绑定字段/集合不变
}
private bool CanGoInbound() => SelectedOrder != null;
}
... ...
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using IndustrialControl.Services;
using System.Collections.ObjectModel;
using System;
using IndustrialControl.Pages;
namespace IndustrialControl.ViewModels;
public partial class OutboundFinishedSearchViewModel : ObservableObject
{
private readonly IWarehouseDataService _dataSvc;
[ObservableProperty] private string? searchOrderNo;
[ObservableProperty] private DateTime _createdDate = DateTime.Today;
[ObservableProperty] private OutboundOrderSummary? selectedOrder;
public OutboundFinishedSearchViewModel(IWarehouseDataService dataSvc)
{
_dataSvc = dataSvc;
Orders = new ObservableCollection<OutboundOrderSummary>();
}
public ObservableCollection<OutboundOrderSummary> Orders { get; }
[RelayCommand]
private async Task SearchAsync()
{
try
{
var list = await _dataSvc.ListOutboundOrdersAsync(SearchOrderNo, CreatedDate);
// ★ 在主线程更新 ObservableCollection,避免看起来“没刷新”
await MainThread.InvokeOnMainThreadAsync(() =>
{
Orders.Clear();
if (list != null)
{
foreach (var o in list)
Orders.Add(o);
}
});
// (排查辅助)无数据时提示一下,确认命令确实执行了
if (list == null || !list.Any())
await Shell.Current.DisplayAlert("提示", "未查询到任何入库单", "确定");
}
catch (Exception ex)
{
await Shell.Current.DisplayAlert("查询失败", ex.Message, "确定");
}
}
// 打开明细(携带 orderNo 导航)
[RelayCommand]
private async Task OpenItemAsync(OutboundOrderSummary item)
{
if (item is null) return;
await Shell.Current.GoToAsync(
$"{nameof(OutboundMaterialPage)}?orderNo={Uri.EscapeDataString(item.OrderNo)}");
}
[RelayCommand(CanExecute = nameof(CanGoOutbound))]
private async Task GoOutboundAsync()
{
if (SelectedOrder == null) return;
// 导航到原“入库明细/扫描”页面,并传入 orderNo
await Shell.Current.GoToAsync($"//OutboundFinished?orderNo={Uri.EscapeDataString(SelectedOrder.OrderNo)}");
}
public async Task LoadOrderAsync(string orderNo)
{
var order = await _dataSvc.GetOutboundOrderAsync(orderNo);
// TODO: 将 order 映射到页面“基础信息”“待入库明细”等绑定源,保持你现有绑定字段/集合不变
}
private bool CanGoOutbound() => SelectedOrder != null;
}
... ...
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using IndustrialControl.Services;
using System.Collections.ObjectModel;
using System;
using IndustrialControl.Pages;
namespace IndustrialControl.ViewModels;
public partial class OutboundMaterialSearchViewModel : ObservableObject
{
private readonly IWarehouseDataService _dataSvc;
[ObservableProperty] private string? searchOrderNo;
[ObservableProperty] private DateTime _createdDate = DateTime.Today;
[ObservableProperty] private OutboundOrderSummary? selectedOrder;
public OutboundMaterialSearchViewModel(IWarehouseDataService dataSvc)
{
_dataSvc = dataSvc;
Orders = new ObservableCollection<OutboundOrderSummary>();
}
public ObservableCollection<OutboundOrderSummary> Orders { get; }
[RelayCommand]
private async Task SearchAsync()
{
try
{
var list = await _dataSvc.ListOutboundOrdersAsync(SearchOrderNo, CreatedDate);
// ★ 在主线程更新 ObservableCollection,避免看起来“没刷新”
await MainThread.InvokeOnMainThreadAsync(() =>
{
Orders.Clear();
if (list != null)
{
foreach (var o in list)
Orders.Add(o);
}
});
// (排查辅助)无数据时提示一下,确认命令确实执行了
if (list == null || !list.Any())
await Shell.Current.DisplayAlert("提示", "未查询到任何入库单", "确定");
}
catch (Exception ex)
{
await Shell.Current.DisplayAlert("查询失败", ex.Message, "确定");
}
}
// 打开明细(携带 orderNo 导航)
[RelayCommand]
private async Task OpenItemAsync(OutboundOrderSummary item)
{
if (item is null) return;
await Shell.Current.GoToAsync(
$"{nameof(OutboundMaterialPage)}?orderNo={Uri.EscapeDataString(item.OrderNo)}");
}
[RelayCommand(CanExecute = nameof(CanGoOutbound))]
private async Task GoOutboundAsync()
{
if (SelectedOrder == null) return;
// 导航到原“入库明细/扫描”页面,并传入 orderNo
await Shell.Current.GoToAsync($"//OutboundMaterial?orderNo={Uri.EscapeDataString(SelectedOrder.OrderNo)}");
}
public async Task LoadOrderAsync(string orderNo)
{
var order = await _dataSvc.GetOutboundOrderAsync(orderNo);
// TODO: 将 order 映射到页面“基础信息”“待入库明细”等绑定源,保持你现有绑定字段/集合不变
}
private bool CanGoOutbound() => SelectedOrder != null;
}
public record OutboundOrderSummary(
string OrderNo,
string InboundType,
string Supplier,
DateTime CreatedAt
);
... ...