正在显示
8 个修改的文件
包含
302 行增加
和
49 行删除
| @@ -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() |
| 51 | { | 59 | { |
| 52 | - await DisplayAlert("提示", "此按钮预留摄像头扫码;硬件扫描直接扣扳机。", "确定"); | 60 | + // 退出页面即注销(防止多个程序/页面抢处理) |
| 61 | + _scanSvc.Scanned -= OnScanned; | ||
| 62 | + _scanSvc.StopListening(); | ||
| 63 | + | ||
| 64 | + base.OnDisappearing(); | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + private void OnScanned(string data, string type) | ||
| 68 | + { | ||
| 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 | + | ||
| 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"; | ||
| 15 | 26 | ||
| 16 | public void Attach(Entry entry) | 27 | public void Attach(Entry entry) |
| 17 | { | 28 | { |
| 18 | - entry.Completed += (s, e) => Publish(((Entry)s!).Text ?? string.Empty); | 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(); | 30 | + try |
| 31 | + { | ||
| 31 | var list = await _dataSvc.ListInboundOrdersAsync(SearchOrderNo, CreatedDate); | 32 | var list = await _dataSvc.ListInboundOrdersAsync(SearchOrderNo, CreatedDate); |
| 33 | + | ||
| 34 | + // ★ 在主线程更新 ObservableCollection,避免看起来“没刷新” | ||
| 35 | + await MainThread.InvokeOnMainThreadAsync(() => | ||
| 36 | + { | ||
| 37 | + Orders.Clear(); | ||
| 38 | + if (list != null) | ||
| 39 | + { | ||
| 32 | foreach (var o in list) | 40 | foreach (var o in list) |
| 33 | Orders.Add(o); | 41 | Orders.Add(o); |
| 34 | } | 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 | + | ||
| 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 |
-
请 注册 或 登录 后发表评论