作者 李壮

scan function success

@@ -24,7 +24,7 @@ @@ -24,7 +24,7 @@
24 <ApplicationTitle>IndustrialControl</ApplicationTitle> 24 <ApplicationTitle>IndustrialControl</ApplicationTitle>
25 25
26 <!-- App Identifier --> 26 <!-- App Identifier -->
27 - <ApplicationId>com.companyname.industrialcontrol</ApplicationId> 27 + <ApplicationId>com.ty.industrialcontrol</ApplicationId>
28 28
29 <!-- Versions --> 29 <!-- Versions -->
30 <ApplicationDisplayVersion>1.0</ApplicationDisplayVersion> 30 <ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
@@ -37,6 +37,24 @@ @@ -37,6 +37,24 @@
37 <TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion> 37 <TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
38 <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion> 38 <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
39 </PropertyGroup> 39 </PropertyGroup>
  40 + <!-- Debug 下:关裁剪、关AOT、关链接器;仅安卓生效 -->
  41 + <PropertyGroup Condition="'$(Configuration)'=='Debug' and $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
  42 + <PublishTrimmed>false</PublishTrimmed>
  43 + <RunAOTCompilation>false</RunAOTCompilation>
  44 + <AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
  45 + <AndroidLinkMode>None</AndroidLinkMode>
  46 + <!-- 旧设备是32位ARM,强制只打 v7a,避免64位/不匹配ABI引起崩溃 -->
  47 + <AndroidSupportedAbis>armeabi-v7a</AndroidSupportedAbis>
  48 + </PropertyGroup>
  49 +
  50 + <!-- Release 也先保守一点(便于现场调试稳定),确认稳定再逐步打开 -->
  51 + <PropertyGroup Condition="'$(Configuration)'=='Release' and $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
  52 + <PublishTrimmed>false</PublishTrimmed>
  53 + <RunAOTCompilation>false</RunAOTCompilation>
  54 + <AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
  55 + <AndroidLinkMode>SdkOnly</AndroidLinkMode>
  56 + <AndroidSupportedAbis>armeabi-v7a</AndroidSupportedAbis>
  57 + </PropertyGroup>
40 58
41 <ItemGroup> 59 <ItemGroup>
42 <!-- App Icon --> 60 <!-- App Icon -->
@@ -18,6 +18,13 @@ public partial class InboundMaterialPage : ContentPage @@ -18,6 +18,13 @@ public partial class InboundMaterialPage : ContentPage
18 BindingContext = vm; 18 BindingContext = vm;
19 _scanSvc = scanSvc; 19 _scanSvc = scanSvc;
20 _vm = vm; 20 _vm = vm;
  21 + // 可选:配置前后缀与防抖
  22 + _scanSvc.Prefix = null; // 例如 "}q" 之类的前缀;没有就留 null
  23 + // _scanSvc.Suffix = "\n"; // 如果设备会附带换行,可去掉;没有就设 null
  24 + //_scanSvc.DebounceMs = 250;
  25 + _scanSvc.Suffix = null; // 先关掉
  26 + _scanSvc.DebounceMs = 0; // 先关掉
  27 +
21 } 28 }
22 29
23 protected override async void OnAppearing() 30 protected override async void OnAppearing()
@@ -29,11 +36,15 @@ public partial class InboundMaterialPage : ContentPage @@ -29,11 +36,15 @@ public partial class InboundMaterialPage : ContentPage
29 { 36 {
30 await _vm.LoadOrderAsync(OrderNo); 37 await _vm.LoadOrderAsync(OrderNo);
31 } 38 }
32 - 39 + // 动态注册广播接收器(只在当前页面前台时生效)
  40 + _scanSvc.Scanned += OnScanned;
  41 + _scanSvc.StartListening();
  42 + //键盘输入
33 _scanSvc.Attach(ScanEntry); 43 _scanSvc.Attach(ScanEntry);
34 ScanEntry.Focus(); 44 ScanEntry.Focus();
35 } 45 }
36 46
  47 +
37 /// <summary> 48 /// <summary>
38 /// 清空扫描记录 49 /// 清空扫描记录
39 /// </summary> 50 /// </summary>
@@ -44,12 +55,25 @@ public partial class InboundMaterialPage : ContentPage @@ -44,12 +55,25 @@ public partial class InboundMaterialPage : ContentPage
44 ScanEntry.Focus(); 55 ScanEntry.Focus();
45 } 56 }
46 57
47 - /// <summary>  
48 - /// 预留摄像头扫码  
49 - /// </summary>  
50 - async void OnScanClicked(object sender, EventArgs e) 58 + protected override void OnDisappearing()
  59 + {
  60 + // 退出页面即注销(防止多个程序/页面抢处理)
  61 + _scanSvc.Scanned -= OnScanned;
  62 + _scanSvc.StopListening();
  63 +
  64 + base.OnDisappearing();
  65 + }
  66 +
  67 + private void OnScanned(string data, string type)
51 { 68 {
52 - await DisplayAlert("提示", "此按钮预留摄像头扫码;硬件扫描直接扣扳机。", "确定"); 69 + MainThread.BeginInvokeOnMainThread(async () =>
  70 + {
  71 + // 常见处理:自动填入单号/条码并触发查询或加入明细
  72 + _vm.ScanCode = data;
  73 +
  74 + // 你原本的逻辑:若识别到是订单号 → 查询;若是包装码 → 加入列表等
  75 + await _vm.HandleScannedAsync(data, type);
  76 + });
53 } 77 }
54 78
55 79
@@ -69,4 +93,6 @@ public partial class InboundMaterialPage : ContentPage @@ -69,4 +93,6 @@ public partial class InboundMaterialPage : ContentPage
69 await DisplayAlert("提示", "入库失败,请检查数据", "确定"); 93 await DisplayAlert("提示", "入库失败,请检查数据", "确定");
70 } 94 }
71 } 95 }
  96 +
  97 +
72 } 98 }
@@ -3,7 +3,14 @@ @@ -3,7 +3,14 @@
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 x:Class="IndustrialControl.Pages.InboundMaterialSearchPage" 5 x:Class="IndustrialControl.Pages.InboundMaterialSearchPage"
  6 + xmlns:conv="clr-namespace:IndustrialControl.Converters"
6 Title="仓储管理系统"> 7 Title="仓储管理系统">
  8 + <ContentPage.Resources>
  9 + <ResourceDictionary>
  10 + <!-- 空/非空转布尔:非空 => true(按钮可用) -->
  11 + <conv:NullToBoolConverter x:Key="NullToBoolConverter" />
  12 + </ResourceDictionary>
  13 + </ContentPage.Resources>
7 14
8 <Grid RowDefinitions="Auto,*,Auto" Padding="16" BackgroundColor="#F6F7FB"> 15 <Grid RowDefinitions="Auto,*,Auto" Padding="16" BackgroundColor="#F6F7FB">
9 16
@@ -13,8 +20,11 @@ @@ -13,8 +20,11 @@
13 <Entry x:Name="OrderEntry" 20 <Entry x:Name="OrderEntry"
14 Grid.Row="0" Grid.Column="0" 21 Grid.Row="0" Grid.Column="0"
15 Placeholder="请输入入库单号/包裹条码" 22 Placeholder="请输入入库单号/包裹条码"
  23 + VerticalOptions="Center"
  24 + BackgroundColor="White"
16 Text="{Binding SearchOrderNo}" /> 25 Text="{Binding SearchOrderNo}" />
17 26
  27 +
18 <DatePicker Grid.Row="1" Grid.Column="0" 28 <DatePicker Grid.Row="1" Grid.Column="0"
19 Date="{Binding CreatedDate}" 29 Date="{Binding CreatedDate}"
20 MinimumDate="2000-01-01" /> 30 MinimumDate="2000-01-01" />
  1 +using System.Threading;
  2 +using IndustrialControl.Services;
1 using IndustrialControl.ViewModels; 3 using IndustrialControl.ViewModels;
2 -  
3 namespace IndustrialControl.Pages; 4 namespace IndustrialControl.Pages;
4 -  
5 public partial class InboundMaterialSearchPage : ContentPage 5 public partial class InboundMaterialSearchPage : ContentPage
6 { 6 {
7 - public InboundMaterialSearchPage(InboundMaterialSearchViewModel vm) 7 +
  8 + private readonly ScanService _scanSvc;
  9 + private readonly InboundMaterialSearchViewModel _vm;
  10 + public InboundMaterialSearchPage(InboundMaterialSearchViewModel vm, ScanService scanSvc)
8 { 11 {
  12 + _vm = vm;
  13 +
  14 + BindingContext = vm;
  15 + _scanSvc = scanSvc;
9 InitializeComponent(); 16 InitializeComponent();
10 - var sp = Application.Current?.Handler?.MauiContext?.Services  
11 - ?? throw new InvalidOperationException("Services not ready");  
12 - BindingContext = sp.GetRequiredService<InboundMaterialSearchViewModel>(); 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();
13 } 33 }
14 34
  35 + /// <summary>
  36 + /// 清空扫描记录
  37 + /// </summary>
  38 + void OnClearClicked(object sender, EventArgs e)
  39 + {
  40 + OrderEntry.Text = string.Empty;
  41 + OrderEntry.Focus();
  42 + }
15 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 + }
16 private async void OnOrderSelected(object sender, SelectionChangedEventArgs e) 61 private async void OnOrderSelected(object sender, SelectionChangedEventArgs e)
17 { 62 {
18 var item = e.CurrentSelection?.FirstOrDefault() as InboundOrderSummary; 63 var item = e.CurrentSelection?.FirstOrDefault() as InboundOrderSummary;
19 if (item is null) return; 64 if (item is null) return;
20 65
21 - // 导航到入库页并带上单号  
22 - await Shell.Current.GoToAsync($"{nameof(InboundMaterialPage)}?orderNo={Uri.EscapeDataString(item.OrderNo)}"); 66 + // 二选一:A) 点选跳转到入库页
  67 + await Shell.Current.GoToAsync(
  68 + $"{nameof(InboundMaterialPage)}?orderNo={Uri.EscapeDataString(item.OrderNo)}");
23 69
24 - // 清除选择,避免返回后仍高亮  
25 - ((CollectionView)sender).SelectedItem = null;  
26 - } 70 + // 或 B) 只把单号写到输入框/VM(不跳转)
  71 + // if (BindingContext is InboundMaterialSearchViewModel vm) vm.SearchOrderNo = item.OrderNo;
27 72
  73 + ((CollectionView)sender).SelectedItem = null; // 清除选中高亮
  74 + }
28 } 75 }
@@ -13,7 +13,7 @@ public interface IWarehouseDataService @@ -13,7 +13,7 @@ public interface IWarehouseDataService
13 Task<IEnumerable<string>> ListInboundBinsAsync(string orderNo); 13 Task<IEnumerable<string>> ListInboundBinsAsync(string orderNo);
14 14
15 // NEW: 查询列表(图1) 15 // NEW: 查询列表(图1)
16 - Task<IEnumerable<InboundOrderSummary>> ListInboundOrdersAsync(string? fuzzyOrderNo, DateTime? createdAt); 16 + Task<IEnumerable<InboundOrderSummary>> ListInboundOrdersAsync(string? orderNoOrBarcode, DateTime createdDate);
17 } 17 }
18 18
19 public record InboundOrder(string OrderNo, string Supplier, string LinkedNo, int ExpectedQty); 19 public record InboundOrder(string OrderNo, string Supplier, string LinkedNo, int ExpectedQty);
@@ -38,25 +38,29 @@ public class MockWarehouseDataService : IWarehouseDataService @@ -38,25 +38,29 @@ public class MockWarehouseDataService : IWarehouseDataService
38 38
39 public Task<SimpleOk> ConfirmOutboundFinishedAsync(string orderNo, IEnumerable<ScanItem> items) 39 public Task<SimpleOk> ConfirmOutboundFinishedAsync(string orderNo, IEnumerable<ScanItem> items)
40 => Task.FromResult(new SimpleOk(true, $"成品出库成功:{items.Count()} 条")); 40 => Task.FromResult(new SimpleOk(true, $"成品出库成功:{items.Count()} 条"));
41 - public Task<IEnumerable<InboundOrderSummary>> ListInboundOrdersAsync(string? fuzzyOrderNo, DateTime? createdAt) 41 + public Task<IEnumerable<InboundOrderSummary>> ListInboundOrdersAsync(string? orderNoOrBarcode, DateTime createdDate)
42 { 42 {
43 - // 模拟几条数据  
44 - var today = createdAt ?? DateTime.Today;  
45 - var samples = new List<InboundOrderSummary>  
46 - {  
47 - new InboundOrderSummary("CGD20250302001", "退料入库", "供应商A", today),  
48 - new InboundOrderSummary("CGD20250302002", "退库入库", "供应商B", today.AddDays(-1)),  
49 - new InboundOrderSummary("CGD20250302003", "采购入库", "供应商C", today.AddDays(-2))  
50 - };  
51 -  
52 - // 如果有模糊条件就过滤  
53 - if (!string.IsNullOrWhiteSpace(fuzzyOrderNo))  
54 - samples = samples  
55 - .Where(s => s.OrderNo.Contains(fuzzyOrderNo, StringComparison.OrdinalIgnoreCase))  
56 - .ToList();  
57 -  
58 - return Task.FromResult<IEnumerable<InboundOrderSummary>>(samples); 43 + // 简单过滤逻辑(按需改造)
  44 + var today = DateTime.Today;
  45 + var all = Enumerable.Range(1, 8).Select(i =>
  46 + new InboundOrderSummary(
  47 + OrderNo: $"CGD{today:yyyyMMdd}-{i:000}",
  48 + InboundType: (i % 2 == 0) ? "采购入库" : "生产入库",
  49 + Supplier: $"供应商{i}",
  50 + CreatedAt: today.AddDays(-i)
  51 + ));
  52 +
  53 + IEnumerable<InboundOrderSummary> filtered = all;
  54 +
  55 + //if (!string.IsNullOrWhiteSpace(orderNoOrBarcode))
  56 + // filtered = filtered.Where(x => x.OrderNo.Contains(orderNoOrBarcode, StringComparison.OrdinalIgnoreCase));
  57 +
  58 + //// 示例:按创建日期“同一天”过滤(你可以换成 >= 起始 && < 次日)
  59 + //filtered = filtered.Where(x => x.CreatedAt.Date == createdDate.Date);
  60 +
  61 + return Task.FromResult(filtered);
59 } 62 }
  63 +
60 public Task<IEnumerable<string>> ListInboundBinsAsync(string orderNo) 64 public Task<IEnumerable<string>> ListInboundBinsAsync(string orderNo)
61 { 65 {
62 var bins = new[] { "CK1_A201", "CK1_A202", "CK1_A203", "CK1_A204", "CK1_B101" }; 66 var bins = new[] { "CK1_A201", "CK1_A202", "CK1_A203", "CK1_A204", "CK1_B101" };
1 -using CommunityToolkit.Mvvm.Messaging; 1 +using System;
  2 +using Microsoft.Maui.Controls;
2 3
3 -namespace IndustrialControl.Services; 4 +#if ANDROID
  5 +using Android.Content;
  6 +using Android.Util; // ✅ 用于 Log
  7 +using IndustrialControl.Droid; // 需要 DynamicScanReceiver
  8 +#endif
4 9
5 -public record ScanMessage(string Data);  
6 -  
7 -public class ScanService 10 +namespace IndustrialControl.Services
8 { 11 {
9 - public void Publish(string data) 12 + public class ScanService
10 { 13 {
11 - if (string.IsNullOrWhiteSpace(data)) return;  
12 - MainThread.BeginInvokeOnMainThread(() =>  
13 - WeakReferenceMessenger.Default.Send(new ScanMessage(data.Trim())));  
14 - } 14 + public event Action<string, string>? Scanned;
15 15
16 - public void Attach(Entry entry)  
17 - {  
18 - entry.Completed += (s, e) => Publish(((Entry)s!).Text ?? string.Empty); 16 + public string? Prefix { get; set; }
  17 + public string? Suffix { get; set; }
  18 + public int DebounceMs { get; set; } = 250;
  19 +
  20 + private string? _lastData;
  21 + private DateTime _lastAt = DateTime.MinValue;
  22 +
  23 + public const string BroadcastAction = "lc";
  24 + public const string DataKey = "data";
  25 + public const string TypeKey = "SCAN_BARCODE_TYPE_NAME";
  26 +
  27 + public void Attach(Entry entry)
  28 + {
  29 + entry.Completed += (s, e) =>
  30 + {
  31 + var data = entry.Text?.Trim();
  32 + if (!string.IsNullOrEmpty(data))
  33 + {
  34 +#if ANDROID
  35 + Log.Info("ScanService", $"[Attach] Entry.Completed -> {data}");
  36 +#endif
  37 + Scanned?.Invoke(data, "kbd");
  38 + entry.Text = string.Empty;
  39 + }
  40 + };
  41 +
  42 + entry.TextChanged += (s, e) =>
  43 + {
  44 + if (string.IsNullOrEmpty(e.NewTextValue)) return;
  45 +
  46 + if (e.NewTextValue.EndsWith("\n") || e.NewTextValue.EndsWith("\r"))
  47 + {
  48 + var data = e.NewTextValue.Trim();
  49 +#if ANDROID
  50 + Log.Info("ScanService", $"[Attach] Entry.TextChanged -> {data}");
  51 +#endif
  52 + Scanned?.Invoke(data, "kbd");
  53 + entry.Text = string.Empty;
  54 + }
  55 + };
  56 + }
  57 +
  58 + public void Publish(string code, string type = "")
  59 + {
  60 +#if ANDROID
  61 + Log.Info("ScanService", $"[Publish] 模拟扫码 -> {code}, type={type}");
  62 +#endif
  63 + FilterAndRaise(code, type);
  64 + }
  65 +
  66 + public void StartListening()
  67 + {
  68 +#if ANDROID
  69 + Android.Util.Log.Info("ScanService", "[StartListening] ENTER");
  70 + if (_receiver != null) return;
  71 +
  72 + _receiver = new DynamicScanReceiver();
  73 + _receiver.OnScanned += OnScannedFromPlatform;
  74 +
  75 + _filter = new IntentFilter(BroadcastAction);
  76 + Android.App.Application.Context.RegisterReceiver(_receiver, _filter);
  77 +
  78 + Log.Info("ScanService", $"[StartListening] 已注册广播 Action={BroadcastAction}");
  79 +#endif
  80 + }
  81 +
  82 + public void StopListening()
  83 + {
  84 +#if ANDROID
  85 + if (_receiver == null) return;
  86 +
  87 + try
  88 + {
  89 + Android.App.Application.Context.UnregisterReceiver(_receiver);
  90 + Log.Info("ScanService", "[StopListening] 已注销广播");
  91 + }
  92 + catch (Exception ex)
  93 + {
  94 + Log.Warn("ScanService", $"[StopListening] 注销异常: {ex.Message}");
  95 + }
  96 +
  97 + _receiver.OnScanned -= OnScannedFromPlatform;
  98 + _receiver = null;
  99 + _filter = null;
  100 +#endif
  101 + }
  102 +
  103 + private bool FilterAndRaise(string data, string type)
  104 + {
  105 + if (!string.IsNullOrEmpty(Prefix) && data.StartsWith(Prefix))
  106 + data = data.Substring(Prefix.Length);
  107 +
  108 + if (!string.IsNullOrEmpty(Suffix) && data.EndsWith(Suffix))
  109 + data = data.Substring(0, data.Length - Suffix.Length);
  110 +
  111 + var now = DateTime.UtcNow;
  112 + if (_lastData == data && (now - _lastAt).TotalMilliseconds < DebounceMs)
  113 + {
  114 +#if ANDROID
  115 + Log.Info("ScanService", $"[FilterAndRaise] 数据去抖: {data}");
  116 +#endif
  117 + return false;
  118 + }
  119 +
  120 + _lastData = data;
  121 + _lastAt = now;
  122 +
  123 +#if ANDROID
  124 + Log.Info("ScanService", $"[FilterAndRaise] 最终触发 -> {data}, type={type}");
  125 +#endif
  126 + Scanned?.Invoke(data, type);
  127 + return true;
  128 + }
  129 +
  130 +#if ANDROID
  131 + private DynamicScanReceiver? _receiver;
  132 + private IntentFilter? _filter;
  133 +
  134 + private void OnScannedFromPlatform(string data, string type)
  135 + {
  136 + Log.Info("ScanService", $"[OnScannedFromPlatform] 原始数据 -> {data}, type={type}");
  137 + FilterAndRaise(data, type);
  138 + }
  139 +#endif
19 } 140 }
20 } 141 }
@@ -27,11 +27,31 @@ public partial class InboundMaterialSearchViewModel : ObservableObject @@ -27,11 +27,31 @@ public partial class InboundMaterialSearchViewModel : ObservableObject
27 [RelayCommand] 27 [RelayCommand]
28 private async Task SearchAsync() 28 private async Task SearchAsync()
29 { 29 {
30 - Orders.Clear();  
31 - var list = await _dataSvc.ListInboundOrdersAsync(SearchOrderNo, CreatedDate);  
32 - foreach (var o in list)  
33 - Orders.Add(o); 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 + }
34 } 53 }
  54 +
35 // 打开明细(携带 orderNo 导航) 55 // 打开明细(携带 orderNo 导航)
36 [RelayCommand] 56 [RelayCommand]
37 private async Task OpenItemAsync(InboundOrderSummary item) 57 private async Task OpenItemAsync(InboundOrderSummary item)
@@ -8,6 +8,7 @@ namespace IndustrialControl.ViewModels @@ -8,6 +8,7 @@ namespace IndustrialControl.ViewModels
8 { 8 {
9 public partial class InboundMaterialViewModel : ObservableObject 9 public partial class InboundMaterialViewModel : ObservableObject
10 { 10 {
  11 + [ObservableProperty] private string? scanCode;
11 private readonly IWarehouseDataService _warehouseSvc; 12 private readonly IWarehouseDataService _warehouseSvc;
12 public ObservableCollection<string> AvailableBins { get; set; } = new ObservableCollection<string> 13 public ObservableCollection<string> AvailableBins { get; set; } = new ObservableCollection<string>
13 { 14 {
@@ -136,7 +137,7 @@ namespace IndustrialControl.ViewModels @@ -136,7 +137,7 @@ namespace IndustrialControl.ViewModels
136 137
137 var row = selected[0]; 138 var row = selected[0];
138 row.Qty += 1; 139 row.Qty += 1;
139 - 140 + row.Qty -= 1;
140 // 确保本行已变绿(如果你的行颜色基于 IsSelected) 141 // 确保本行已变绿(如果你的行颜色基于 IsSelected)
141 if (!row.IsSelected) row.IsSelected = true; 142 if (!row.IsSelected) row.IsSelected = true;
142 SelectedScanItem = row; 143 SelectedScanItem = row;
@@ -172,6 +173,12 @@ namespace IndustrialControl.ViewModels @@ -172,6 +173,12 @@ namespace IndustrialControl.ViewModels
172 // if (row.Qty == 0) row.IsSelected = false; 173 // if (row.Qty == 0) row.IsSelected = false;
173 } 174 }
174 175
  176 + public async Task HandleScannedAsync(string data, string symbology)
  177 + {
  178 + // TODO: 你的规则(判断是订单号/物料码/包装码等)
  179 + // 然后:查询接口 / 加入列表 / 自动提交
  180 + await Task.CompletedTask;
  181 + }
175 private Task ShowTip(string message) => 182 private Task ShowTip(string message) =>
176 Shell.Current?.DisplayAlert("提示", message, "确定") ?? Task.CompletedTask; 183 Shell.Current?.DisplayAlert("提示", message, "确定") ?? Task.CompletedTask;
177 184