作者 李壮

outbound

正在显示 31 个修改的文件 包含 1348 行增加1024 行删除
@@ -59,13 +59,17 @@ public record InboundScannedRow( @@ -59,13 +59,17 @@ public record InboundScannedRow(
59 59
60 60
61 public record OutboundPendingRow( 61 public record OutboundPendingRow(
62 - string? Barcode,  
63 - string? DetailId,  
64 - string? Location,  
65 string? MaterialName, 62 string? MaterialName,
66 - int PendingQty, // instockQty  
67 - int ScannedQty, // qty  
68 - string? Spec); 63 + string? MaterialCode,
  64 + string? Spec,
  65 + string? Location,
  66 + string? ProductionBatch,
  67 + string? StockBatch,
  68 + int OutstockQty,
  69 + int Qty
  70 +);
  71 +
  72 +
69 73
70 public record OutboundScannedRow( 74 public record OutboundScannedRow(
71 string Barcode, 75 string Barcode,
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 <ContentPage x:Class="IndustrialControl.Pages.InboundMoldPage" 2 <ContentPage x:Class="IndustrialControl.Pages.InboundMoldPage"
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 - Title="仓储管理系统"> 5 + Shell.NavBarIsVisible="True">
6 6
7 <Grid RowDefinitions="Auto,Auto,*,Auto,Auto" Padding="12" RowSpacing="12"> 7 <Grid RowDefinitions="Auto,Auto,*,Auto,Auto" Padding="12" RowSpacing="12">
8 8
1 <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" 1 <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
2 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 2 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
3 x:Class="IndustrialControl.Pages.OutboundMoldPage" 3 x:Class="IndustrialControl.Pages.OutboundMoldPage"
4 - BackgroundColor="White"> 4 + BackgroundColor="White" Shell.NavBarIsVisible="True">
5 5
6 <Grid RowDefinitions="Auto,Auto,Auto,*,Auto"> 6 <Grid RowDefinitions="Auto,Auto,Auto,*,Auto">
7 7
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" 2 <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
3 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 3 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
4 x:Class="IndustrialControl.Pages.OutboundMoldSearchPage" 4 x:Class="IndustrialControl.Pages.OutboundMoldSearchPage"
5 - Title="工单查询"> 5 + Shell.NavBarIsVisible="True">
6 6
7 <!-- 根布局:顶部(蓝条) / 条件区 / 列表 --> 7 <!-- 根布局:顶部(蓝条) / 条件区 / 列表 -->
8 <Grid RowDefinitions="Auto,Auto,*"> 8 <Grid RowDefinitions="Auto,Auto,*">
@@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
4 xmlns="http://schemas.microsoft.com/dotnet/2021/maui" 4 xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
5 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 5 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
6 xmlns:conv="clr-namespace:IndustrialControl.Converters" 6 xmlns:conv="clr-namespace:IndustrialControl.Converters"
7 - Title="模具出库执行"> 7 + Shell.NavBarIsVisible="True">
8 <ContentPage.Resources> 8 <ContentPage.Resources>
9 <ResourceDictionary> 9 <ResourceDictionary>
10 <!-- 转换器 --> 10 <!-- 转换器 -->
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" 2 <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
3 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 3 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
4 x:Class="IndustrialControl.Pages.WorkOrderSearchPage" 4 x:Class="IndustrialControl.Pages.WorkOrderSearchPage"
5 - Title="工单查询"> 5 + Shell.NavBarIsVisible="True">
6 6
7 <!-- 根布局:顶部(蓝条) / 条件区 / 列表 --> 7 <!-- 根布局:顶部(蓝条) / 条件区 / 列表 -->
8 <Grid RowDefinitions="Auto,Auto,*"> 8 <Grid RowDefinitions="Auto,Auto,*">
1 <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" 1 <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
2 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 2 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
3 x:Class="IndustrialControl.Pages.InboundMaterialPage" 3 x:Class="IndustrialControl.Pages.InboundMaterialPage"
4 - BackgroundColor="White"> 4 + BackgroundColor="White"
  5 + Shell.NavBarIsVisible="True">
5 6
6 <Grid RowDefinitions="Auto,Auto,Auto,*,Auto"> 7 <Grid RowDefinitions="Auto,Auto,Auto,*,Auto">
7 8
@@ -231,7 +232,7 @@ @@ -231,7 +232,7 @@
231 VerticalOptions="Fill" 232 VerticalOptions="Fill"
232 HeightRequest="50"> 233 HeightRequest="50">
233 <Grid.GestureRecognizers> 234 <Grid.GestureRecognizers>
234 - <TapGestureRecognizer Command="{Binding ConfirmCommand}" /> 235 + <TapGestureRecognizer Tapped="OnConfirmClicked" />
235 </Grid.GestureRecognizers> 236 </Grid.GestureRecognizers>
236 <StackLayout Orientation="Horizontal" 237 <StackLayout Orientation="Horizontal"
237 HorizontalOptions="Center" 238 HorizontalOptions="Center"
@@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
4 xmlns="http://schemas.microsoft.com/dotnet/2021/maui" 4 xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
5 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 5 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
6 x:Name="Page" 6 x:Name="Page"
7 - Title="入库单查询"> 7 + Shell.NavBarIsVisible="True">
8 8
9 <ScrollView> 9 <ScrollView>
10 <VerticalStackLayout Spacing="12" Padding="12"> 10 <VerticalStackLayout Spacing="12" Padding="12">
1 <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" 1 <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
2 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 2 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
3 x:Class="IndustrialControl.Pages.InboundProductionPage" 3 x:Class="IndustrialControl.Pages.InboundProductionPage"
4 - BackgroundColor="White"> 4 + BackgroundColor="White" Shell.NavBarIsVisible="True">
5 5
6 <Grid RowDefinitions="Auto,Auto,Auto,*,Auto"> 6 <Grid RowDefinitions="Auto,Auto,Auto,*,Auto">
7 7
@@ -45,7 +45,7 @@ @@ -45,7 +45,7 @@
45 <Label.FormattedText> 45 <Label.FormattedText>
46 <FormattedString> 46 <FormattedString>
47 <Span Text="工单号:" FontAttributes="Bold"/> 47 <Span Text="工单号:" FontAttributes="Bold"/>
48 - <Span Text="{Binding ArrivalNo}"/> 48 + <Span Text="{Binding WorkOrderNo}"/>
49 </FormattedString> 49 </FormattedString>
50 </Label.FormattedText> 50 </Label.FormattedText>
51 </Label> 51 </Label>
@@ -55,7 +55,7 @@ @@ -55,7 +55,7 @@
55 <Label.FormattedText> 55 <Label.FormattedText>
56 <FormattedString> 56 <FormattedString>
57 <Span Text="产品名称:" FontAttributes="Bold"/> 57 <Span Text="产品名称:" FontAttributes="Bold"/>
58 - <Span Text="{Binding PurchaseNo}"/> 58 + <Span Text="{Binding MaterialName}"/>
59 </FormattedString> 59 </FormattedString>
60 </Label.FormattedText> 60 </Label.FormattedText>
61 </Label> 61 </Label>
@@ -65,7 +65,7 @@ @@ -65,7 +65,7 @@
65 <Label.FormattedText> 65 <Label.FormattedText>
66 <FormattedString> 66 <FormattedString>
67 <Span Text="待入库数:" FontAttributes="Bold"/> 67 <Span Text="待入库数:" FontAttributes="Bold"/>
68 - <Span Text="{Binding SupplierName}"/> 68 + <Span Text="{Binding InstockQty}"/>
69 </FormattedString> 69 </FormattedString>
70 </Label.FormattedText> 70 </Label.FormattedText>
71 </Label> 71 </Label>
@@ -212,7 +212,7 @@ @@ -212,7 +212,7 @@
212 VerticalOptions="Fill" 212 VerticalOptions="Fill"
213 HeightRequest="50"> 213 HeightRequest="50">
214 <Grid.GestureRecognizers> 214 <Grid.GestureRecognizers>
215 - <TapGestureRecognizer Command="{Binding ConfirmCommand}" /> 215 + <TapGestureRecognizer Tapped="OnConfirmClicked" />
216 </Grid.GestureRecognizers> 216 </Grid.GestureRecognizers>
217 <StackLayout Orientation="Horizontal" 217 <StackLayout Orientation="Horizontal"
218 HorizontalOptions="Center" 218 HorizontalOptions="Center"
@@ -9,6 +9,9 @@ namespace IndustrialControl.Pages; @@ -9,6 +9,9 @@ namespace IndustrialControl.Pages;
9 [QueryProperty(nameof(OrderTypeName), "orderTypeName")] 9 [QueryProperty(nameof(OrderTypeName), "orderTypeName")]
10 [QueryProperty(nameof(PurchaseNo), "purchaseNo")] 10 [QueryProperty(nameof(PurchaseNo), "purchaseNo")]
11 [QueryProperty(nameof(SupplierName), "supplierName")] 11 [QueryProperty(nameof(SupplierName), "supplierName")]
  12 +[QueryProperty(nameof(WorkOrderNo), "workOrderNo")]
  13 +[QueryProperty(nameof(MaterialName), "materialName")]
  14 +[QueryProperty(nameof(InstockQty), "instockQty")]
12 [QueryProperty(nameof(CreatedTime), "createdTime")] 15 [QueryProperty(nameof(CreatedTime), "createdTime")]
13 public partial class InboundProductionPage : ContentPage 16 public partial class InboundProductionPage : ContentPage
14 { 17 {
@@ -21,6 +24,10 @@ public partial class InboundProductionPage : ContentPage @@ -21,6 +24,10 @@ public partial class InboundProductionPage : ContentPage
21 public string? PurchaseNo { get; set; } 24 public string? PurchaseNo { get; set; }
22 public string? SupplierName { get; set; } 25 public string? SupplierName { get; set; }
23 public string? CreatedTime { get; set; } 26 public string? CreatedTime { get; set; }
  27 + public string? WorkOrderNo { get; set; }
  28 + public string? MaterialName { get; set; }
  29 + public int InstockQty { get; set; }
  30 +
24 private readonly IDialogService _dialogs; 31 private readonly IDialogService _dialogs;
25 32
26 public InboundProductionPage(InboundProductionViewModel vm, ScanService scanSvc, IDialogService dialogs) 33 public InboundProductionPage(InboundProductionViewModel vm, ScanService scanSvc, IDialogService dialogs)
@@ -53,6 +60,9 @@ public partial class InboundProductionPage : ContentPage @@ -53,6 +60,9 @@ public partial class InboundProductionPage : ContentPage
53 orderTypeName: OrderTypeName ?? "", 60 orderTypeName: OrderTypeName ?? "",
54 purchaseNo: PurchaseNo ?? "", 61 purchaseNo: PurchaseNo ?? "",
55 supplierName: SupplierName ?? "", 62 supplierName: SupplierName ?? "",
  63 + workOrderNo: WorkOrderNo ?? "",
  64 + materialName: MaterialName ?? "",
  65 + instockQty: InstockQty,
56 createdTime: CreatedTime ?? "" 66 createdTime: CreatedTime ?? ""
57 ); 67 );
58 } 68 }
@@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
4 xmlns="http://schemas.microsoft.com/dotnet/2021/maui" 4 xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
5 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 5 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
6 x:Name="Page" 6 x:Name="Page"
7 - Title="入库单查询"> 7 + Shell.NavBarIsVisible="True">
8 8
9 <ScrollView> 9 <ScrollView>
10 <VerticalStackLayout Spacing="12" Padding="12"> 10 <VerticalStackLayout Spacing="12" Padding="12">
1 <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" 1 <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
2 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 2 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
3 x:Class="IndustrialControl.Pages.OutboundFinishedPage" 3 x:Class="IndustrialControl.Pages.OutboundFinishedPage"
4 - BackgroundColor="White"> 4 + BackgroundColor="White" Shell.NavBarIsVisible="True">
5 5
6 <Grid RowDefinitions="Auto,Auto,Auto,*,Auto"> 6 <Grid RowDefinitions="Auto,Auto,Auto,*,Auto">
7 7
@@ -14,216 +14,243 @@ @@ -14,216 +14,243 @@
14 FontAttributes="Bold"/> 14 FontAttributes="Bold"/>
15 </Grid> 15 </Grid>
16 16
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"
24 HeightRequest="40" 24 HeightRequest="40"
25 - Text="{Binding OrderNo}" />  
26 - <ImageButton Grid.Column="1"  
27 - Source="scan.png"  
28 - BackgroundColor="#E6F2FF"  
29 - CornerRadius="4"  
30 - Padding="10"  
31 - Clicked="OnScanClicked"/> 25 + Text="{Binding ScanCode}" />
  26 +
32 </Grid> 27 </Grid>
33 28
34 <!-- 基础信息 --> 29 <!-- 基础信息 -->
35 - <Frame Grid.Row="2" Margin="16,0" Padding="8" BorderColor="#CCCCCC" BackgroundColor="White">  
36 - <Grid RowDefinitions="Auto,Auto,Auto,Auto"  
37 - ColumnDefinitions="Auto,* ,Auto,*"  
38 - ColumnSpacing="8"  
39 - RowSpacing="6">  
40 -  
41 - <!-- 出库单号(独占一行) -->  
42 - <Label Grid.Row="0" Grid.Column="0" Text="出库单号:" FontAttributes="Bold" VerticalOptions="Center" />  
43 - <Label Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="3"  
44 - Text="{Binding OrderNo}" VerticalOptions="Center" />  
45 -  
46 - <!-- 发货单号 + 客户 -->  
47 - <Label Grid.Row="1" Grid.Column="0" Text="发货单号:" FontAttributes="Bold" VerticalOptions="Center" />  
48 - <Label Grid.Row="1" Grid.Column="1" Text="{Binding DeliveryNo}" VerticalOptions="Center" />  
49 - <Label Grid.Row="1" Grid.Column="2" Text="客户:" FontAttributes="Bold" VerticalOptions="Center" />  
50 - <Label Grid.Row="1" Grid.Column="3" Text="{Binding Customer}" VerticalOptions="Center" />  
51 -  
52 - <!-- 要求发货时间 + 关联销售单 -->  
53 - <Label Grid.Row="2" Grid.Column="0" Text="要求发货时间:" FontAttributes="Bold" VerticalOptions="Center" />  
54 - <Label Grid.Row="2" Grid.Column="1" Text="{Binding DeliveryTime}" VerticalOptions="Center" />  
55 - <Label Grid.Row="2" Grid.Column="2" Text="关联销售单:" FontAttributes="Bold" VerticalOptions="Center" />  
56 - <Label Grid.Row="2" Grid.Column="3" Text="{Binding SalesOrder}" VerticalOptions="Center" />  
57 -  
58 - <!-- 发货单备注(独占一行) -->  
59 - <Label Grid.Row="3" Grid.Column="0" Text="发货单备注:" FontAttributes="Bold" VerticalOptions="Center" />  
60 - <Label Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="3"  
61 - Text="{Binding Remark}" VerticalOptions="Center" /> 30 + <Frame Grid.Row="2" Margin="8,0" Padding="8" BorderColor="#CCCCCC" BackgroundColor="White">
  31 + <Grid RowDefinitions="Auto,Auto" ColumnDefinitions="*,*" ColumnSpacing="16" RowSpacing="6">
62 32
63 - </Grid>  
64 - </Frame> 33 + <!-- 出库单号 -->
  34 + <Label Grid.Row="0" Grid.Column="0" FontSize="13" LineBreakMode="TailTruncation" MaxLines="1">
  35 + <Label.FormattedText>
  36 + <FormattedString>
  37 + <Span Text="出库单号:" FontAttributes="Bold"/>
  38 + <Span Text="{Binding OutstockNo}"/>
  39 + </FormattedString>
  40 + </Label.FormattedText>
  41 + </Label>
65 42
  43 + <!-- 发货单号 -->
  44 + <Label Grid.Row="1" Grid.Column="0" FontSize="13" LineBreakMode="TailTruncation" MaxLines="1">
  45 + <Label.FormattedText>
  46 + <FormattedString>
  47 + <Span Text="发货单号:" FontAttributes="Bold"/>
  48 + <Span Text="{Binding DeliveryNo}"/>
  49 + </FormattedString>
  50 + </Label.FormattedText>
  51 + </Label>
66 52
  53 + <!-- 客户 -->
  54 + <Label Grid.Row="1" Grid.Column="1" FontSize="13" LineBreakMode="TailTruncation" MaxLines="1">
  55 + <Label.FormattedText>
  56 + <FormattedString>
  57 + <Span Text="客户:" FontAttributes="Bold"/>
  58 + <Span Text="{Binding PurchaseNo}"/>
  59 + </FormattedString>
  60 + </Label.FormattedText>
  61 + </Label>
67 62
68 - <!-- Tab切换 + 表格 --> 63 + <!-- 要求发货时间 -->
  64 + <Label Grid.Row="2" Grid.Column="0" FontSize="13" LineBreakMode="TailTruncation" MaxLines="1">
  65 + <Label.FormattedText>
  66 + <FormattedString>
  67 + <Span Text="要求发货时间:" FontAttributes="Bold"/>
  68 + <Span Text="{Binding SupplierName}"/>
  69 + </FormattedString>
  70 + </Label.FormattedText>
  71 + </Label>
  72 + <!-- 关联销售单 -->
  73 + <Label Grid.Row="2" Grid.Column="1" FontSize="13" LineBreakMode="TailTruncation" MaxLines="1">
  74 + <Label.FormattedText>
  75 + <FormattedString>
  76 + <Span Text="关联销售单:" FontAttributes="Bold"/>
  77 + <Span Text="{Binding SupplierName}"/>
  78 + </FormattedString>
  79 + </Label.FormattedText>
  80 + </Label>
  81 + <!-- 发货单备注 -->
  82 + <Label Grid.Row="3" Grid.Column="0" FontSize="13" LineBreakMode="TailTruncation" MaxLines="1">
  83 + <Label.FormattedText>
  84 + <FormattedString>
  85 + <Span Text="发货单备注:" FontAttributes="Bold"/>
  86 + <Span Text="{Binding SupplierName}"/>
  87 + </FormattedString>
  88 + </Label.FormattedText>
  89 + </Label>
  90 + </Grid>
  91 + </Frame>
  92 +
  93 + <!-- Tab切换 -->
69 <Grid Grid.Row="3" RowDefinitions="Auto,Auto,*" Margin="0"> 94 <Grid Grid.Row="3" RowDefinitions="Auto,Auto,*" Margin="0">
70 - <!-- Tab -->  
71 <Grid ColumnDefinitions="*,*" BackgroundColor="White"> 95 <Grid ColumnDefinitions="*,*" BackgroundColor="White">
72 - <Button Text="出库单明细"  
73 - BackgroundColor="{Binding PendingTabColor}"  
74 - TextColor="{Binding PendingTextColor}"  
75 - Clicked="OnPendingTabClicked"/> 96 + <Button Text="待入库明细"
  97 + Command="{Binding ShowPendingCommand}"
  98 + BackgroundColor="{Binding PendingTabColor}"
  99 + TextColor="{Binding PendingTextColor}" />
76 <Button Text="扫描明细" 100 <Button Text="扫描明细"
77 - Grid.Column="1"  
78 - BackgroundColor="{Binding ScannedTabColor}"  
79 - TextColor="{Binding ScannedTextColor}"  
80 - Clicked="OnScannedTabClicked"/> 101 + Grid.Column="1"
  102 + Command="{Binding ShowScannedCommand}"
  103 + BackgroundColor="{Binding ScannedTabColor}"
  104 + TextColor="{Binding ScannedTextColor}" />
81 </Grid> 105 </Grid>
82 106
83 - <!-- 出库单明细表头 -->  
84 - <!-- 出库单明细表头 -->  
85 - <Grid Grid.Row="1"  
86 - ColumnDefinitions="2*,2*,1.5*,2*,2*,1.5*,1.5*"  
87 - BackgroundColor="#F2F2F2"  
88 - IsVisible="{Binding IsPendingVisible}"  
89 - Padding="4"  
90 - HeightRequest="50">  
91 - <Label Text="产品名称" FontAttributes="Bold"  
92 - HorizontalTextAlignment="Center"  
93 - VerticalTextAlignment="Center"  
94 - LineBreakMode="WordWrap"/>  
95 - <Label Grid.Column="1" Text="产品编码" FontAttributes="Bold"  
96 - HorizontalTextAlignment="Center"  
97 - VerticalTextAlignment="Center"  
98 - LineBreakMode="WordWrap"/>  
99 - <Label Grid.Column="2" Text="规格" FontAttributes="Bold"  
100 - HorizontalTextAlignment="Center"  
101 - VerticalTextAlignment="Center"  
102 - LineBreakMode="WordWrap"/>  
103 - <Label Grid.Column="3" Text="出库库位" FontAttributes="Bold"  
104 - HorizontalTextAlignment="Center"  
105 - VerticalTextAlignment="Center"  
106 - LineBreakMode="WordWrap"/>  
107 - <Label Grid.Column="4" Text="生产批号" FontAttributes="Bold"  
108 - HorizontalTextAlignment="Center"  
109 - VerticalTextAlignment="Center"  
110 - LineBreakMode="WordWrap"/>  
111 - <Label Grid.Column="5" Text="出库数量" FontAttributes="Bold"  
112 - HorizontalTextAlignment="Center"  
113 - VerticalTextAlignment="Center"  
114 - LineBreakMode="WordWrap"/>  
115 - <Label Grid.Column="6" Text="已扫描数" FontAttributes="Bold"  
116 - HorizontalTextAlignment="Center"  
117 - VerticalTextAlignment="Center"  
118 - LineBreakMode="WordWrap"/> 107 + <!-- 待出库明细表头 -->
  108 + <Grid Grid.Row="1" ColumnDefinitions="*,*,*,*,*,*,*" BackgroundColor="#F2F2F2" IsVisible="{Binding IsPendingVisible}" Padding="8">
  109 + <Label Text="产品名称" FontAttributes="Bold" />
  110 + <Label Grid.Column="1" Text="产品编码" FontAttributes="Bold" />
  111 + <Label Grid.Column="2" Text="规格" FontAttributes="Bold" />
  112 + <Label Grid.Column="3" Text="出库库位" FontAttributes="Bold" />
  113 + <Label Grid.Column="4" Text="生产批号" FontAttributes="Bold" />
  114 + <Label Grid.Column="5" Text="出库数量" FontAttributes="Bold" />
  115 + <Label Grid.Column="6" Text="已扫描数" FontAttributes="Bold" />
119 </Grid> 116 </Grid>
120 117
121 - <!-- 出库单明细列表 --> 118 + <!-- 待入库明细列表 -->
122 <CollectionView Grid.Row="2" 119 <CollectionView Grid.Row="2"
123 - ItemsSource="{Binding PendingList}"  
124 - IsVisible="{Binding IsPendingVisible}"> 120 + ItemsSource="{Binding PendingList}"
  121 + IsVisible="{Binding IsPendingVisible}"
  122 + SelectionMode="Single">
125 <CollectionView.ItemTemplate> 123 <CollectionView.ItemTemplate>
126 <DataTemplate> 124 <DataTemplate>
127 - <Grid ColumnDefinitions="2*,2*,1.5*,2*,2*,1.5*,1.5*"  
128 - Padding="4"  
129 - BackgroundColor="White">  
130 - <Label Text="{Binding Name}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
131 - <Label Grid.Column="1" Text="{Binding Code}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
132 - <Label Grid.Column="2" Text="{Binding Spec}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
133 - <Label Grid.Column="3" Text="{Binding Bin}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
134 - <Label Grid.Column="4" Text="{Binding BatchNo}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
135 - <Label Grid.Column="5" Text="{Binding OutQty}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
136 - <Label Grid.Column="6" Text="{Binding ScannedQty}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/> 125 + <Grid ColumnDefinitions="*,*,*,*,*,*,*" Padding="8" BackgroundColor="White">
  126 + <VisualStateManager.VisualStateGroups>
  127 + <VisualStateGroup Name="CommonStates">
  128 + <VisualState Name="Normal">
  129 + <VisualState.Setters>
  130 + <Setter Property="BackgroundColor" Value="White"/>
  131 + </VisualState.Setters>
  132 + </VisualState>
  133 + <VisualState Name="Selected">
  134 + <VisualState.Setters>
  135 + <Setter Property="BackgroundColor" Value="#CCFFCC"/>
  136 + </VisualState.Setters>
  137 + </VisualState>
  138 + </VisualStateGroup>
  139 + </VisualStateManager.VisualStateGroups>
  140 + <Label Text="{Binding Name}" />
  141 + <Label Grid.Column="1" Text="{Binding MaterialCode}" />
  142 + <Label Grid.Column="2" Text="{Binding Spec}" />
  143 + <Label Grid.Column="3" Text="{Binding Location}" />
  144 + <Label Grid.Column="4" Text="{Binding ProductionBatch}" />
  145 + <Label Grid.Column="5" Text="{Binding OutstockQty}" />
  146 + <Label Grid.Column="6" Text="{Binding Qty}" />
137 </Grid> 147 </Grid>
138 </DataTemplate> 148 </DataTemplate>
139 </CollectionView.ItemTemplate> 149 </CollectionView.ItemTemplate>
140 </CollectionView> 150 </CollectionView>
141 151
142 -  
143 <!-- 扫描明细表头 --> 152 <!-- 扫描明细表头 -->
144 - <Grid Grid.Row="1"  
145 - ColumnDefinitions="40,*,*,*"  
146 - BackgroundColor="#F2F2F2"  
147 - IsVisible="{Binding IsScannedVisible}"  
148 - Padding="4"  
149 - HeightRequest="30">  
150 - <Label Text="选择" FontAttributes="Bold" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" LineBreakMode="NoWrap"/>  
151 - <Label Grid.Column="1" Text="条码" FontAttributes="Bold" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" LineBreakMode="NoWrap"/>  
152 - <Label Grid.Column="2" Text="产品名称" FontAttributes="Bold" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" LineBreakMode="NoWrap"/>  
153 - <Label Grid.Column="3" Text="数量" FontAttributes="Bold" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" LineBreakMode="NoWrap"/> 153 + <Grid Grid.Row="1" ColumnDefinitions="40,*,*,*" BackgroundColor="#F2F2F2" IsVisible="{Binding IsScannedVisible}" Padding="8">
  154 + <Label Text="选择" FontAttributes="Bold" />
  155 + <Label Grid.Column="1" Text="物料名称" FontAttributes="Bold" />
  156 + <Label Grid.Column="2" Text="条码" FontAttributes="Bold" />
  157 + <Label Grid.Column="3" Text="数量" FontAttributes="Bold" />
154 </Grid> 158 </Grid>
155 159
156 <!-- 扫描明细列表 --> 160 <!-- 扫描明细列表 -->
157 <CollectionView Grid.Row="2" 161 <CollectionView Grid.Row="2"
158 - ItemsSource="{Binding ScannedList}"  
159 - IsVisible="{Binding IsScannedVisible}"> 162 + ItemsSource="{Binding ScannedList}"
  163 + IsVisible="{Binding IsScannedVisible}"
  164 + SelectionMode="Single"
  165 + SelectedItem="{Binding SelectedScanItem, Mode=TwoWay}">
160 <CollectionView.ItemTemplate> 166 <CollectionView.ItemTemplate>
161 <DataTemplate> 167 <DataTemplate>
162 - <Grid ColumnDefinitions="40,*,*,*" Padding="4" BackgroundColor="White">  
163 - <CheckBox IsChecked="{Binding IsSelected}" HorizontalOptions="Center" VerticalOptions="Center" />  
164 - <Label Grid.Column="1" Text="{Binding Barcode}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" />  
165 - <Label Grid.Column="2" Text="{Binding Name}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" />  
166 - <Label Grid.Column="3" Text="{Binding Qty}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" /> 168 + <Grid ColumnDefinitions="40,*,*,*" Padding="8" BackgroundColor="White">
  169 + <Grid.Triggers>
  170 + <!-- ScanStatus = true → 绿色 -->
  171 + <DataTrigger TargetType="Grid" Binding="{Binding ScanStatus}" Value="True">
  172 + <Setter Property="BackgroundColor" Value="#E6FFE6" />
  173 + </DataTrigger>
  174 + </Grid.Triggers>
  175 +
  176 + <CheckBox IsChecked="{Binding IsSelected}" />
  177 + <Label Grid.Column="1" Text="{Binding Name}" />
  178 + <Label Grid.Column="2" Text="{Binding Barcode}" />
  179 + <Entry Grid.Column="3"
  180 + Keyboard="Numeric"
  181 + HorizontalTextAlignment="Center"
  182 + WidthRequest="64"
  183 + Completed="OnQtyCompleted"
  184 + Text="{Binding Qty, Mode=TwoWay, Converter={StaticResource IntConverter}}" />
  185 +
167 </Grid> 186 </Grid>
168 </DataTemplate> 187 </DataTemplate>
169 </CollectionView.ItemTemplate> 188 </CollectionView.ItemTemplate>
170 </CollectionView> 189 </CollectionView>
171 - </Grid>  
172 190
  191 + </Grid>
173 192
174 <!-- 底部按钮 --> 193 <!-- 底部按钮 -->
175 <Grid Grid.Row="4" ColumnDefinitions="*,*,*" Padding="16,8" ColumnSpacing="10"> 194 <Grid Grid.Row="4" ColumnDefinitions="*,*,*" Padding="16,8" ColumnSpacing="10">
176 195
177 <!-- 扫描通过 --> 196 <!-- 扫描通过 -->
178 - <HorizontalStackLayout BackgroundColor="#4CAF50"  
179 - Padding="10"  
180 - HorizontalOptions="FillAndExpand"  
181 - VerticalOptions="Center"  
182 - Grid.Column="0">  
183 - <Image Source="pass.png" WidthRequest="20" HeightRequest="20" />  
184 - <Label Text="扫描通过"  
185 - VerticalOptions="Center"  
186 - HorizontalOptions="Center"  
187 - TextColor="White"  
188 - Margin="5,0,0,0" />  
189 - <HorizontalStackLayout.GestureRecognizers> 197 + <Grid BackgroundColor="#4CAF50"
  198 + HorizontalOptions="Fill"
  199 + VerticalOptions="Fill"
  200 + HeightRequest="50">
  201 + <Grid.GestureRecognizers>
190 <TapGestureRecognizer Command="{Binding PassScanCommand}" /> 202 <TapGestureRecognizer Command="{Binding PassScanCommand}" />
191 - </HorizontalStackLayout.GestureRecognizers>  
192 - </HorizontalStackLayout> 203 + </Grid.GestureRecognizers>
  204 + <StackLayout Orientation="Horizontal"
  205 + HorizontalOptions="Center"
  206 + VerticalOptions="Center">
  207 + <Image Source="pass.png" HeightRequest="20" WidthRequest="20" />
  208 + <Label Text="扫描通过"
  209 + Margin="5,0,0,0"
  210 + VerticalOptions="Center"
  211 + TextColor="White" />
  212 + </StackLayout>
  213 + </Grid>
193 214
194 <!-- 取消扫描 --> 215 <!-- 取消扫描 -->
195 - <HorizontalStackLayout BackgroundColor="#F44336"  
196 - Padding="10"  
197 - HorizontalOptions="FillAndExpand"  
198 - VerticalOptions="Center"  
199 - Grid.Column="1">  
200 - <Image Source="cancel.png" WidthRequest="20" HeightRequest="20" />  
201 - <Label Text="取消扫描"  
202 - VerticalOptions="Center"  
203 - HorizontalOptions="Center"  
204 - TextColor="White"  
205 - Margin="5,0,0,0" />  
206 - <HorizontalStackLayout.GestureRecognizers> 216 + <Grid Grid.Column="1"
  217 + BackgroundColor="#F44336"
  218 + HorizontalOptions="Fill"
  219 + VerticalOptions="Fill"
  220 + HeightRequest="50">
  221 + <Grid.GestureRecognizers>
207 <TapGestureRecognizer Command="{Binding CancelScanCommand}" /> 222 <TapGestureRecognizer Command="{Binding CancelScanCommand}" />
208 - </HorizontalStackLayout.GestureRecognizers>  
209 - </HorizontalStackLayout>  
210 -  
211 - <!-- 确认出库 -->  
212 - <HorizontalStackLayout BackgroundColor="#2196F3"  
213 - Padding="10"  
214 - HorizontalOptions="FillAndExpand"  
215 - VerticalOptions="Center"  
216 - Grid.Column="2">  
217 - <Image Source="confirm.png" WidthRequest="20" HeightRequest="20" />  
218 - <Label Text="确认出库"  
219 - VerticalOptions="Center"  
220 - HorizontalOptions="Center"  
221 - TextColor="White"  
222 - Margin="5,0,0,0" />  
223 - <HorizontalStackLayout.GestureRecognizers>  
224 - <TapGestureRecognizer Command="{Binding ConfirmCommand}" />  
225 - </HorizontalStackLayout.GestureRecognizers>  
226 - </HorizontalStackLayout> 223 + </Grid.GestureRecognizers>
  224 + <StackLayout Orientation="Horizontal"
  225 + HorizontalOptions="Center"
  226 + VerticalOptions="Center">
  227 + <Image Source="cancel.png" HeightRequest="20" WidthRequest="20" />
  228 + <Label Text="取消扫描"
  229 + Margin="5,0,0,0"
  230 + VerticalOptions="Center"
  231 + TextColor="White" />
  232 + </StackLayout>
  233 + </Grid>
  234 +
  235 + <!-- 确认入库 -->
  236 + <Grid Grid.Column="2"
  237 + BackgroundColor="#2196F3"
  238 + HorizontalOptions="Fill"
  239 + VerticalOptions="Fill"
  240 + HeightRequest="50">
  241 + <Grid.GestureRecognizers>
  242 + <TapGestureRecognizer Tapped="OnConfirmClicked" />
  243 + </Grid.GestureRecognizers>
  244 + <StackLayout Orientation="Horizontal"
  245 + HorizontalOptions="Center"
  246 + VerticalOptions="Center">
  247 + <Image Source="confirm.png" HeightRequest="20" WidthRequest="20" />
  248 + <Label Text="确认出库"
  249 + Margin="5,0,0,0"
  250 + VerticalOptions="Center"
  251 + TextColor="White" />
  252 + </StackLayout>
  253 + </Grid>
227 254
228 </Grid> 255 </Grid>
229 256
1 using IndustrialControl.Services; 1 using IndustrialControl.Services;
2 using IndustrialControl.ViewModels; 2 using IndustrialControl.ViewModels;
3 3
4 -namespace IndustrialControl.Pages 4 +namespace IndustrialControl.Pages;
  5 +
  6 +[QueryProperty(nameof(OutstockId), "outstockId")]
  7 +[QueryProperty(nameof(OutstockNo), "outstockNo")]
  8 +[QueryProperty(nameof(OrderType), "orderType")]
  9 +[QueryProperty(nameof(OrderTypeName), "orderTypeName")]
  10 +[QueryProperty(nameof(RequisitionMaterialNo), "requisitionMaterialNo")]
  11 +[QueryProperty(nameof(ReturnNo), "returnNo")]
  12 +[QueryProperty(nameof(DeliveryNo), "deliveryNo")]
  13 +[QueryProperty(nameof(CreatedTime), "createdTime")]
  14 +public partial class OutboundFinishedPage : ContentPage
5 { 15 {
6 - [QueryProperty(nameof(OutstockId), "outstockId")]  
7 - [QueryProperty(nameof(OutstockNo), "outstockNo")]  
8 - [QueryProperty(nameof(OrderType), "orderType")]  
9 - [QueryProperty(nameof(OrderTypeName), "orderTypeName")]  
10 - [QueryProperty(nameof(PurchaseNo), "purchaseNo")]  
11 - [QueryProperty(nameof(SupplierName), "supplierName")]  
12 - [QueryProperty(nameof(CreatedTime), "createdTime")]  
13 - public partial class OutboundFinishedPage : ContentPage 16 + private readonly ScanService _scanSvc;
  17 + private readonly OutboundFinishedViewModel _vm;
  18 + public string? OutstockId { get; set; }
  19 + public string? OutstockNo { get; set; }
  20 + public string? OrderType { get; set; }
  21 + public string? OrderTypeName { get; set; }
  22 + public string? RequisitionMaterialNo { get; set; }
  23 + public string? ReturnNo { get; set; }
  24 + public string? DeliveryNo { get; set; }
  25 + public string? CreatedTime { get; set; }
  26 + private readonly IDialogService _dialogs;
  27 +
  28 + public OutboundFinishedPage(OutboundFinishedViewModel vm, ScanService scanSvc, IDialogService dialogs)
14 { 29 {
15 - private readonly ScanService _scanSvc;  
16 - private readonly OutboundFinishedViewModel _vm;  
17 - public string? OutstockId { get; set; }  
18 - public string? OutstockNo { get; set; }  
19 - public string? OrderType { get; set; }  
20 - public string? OrderTypeName { get; set; }  
21 - public string? PurchaseNo { get; set; }  
22 - public string? SupplierName { get; set; }  
23 - public string? CreatedTime { get; set; }  
24 -  
25 -  
26 - public OutboundFinishedPage(OutboundFinishedViewModel vm, ScanService scanSvc)  
27 - {  
28 - InitializeComponent();  
29 - BindingContext = vm;  
30 - _scanSvc = scanSvc;  
31 - _vm = vm;  
32 - _scanSvc.Prefix = null; // 例如 "}q" 之类的前缀;没有就留 null  
33 - // _scanSvc.Suffix = "\n"; // 如果设备会附带换行,可去掉;没有就设 null  
34 - //_scanSvc.DebounceMs = 250;  
35 - _scanSvc.Suffix = null; // 先关掉  
36 - _scanSvc.DebounceMs = 0; // 先关掉  
37 - } 30 + InitializeComponent();
  31 + BindingContext = vm;
  32 + _scanSvc = scanSvc;
  33 + _vm = vm;
  34 + _dialogs = dialogs;
  35 + // 可选:配置前后缀与防抖
  36 + _scanSvc.Prefix = null; // 例如 "}q" 之类的前缀;没有就留 null
  37 + // _scanSvc.Suffix = "\n"; // 如果设备会附带换行,可去掉;没有就设 null
  38 + //_scanSvc.DebounceMs = 250;
  39 + _scanSvc.Suffix = null; // 先关掉
  40 + _scanSvc.DebounceMs = 0; // 先关掉
  41 +
  42 + }
  43 +
  44 + protected override async void OnAppearing()
  45 + {
  46 + base.OnAppearing();
38 47
39 - protected override async void OnAppearing() 48 + // ✅ 用搜索页带过来的基础信息初始化页面,并拉取两张表
  49 + if (!string.IsNullOrWhiteSpace(OutstockId))
40 { 50 {
41 - base.OnAppearing();  
42 -  
43 - // ✅ 用搜索页带过来的基础信息初始化页面,并拉取两张表  
44 - if (!string.IsNullOrWhiteSpace(OutstockId))  
45 - {  
46 - await _vm.InitializeFromSearchAsync(  
47 - outstockId: OutstockId ?? "",  
48 - outstockNo: OutstockNo ?? "",  
49 - orderType: OrderType ?? "",  
50 - orderTypeName: OrderTypeName ?? "",  
51 - purchaseNo: PurchaseNo ?? "",  
52 - supplierName: SupplierName ?? "",  
53 - createdTime: CreatedTime ?? ""  
54 - );  
55 - }  
56 -  
57 - _scanSvc.Scanned += OnScanned;  
58 - _scanSvc.StartListening();  
59 - _scanSvc.Attach(ScanEntry);  
60 - ScanEntry.Focus(); 51 + await _vm.InitializeFromSearchAsync(
  52 + outstockId: OutstockId ?? "",
  53 + outstockNo: OutstockNo ?? "",
  54 + orderType: OrderType ?? "",
  55 + orderTypeName: OrderTypeName ?? "",
  56 + requisitionMaterialNo: RequisitionMaterialNo ?? "",
  57 + returnNo: ReturnNo ?? "",
  58 + deliveryNo: DeliveryNo ?? "",
  59 + createdTime: CreatedTime ?? ""
  60 + );
61 } 61 }
62 62
63 - protected override void OnDisappearing()  
64 - {  
65 - // 退出页面即注销(防止多个程序/页面抢处理)  
66 - _scanSvc.Scanned -= OnScanned;  
67 - _scanSvc.StopListening(); 63 + _scanSvc.Scanned += OnScanned;
  64 + _scanSvc.StartListening();
  65 + _scanSvc.Attach(ScanEntry);
  66 + ScanEntry.Focus();
  67 + }
68 68
69 - base.OnDisappearing();  
70 - }  
71 69
72 - private void OnScanned(string data, string type)  
73 - {  
74 - MainThread.BeginInvokeOnMainThread(async () =>  
75 - {  
76 - // 常见处理:自动填入单号/条码并触发查询或加入明细  
77 - _vm.ScanCode = data;  
78 -  
79 - // 你原本的逻辑:若识别到是订单号 → 查询;若是包装码 → 加入列表等  
80 - await _vm.HandleScannedAsync(data, type);  
81 - });  
82 - } 70 + /// <summary>
  71 + /// 清空扫描记录
  72 + /// </summary>
  73 + void OnClearClicked(object sender, EventArgs e)
  74 + {
  75 + _vm.ClearScan();
  76 + ScanEntry.Text = string.Empty;
  77 + ScanEntry.Focus();
  78 + }
83 79
84 - /// <summary>  
85 - /// 清空扫描记录  
86 - /// </summary>  
87 - void OnClearClicked(object sender, EventArgs e)  
88 - {  
89 - _vm.ClearScan();  
90 - ScanEntry.Text = string.Empty;  
91 - ScanEntry.Focus();  
92 - } 80 + protected override void OnDisappearing()
  81 + {
  82 + // 退出页面即注销(防止多个程序/页面抢处理)
  83 + _scanSvc.Scanned -= OnScanned;
  84 + _scanSvc.StopListening();
93 85
94 - /// <summary>  
95 - /// 预留摄像头扫码  
96 - /// </summary>  
97 - async void OnScanClicked(object sender, EventArgs e)  
98 - {  
99 - await DisplayAlert("提示", "此按钮预留摄像头扫码;硬件扫描直接扣扳机。", "确定");  
100 - } 86 + base.OnDisappearing();
  87 + }
101 88
102 - /// <summary>  
103 - /// 点击“出库单明细”Tab  
104 - /// </summary>  
105 - void OnPendingTabClicked(object sender, EventArgs e) 89 + private void OnScanned(string data, string type)
  90 + {
  91 + MainThread.BeginInvokeOnMainThread(async () =>
106 { 92 {
107 - _vm.ShowPendingCommand.Execute(null);  
108 - } 93 + // 常见处理:自动填入单号/条码并触发查询或加入明细
  94 + _vm.ScanCode = data;
  95 +
  96 + // 你原本的逻辑:若识别到是订单号 → 查询;若是包装码 → 加入列表等
  97 + await _vm.HandleScannedAsync(data, type);
  98 + });
  99 + }
109 100
110 - /// <summary>  
111 - /// 点击“扫描明细”Tab  
112 - /// </summary>  
113 - void OnScannedTabClicked(object sender, EventArgs e)  
114 - {  
115 - _vm.ShowScannedCommand.Execute(null);  
116 - }  
117 101
118 - /// <summary>  
119 - /// 扫描通过  
120 - /// </summary>  
121 - void OnPassScanClicked(object sender, EventArgs e) 102 + /// <summary>
  103 + /// 确认入库按钮点击
  104 + /// </summary>
  105 + async void OnConfirmClicked(object sender, EventArgs e)
  106 + {
  107 + var ok = await _vm.ConfirmOutboundAsync();
  108 + if (ok)
122 { 109 {
123 - _vm.PassScanCommand.Execute(null);  
124 - } 110 + await DisplayAlert("提示", "入库成功", "确定");
  111 + _vm.ClearAll();
125 112
126 - /// <summary>  
127 - /// 取消扫描  
128 - /// </summary>  
129 - void OnCancelScanClicked(object sender, EventArgs e) 113 + // ✅ 返回到工单查询页面(InboundFinishedSearchPage)
  114 + await Shell.Current.GoToAsync($"//{nameof(OutboundFinishedSearchPage)}");
  115 + }
  116 + else
130 { 117 {
131 - _vm.CancelScanCommand.Execute(null); 118 + await DisplayAlert("提示", "入库失败,请检查数据", "确定");
132 } 119 }
  120 + }
  121 +
133 122
134 - /// <summary>  
135 - /// 确认出库  
136 - /// </summary>  
137 - async void OnConfirmClicked(object sender, EventArgs e) 123 + private async void OnBinTapped(object? sender, TappedEventArgs e)
  124 + {
  125 + var bindable = sender as BindableObject;
  126 + var row = bindable?.BindingContext;
  127 + if (row == null) return;
  128 +
  129 + var type = row.GetType();
  130 + var currentBin = type.GetProperty("Location")?.GetValue(row)?.ToString();
  131 +
  132 + // 1) 打开公共组件选择库位(你已完成的组件)
  133 + var picked = await BinPickerPage.ShowAsync(currentBin);
  134 + if (picked == null) return;
  135 +
  136 + // 2) 调用 VM:带上行对象 + 选中的 BinInfo,内部会调接口 & 回填行
  137 + await _vm.UpdateRowLocationAsync(row, picked);
  138 + }
  139 +
  140 + private async void OnQtyCompleted(object sender, EventArgs e)
  141 + {
  142 + if (sender is not Entry entry) return;
  143 + if (entry.BindingContext is not IndustrialControl.ViewModels.OutScannedItem row) return;
  144 +
  145 + // 只看 ScanStatus:未通过则不提交
  146 + if (!row.ScanStatus)
138 { 147 {
139 - _vm.ConfirmCommand.Execute(null); 148 + await DisplayAlert("提示", "该行尚未扫描通过,不能修改数量。", "确定");
  149 + return;
140 } 150 }
  151 +
  152 + await _vm.UpdateQuantityForRowAsync(row);
141 } 153 }
  154 +
142 } 155 }
1 <?xml version="1.0" encoding="utf-8" ?> 1 <?xml version="1.0" encoding="utf-8" ?>
2 -<ContentPage x:Name="Page" 2 +<ContentPage
  3 + x:Class="IndustrialControl.Pages.OutboundFinishedSearchPage"
3 xmlns="http://schemas.microsoft.com/dotnet/2021/maui" 4 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> 5 + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  6 + x:Name="Page"
  7 + Shell.NavBarIsVisible="True">
14 8
15 - <Grid RowDefinitions="Auto,*,Auto" Padding="16" BackgroundColor="#F6F7FB"> 9 + <ScrollView>
  10 + <VerticalStackLayout Spacing="12" Padding="12">
16 11
17 - <!-- 顶部:输入条件(第0行) -->  
18 - <VerticalStackLayout Grid.Row="0" Spacing="10"> 12 + <!-- 查询区域 -->
19 <Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,Auto" ColumnSpacing="8" RowSpacing="8"> 13 <Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,Auto" ColumnSpacing="8" RowSpacing="8">
20 <Entry x:Name="OrderEntry" 14 <Entry x:Name="OrderEntry"
21 Grid.Row="0" Grid.Column="0" 15 Grid.Row="0" Grid.Column="0"
22 - Placeholder="请输入出库单条码"  
23 - VerticalOptions="Center"  
24 - BackgroundColor="White" 16 + Placeholder="请扫描输入出库单条码"
25 Text="{Binding SearchOrderNo}" /> 17 Text="{Binding SearchOrderNo}" />
26 18
27 -  
28 - <!-- 开始日期 -->  
29 - <DatePicker Grid.Row="1" Grid.Column="0"  
30 - Date="{Binding StartDate}"  
31 - MinimumDate="2000-01-01"  
32 - MaximumDate="{Binding EndDate}" />  
33 -  
34 - <!-- 结束日期 -->  
35 - <DatePicker Grid.Row="1" Grid.Column="1"  
36 - Date="{Binding EndDate}"  
37 - MinimumDate="{Binding StartDate}" />  
38 - <Button Grid.Row="1" Grid.Column="1" 19 + <Button Grid.Row="0" Grid.Column="1"
39 Text="查询" 20 Text="查询"
40 Command="{Binding SearchCommand}" /> 21 Command="{Binding SearchCommand}" />
  22 +
  23 + <Grid Grid.Row="1" Grid.ColumnSpan="2" ColumnDefinitions="Auto,*,Auto,*" ColumnSpacing="8">
  24 + <Label Grid.Column="0" Text="开始:" VerticalTextAlignment="Center"/>
  25 + <DatePicker Grid.Column="1" Date="{Binding StartDate}" />
  26 + <Label Grid.Column="2" Text="结束:" VerticalTextAlignment="Center"/>
  27 + <DatePicker Grid.Column="3" Date="{Binding EndDate}" />
  28 + </Grid>
41 </Grid> 29 </Grid>
42 - </VerticalStackLayout>  
43 30
44 - <!-- 中部:结果列表(第1行) -->  
45 - <CollectionView Grid.Row="1"  
46 - ItemsSource="{Binding Orders}"  
47 - SelectionMode="Single"  
48 - SelectedItem="{Binding SelectedOrder, Mode=TwoWay}">  
49 - <CollectionView.ItemTemplate>  
50 - <DataTemplate>  
51 - <Frame Margin="0,8,0,0" Padding="12" HasShadow="True" CornerRadius="10">  
52 - <!-- ⭐ 点击整卡片触发命令 -->  
53 - <Frame.GestureRecognizers>  
54 - <TapGestureRecognizer  
55 - Command="{Binding BindingContext.GoOutboundCommand, Source={x:Reference Page}}"  
56 - CommandParameter="{Binding .}" />  
57 - </Frame.GestureRecognizers>  
58 - <Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="Auto,*" ColumnSpacing="8">  
59 - <Label Grid.Row="0" Grid.Column="0" Text="出库单号:" FontAttributes="Bold"/>  
60 - <Label Grid.Row="0" Grid.Column="1" Text="{Binding outstockNo}"/> 31 + <!-- 列表区域 -->
  32 + <CollectionView Grid.Row="1"
  33 + ItemsSource="{Binding Orders}"
  34 + SelectionMode="Single"
  35 + SelectedItem="{Binding SelectedOrder, Mode=TwoWay}">
  36 + <CollectionView.ItemTemplate>
  37 + <DataTemplate>
  38 + <Frame Margin="0,8,0,0" Padding="12" HasShadow="True" CornerRadius="10">
  39 + <!-- 点击整卡片:直接调用 VM 的 GoOutboundCommand,并把当前项作为参数 -->
  40 + <Frame.GestureRecognizers>
  41 + <TapGestureRecognizer
  42 + Command="{Binding BindingContext.GoOutboundCommand, Source={x:Reference Page}}"
  43 + CommandParameter="{Binding .}" />
  44 + </Frame.GestureRecognizers>
  45 +
  46 + <Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto"
  47 + ColumnDefinitions="Auto,*" ColumnSpacing="8">
  48 + <Label Grid.Row="0" Grid.Column="0" Text="出库单号:" FontAttributes="Bold"/>
  49 + <Label Grid.Row="0" Grid.Column="1" Text="{Binding outstockNo}"/>
61 50
62 - <Label Grid.Row="1" Grid.Column="0" Text="出库单类型:" FontAttributes="Bold"/>  
63 - <Label Grid.Row="1" Grid.Column="1" Text="{Binding orderTypeName}" /> 51 + <Label Grid.Row="1" Grid.Column="0" Text="出库单类型:" FontAttributes="Bold"/>
  52 + <Label Grid.Row="1" Grid.Column="1" Text="{Binding orderTypeName}" />
64 53
65 - <Label Grid.Row="2" Grid.Column="0" Text="关联发货号:" FontAttributes="Bold"/>  
66 - <Label Grid.Row="2" Grid.Column="1" Text="{Binding deliveryNo}" /> 54 + <Label Grid.Row="2" Grid.Column="0" Text="关联发货号:" FontAttributes="Bold"/>
  55 + <Label Grid.Row="2" Grid.Column="1" Text="{Binding deliveryNo}" />
67 56
68 - <Label Grid.Row="3" Grid.Column="0" Text="关联销售号:" FontAttributes="Bold"/>  
69 - <Label Grid.Row="3" Grid.Column="1" Text="{Binding arrivalNo}" /> 57 + <Label Grid.Row="3" Grid.Column="0" Text="关联销售号:" FontAttributes="Bold"/>
  58 + <Label Grid.Row="3" Grid.Column="1" Text="{Binding arrivalNo}" />
70 59
71 - <Label Grid.Row="4" Grid.Column="0" Text="创建日期:" FontAttributes="Bold"/>  
72 - <Label Grid.Row="4" Grid.Column="1" Text="{Binding createdTime}" />  
73 - </Grid>  
74 - </Frame>  
75 - </DataTemplate>  
76 - </CollectionView.ItemTemplate>  
77 - </CollectionView> 60 + <Label Grid.Row="4" Grid.Column="0" Text="创建日期:" FontAttributes="Bold"/>
  61 + <Label Grid.Row="4" Grid.Column="1" Text="{Binding createdTime}" />
  62 + </Grid>
  63 + </Frame>
  64 + </DataTemplate>
  65 + </CollectionView.ItemTemplate>
  66 + </CollectionView>
78 67
79 - <!-- 底部:操作(第2行) -->  
80 - <Grid Grid.Row="2" ColumnDefinitions="*,Auto" Padding="0,8,0,0">  
81 - <Label Text="{Binding Orders.Count, StringFormat='共 {0} 条'}"  
82 - VerticalTextAlignment="Center" />  
83 - </Grid>  
84 - </Grid> 68 + </VerticalStackLayout>
  69 + </ScrollView>
85 </ContentPage> 70 </ContentPage>
1 -using System.Threading;  
2 using IndustrialControl.Services; 1 using IndustrialControl.Services;
3 using IndustrialControl.ViewModels; 2 using IndustrialControl.ViewModels;
4 namespace IndustrialControl.Pages; 3 namespace IndustrialControl.Pages;
@@ -10,7 +9,7 @@ public partial class OutboundFinishedSearchPage : ContentPage @@ -10,7 +9,7 @@ public partial class OutboundFinishedSearchPage : ContentPage
10 public OutboundFinishedSearchPage(OutboundFinishedSearchViewModel vm, ScanService scanSvc) 9 public OutboundFinishedSearchPage(OutboundFinishedSearchViewModel vm, ScanService scanSvc)
11 { 10 {
12 _vm = vm; 11 _vm = vm;
13 - 12 +
14 BindingContext = vm; 13 BindingContext = vm;
15 _scanSvc = scanSvc; 14 _scanSvc = scanSvc;
16 InitializeComponent(); 15 InitializeComponent();
@@ -30,6 +29,8 @@ public partial class OutboundFinishedSearchPage : ContentPage @@ -30,6 +29,8 @@ public partial class OutboundFinishedSearchPage : ContentPage
30 //键盘输入 29 //键盘输入
31 _scanSvc.Attach(OrderEntry); 30 _scanSvc.Attach(OrderEntry);
32 OrderEntry.Focus(); 31 OrderEntry.Focus();
  32 +
  33 +
33 } 34 }
34 35
35 /// <summary> 36 /// <summary>
@@ -59,4 +60,5 @@ public partial class OutboundFinishedSearchPage : ContentPage @@ -59,4 +60,5 @@ public partial class OutboundFinishedSearchPage : ContentPage
59 }); 60 });
60 } 61 }
61 62
  63 +
62 } 64 }
1 <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" 1 <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
2 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 2 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
3 x:Class="IndustrialControl.Pages.OutboundMaterialPage" 3 x:Class="IndustrialControl.Pages.OutboundMaterialPage"
4 - BackgroundColor="White"> 4 + BackgroundColor="White" Shell.NavBarIsVisible="True">
5 5
6 <Grid RowDefinitions="Auto,Auto,Auto,*,Auto"> 6 <Grid RowDefinitions="Auto,Auto,Auto,*,Auto">
7 7
8 <!-- 顶部蓝色标题栏 --> 8 <!-- 顶部蓝色标题栏 -->
9 <Grid BackgroundColor="#007BFF" HeightRequest="60" Padding="16,0"> 9 <Grid BackgroundColor="#007BFF" HeightRequest="60" Padding="16,0">
10 - <Label Text="仓储管理系统"  
11 - VerticalOptions="Center" 10 + <Label Text="仓储管理系统"
  11 + VerticalOptions="Center"
12 TextColor="White" 12 TextColor="White"
13 - FontSize="18" 13 + FontSize="18"
14 FontAttributes="Bold"/> 14 FontAttributes="Bold"/>
15 </Grid> 15 </Grid>
16 16
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"  
20 - Placeholder="请扫描出库单/产品/包装条码" 19 + <Entry x:Name="ScanEntry"
  20 + Placeholder="请扫描输入出库单条码"
21 FontSize="14" 21 FontSize="14"
22 VerticalOptions="Center" 22 VerticalOptions="Center"
23 BackgroundColor="White" 23 BackgroundColor="White"
24 HeightRequest="40" 24 HeightRequest="40"
25 Text="{Binding ScanCode}" /> 25 Text="{Binding ScanCode}" />
26 - <ImageButton Grid.Column="1"  
27 - Source="scan.png"  
28 - BackgroundColor="#E6F2FF"  
29 - CornerRadius="4"  
30 - Padding="10"  
31 - Clicked="OnScanClicked"/> 26 +
32 </Grid> 27 </Grid>
33 28
34 <!-- 基础信息 --> 29 <!-- 基础信息 -->
35 - <Frame Grid.Row="2" Margin="16,0" Padding="8" BorderColor="#CCCCCC" BackgroundColor="White">  
36 - <Grid RowDefinitions="Auto,Auto,Auto,Auto" ColumnDefinitions="Auto,*" ColumnSpacing="8" RowSpacing="6">  
37 -  
38 - <!-- 出库单号 -->  
39 - <Label Grid.Row="0" Grid.Column="0" Text="出库单号:" FontAttributes="Bold"/>  
40 - <Label Grid.Row="0" Grid.Column="1" Text="{Binding OrderNo}" LineBreakMode="CharacterWrap"/>  
41 -  
42 - <!-- 发货单号 + 客户 -->  
43 - <Label Grid.Row="1" Grid.Column="0" Text="发货单号:" FontAttributes="Bold"/>  
44 - <Label Grid.Row="1" Grid.Column="1" Text="{Binding DeliveryNo}" LineBreakMode="CharacterWrap"/>  
45 - <Label Grid.Row="1" Grid.Column="2" Text="客户:" FontAttributes="Bold"/>  
46 - <Label Grid.Row="1" Grid.Column="3" Text="{Binding CustomerName}" LineBreakMode="CharacterWrap"/>  
47 -  
48 - <!-- 发货时间 + 关联销售单 -->  
49 - <Label Grid.Row="2" Grid.Column="0" Text="发货时间:" FontAttributes="Bold"/>  
50 - <Label Grid.Row="2" Grid.Column="1" Text="{Binding DeliveryTime}" LineBreakMode="CharacterWrap"/>  
51 - <Label Grid.Row="2" Grid.Column="2" Text="关联销售单:" FontAttributes="Bold"/>  
52 - <Label Grid.Row="2" Grid.Column="3" Text="{Binding LinkedOrderNo}" LineBreakMode="CharacterWrap"/>  
53 -  
54 - <!-- 发货单备注 -->  
55 - <Label Grid.Row="3" Grid.Column="0" Text="发货单备注:" FontAttributes="Bold"/>  
56 - <Label Grid.Row="3" Grid.Column="1" Text="{Binding Remark}" LineBreakMode="WordWrap"/> 30 + <Frame Grid.Row="2" Margin="8,0" Padding="8" BorderColor="#CCCCCC" BackgroundColor="White">
  31 + <Grid RowDefinitions="Auto,Auto" ColumnDefinitions="*,*" ColumnSpacing="16" RowSpacing="6">
  32 +
  33 + <!-- 第一行左:出库单号 -->
  34 + <Label Grid.Row="0" Grid.Column="0" FontSize="13" LineBreakMode="TailTruncation" MaxLines="1">
  35 + <Label.FormattedText>
  36 + <FormattedString>
  37 + <Span Text="出库单号:" FontAttributes="Bold"/>
  38 + <Span Text="{Binding OutstockNo}"/>
  39 + </FormattedString>
  40 + </Label.FormattedText>
  41 + </Label>
  42 +
  43 + <!-- 第一行右:领料单号 -->
  44 + <Label Grid.Row="1" Grid.Column="0" FontSize="13" LineBreakMode="TailTruncation" MaxLines="1">
  45 + <Label.FormattedText>
  46 + <FormattedString>
  47 + <Span Text="领料单号:" FontAttributes="Bold"/>
  48 + <Span Text="{Binding RequisitionMaterialNo}"/>
  49 + </FormattedString>
  50 + </Label.FormattedText>
  51 + </Label>
  52 +
  53 + <!-- 第二行左:工单号 -->
  54 + <Label Grid.Row="1" Grid.Column="1" FontSize="13" LineBreakMode="TailTruncation" MaxLines="1">
  55 + <Label.FormattedText>
  56 + <FormattedString>
  57 + <Span Text="工单号:" FontAttributes="Bold"/>
  58 + <Span Text="{Binding WorkOrderNo}"/>
  59 + </FormattedString>
  60 + </Label.FormattedText>
  61 + </Label>
  62 +
  63 + <!-- 第二行右:出库单备注 -->
  64 + <Label Grid.Row="2" Grid.Column="0" FontSize="13" LineBreakMode="TailTruncation" MaxLines="1">
  65 + <Label.FormattedText>
  66 + <FormattedString>
  67 + <Span Text="出库单备注:" FontAttributes="Bold"/>
  68 + <Span Text="{Binding SupplierName}"/>
  69 + </FormattedString>
  70 + </Label.FormattedText>
  71 + </Label>
  72 +
57 </Grid> 73 </Grid>
58 </Frame> 74 </Frame>
59 75
60 - <!-- Tab + 列表 -->  
61 - <Grid Grid.Row="3" RowDefinitions="Auto,Auto,*">  
62 -  
63 - <!-- Tab --> 76 + <!-- Tab切换 -->
  77 + <Grid Grid.Row="3" RowDefinitions="Auto,Auto,*" Margin="0">
64 <Grid ColumnDefinitions="*,*" BackgroundColor="White"> 78 <Grid ColumnDefinitions="*,*" BackgroundColor="White">
65 - <Button Text="出库单明细"  
66 - BackgroundColor="{Binding PendingTabColor}"  
67 - TextColor="{Binding PendingTextColor}"  
68 - Clicked="OnPendingTabClicked"/>  
69 - <Button Text="扫描明细" 79 + <Button Text="出库单明细"
  80 + Command="{Binding ShowPendingCommand}"
  81 + BackgroundColor="{Binding PendingTabColor}"
  82 + TextColor="{Binding PendingTextColor}" />
  83 + <Button Text="扫描明细"
70 Grid.Column="1" 84 Grid.Column="1"
71 - BackgroundColor="{Binding ScannedTabColor}"  
72 - TextColor="{Binding ScannedTextColor}"  
73 - Clicked="OnScannedTabClicked"/> 85 + Command="{Binding ShowScannedCommand}"
  86 + BackgroundColor="{Binding ScannedTabColor}"
  87 + TextColor="{Binding ScannedTextColor}" />
74 </Grid> 88 </Grid>
75 89
76 - <!-- 出库单明细表头 -->  
77 - <Grid Grid.Row="1"  
78 - ColumnDefinitions="2*,2*,1.5*,1.5*,1.5*,1.5*,1.5*"  
79 - BackgroundColor="#F2F2F2"  
80 - IsVisible="{Binding IsPendingVisible}"  
81 - Padding="4">  
82 - <Label Text="产品名称" FontAttributes="Bold" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
83 - <Label Grid.Column="1" Text="产品编码" FontAttributes="Bold" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
84 - <Label Grid.Column="2" Text="规格" FontAttributes="Bold" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
85 - <Label Grid.Column="3" Text="出库库位" FontAttributes="Bold" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
86 - <Label Grid.Column="4" Text="批次号" FontAttributes="Bold" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
87 - <Label Grid.Column="5" Text="出库数量" FontAttributes="Bold" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
88 - <Label Grid.Column="6" Text="已扫描数" FontAttributes="Bold" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/> 90 + <!-- 待入库明细表头 -->
  91 + <Grid Grid.Row="1" ColumnDefinitions="*,*,*,*,*,*,*" BackgroundColor="#F2F2F2" IsVisible="{Binding IsPendingVisible}" Padding="8">
  92 + <Label Text="物料名称" FontAttributes="Bold" />
  93 + <Label Grid.Column="1" Text="物料编码" FontAttributes="Bold" />
  94 + <Label Grid.Column="2" Text="规格" FontAttributes="Bold" />
  95 + <Label Grid.Column="3" Text="出库库位" FontAttributes="Bold" />
  96 + <Label Grid.Column="4" Text="批次号" FontAttributes="Bold" />
  97 + <Label Grid.Column="5" Text="出库数量" FontAttributes="Bold" />
  98 + <Label Grid.Column="6" Text="已扫描数量" FontAttributes="Bold" />
89 </Grid> 99 </Grid>
90 100
91 - <!-- 出库单明细列表 --> 101 + <!-- 待入库明细列表 -->
92 <CollectionView Grid.Row="2" 102 <CollectionView Grid.Row="2"
93 ItemsSource="{Binding PendingList}" 103 ItemsSource="{Binding PendingList}"
94 - IsVisible="{Binding IsPendingVisible}"> 104 + IsVisible="{Binding IsPendingVisible}"
  105 + SelectionMode="Single">
95 <CollectionView.ItemTemplate> 106 <CollectionView.ItemTemplate>
96 <DataTemplate> 107 <DataTemplate>
97 - <Grid ColumnDefinitions="2*,2*,1.5*,1.5*,1.5*,1.5*,1.5*" Padding="4" BackgroundColor="White">  
98 - <Label Text="{Binding Name}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
99 - <Label Grid.Column="1" Text="{Binding Code}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
100 - <Label Grid.Column="2" Text="{Binding Spec}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
101 - <Label Grid.Column="3" Text="{Binding Bin}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
102 - <Label Grid.Column="4" Text="{Binding BatchNo}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
103 - <Label Grid.Column="5" Text="{Binding OutQty}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
104 - <Label Grid.Column="6" Text="{Binding ScannedQty}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/> 108 + <Grid ColumnDefinitions="*,*,*,*,*,*,*" Padding="8" BackgroundColor="White">
  109 + <VisualStateManager.VisualStateGroups>
  110 + <VisualStateGroup Name="CommonStates">
  111 + <VisualState Name="Normal">
  112 + <VisualState.Setters>
  113 + <Setter Property="BackgroundColor" Value="White"/>
  114 + </VisualState.Setters>
  115 + </VisualState>
  116 + <VisualState Name="Selected">
  117 + <VisualState.Setters>
  118 + <Setter Property="BackgroundColor" Value="#CCFFCC"/>
  119 + </VisualState.Setters>
  120 + </VisualState>
  121 + </VisualStateGroup>
  122 + </VisualStateManager.VisualStateGroups>
  123 + <Label Text="{Binding Name}" />
  124 + <Label Grid.Column="1" Text="{Binding MaterialCode}" />
  125 + <Label Grid.Column="2" Text="{Binding Spec}" />
  126 + <Label Grid.Column="3" Text="{Binding Location}" />
  127 + <Label Grid.Column="4" Text="{Binding StockBatch}" />
  128 + <Label Grid.Column="5" Text="{Binding OutstockQty}" />
  129 + <Label Grid.Column="6" Text="{Binding Qty}" />
105 </Grid> 130 </Grid>
106 </DataTemplate> 131 </DataTemplate>
107 </CollectionView.ItemTemplate> 132 </CollectionView.ItemTemplate>
108 </CollectionView> 133 </CollectionView>
109 134
110 <!-- 扫描明细表头 --> 135 <!-- 扫描明细表头 -->
111 - <Grid Grid.Row="1"  
112 - ColumnDefinitions="40,*,*,*"  
113 - BackgroundColor="#F2F2F2"  
114 - IsVisible="{Binding IsScannedVisible}"  
115 - Padding="4">  
116 - <Label Text="选择" FontAttributes="Bold" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
117 - <Label Grid.Column="1" Text="条码" FontAttributes="Bold" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
118 - <Label Grid.Column="2" Text="物料名称" FontAttributes="Bold" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
119 - <Label Grid.Column="3" Text="数量" FontAttributes="Bold" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/> 136 + <Grid Grid.Row="1" ColumnDefinitions="40,*,*,*" BackgroundColor="#F2F2F2" IsVisible="{Binding IsScannedVisible}" Padding="8">
  137 + <Label Text="选择" FontAttributes="Bold" />
  138 + <Label Grid.Column="1" Text="物料名称" FontAttributes="Bold" />
  139 + <Label Grid.Column="2" Text="条码" FontAttributes="Bold" />
  140 + <Label Grid.Column="3" Text="数量" FontAttributes="Bold" />
120 </Grid> 141 </Grid>
121 142
122 <!-- 扫描明细列表 --> 143 <!-- 扫描明细列表 -->
123 <CollectionView Grid.Row="2" 144 <CollectionView Grid.Row="2"
124 - ItemsSource="{Binding ScannedList}"  
125 - IsVisible="{Binding IsScannedVisible}"> 145 + ItemsSource="{Binding ScannedList}"
  146 + IsVisible="{Binding IsScannedVisible}"
  147 + SelectionMode="Single"
  148 + SelectedItem="{Binding SelectedScanItem, Mode=TwoWay}">
126 <CollectionView.ItemTemplate> 149 <CollectionView.ItemTemplate>
127 <DataTemplate> 150 <DataTemplate>
128 - <Grid ColumnDefinitions="40,*,*,*" Padding="4" BackgroundColor="White">  
129 - <CheckBox IsChecked="{Binding IsSelected}" HorizontalOptions="Center" VerticalOptions="Center"/>  
130 - <Label Grid.Column="1" Text="{Binding Barcode}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
131 - <Label Grid.Column="2" Text="{Binding Name}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/>  
132 - <Label Grid.Column="3" Text="{Binding Qty}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"/> 151 + <Grid ColumnDefinitions="40,*,*,*" Padding="8" BackgroundColor="White">
  152 + <Grid.Triggers>
  153 + <!-- ScanStatus = true → 绿色 -->
  154 + <DataTrigger TargetType="Grid" Binding="{Binding ScanStatus}" Value="True">
  155 + <Setter Property="BackgroundColor" Value="#E6FFE6" />
  156 + </DataTrigger>
  157 + </Grid.Triggers>
  158 +
  159 + <CheckBox IsChecked="{Binding IsSelected}" />
  160 + <Label Grid.Column="1" Text="{Binding Name}" />
  161 + <Label Grid.Column="2" Text="{Binding Barcode}" />
  162 + <Entry Grid.Column="3"
  163 + Keyboard="Numeric"
  164 + HorizontalTextAlignment="Center"
  165 + WidthRequest="64"
  166 + Completed="OnQtyCompleted"
  167 + Text="{Binding Qty, Mode=TwoWay, Converter={StaticResource IntConverter}}" />
  168 +
133 </Grid> 169 </Grid>
134 </DataTemplate> 170 </DataTemplate>
135 </CollectionView.ItemTemplate> 171 </CollectionView.ItemTemplate>
136 </CollectionView> 172 </CollectionView>
  173 +
137 </Grid> 174 </Grid>
138 175
  176 + <!-- 底部按钮 -->
139 <Grid Grid.Row="4" ColumnDefinitions="*,*,*" Padding="16,8" ColumnSpacing="10"> 177 <Grid Grid.Row="4" ColumnDefinitions="*,*,*" Padding="16,8" ColumnSpacing="10">
140 178
141 <!-- 扫描通过 --> 179 <!-- 扫描通过 -->
@@ -177,20 +215,20 @@ @@ -177,20 +215,20 @@
177 </StackLayout> 215 </StackLayout>
178 </Grid> 216 </Grid>
179 217
180 - <!-- 确认库 --> 218 + <!-- 确认库 -->
181 <Grid Grid.Column="2" 219 <Grid Grid.Column="2"
182 BackgroundColor="#2196F3" 220 BackgroundColor="#2196F3"
183 HorizontalOptions="Fill" 221 HorizontalOptions="Fill"
184 VerticalOptions="Fill" 222 VerticalOptions="Fill"
185 HeightRequest="50"> 223 HeightRequest="50">
186 <Grid.GestureRecognizers> 224 <Grid.GestureRecognizers>
187 - <TapGestureRecognizer Command="{Binding ConfirmCommand}" /> 225 + <TapGestureRecognizer Tapped="OnConfirmClicked" />
188 </Grid.GestureRecognizers> 226 </Grid.GestureRecognizers>
189 <StackLayout Orientation="Horizontal" 227 <StackLayout Orientation="Horizontal"
190 HorizontalOptions="Center" 228 HorizontalOptions="Center"
191 VerticalOptions="Center"> 229 VerticalOptions="Center">
192 <Image Source="confirm.png" HeightRequest="20" WidthRequest="20" /> 230 <Image Source="confirm.png" HeightRequest="20" WidthRequest="20" />
193 - <Label Text="确认库" 231 + <Label Text="确认库"
194 Margin="5,0,0,0" 232 Margin="5,0,0,0"
195 VerticalOptions="Center" 233 VerticalOptions="Center"
196 TextColor="White" /> 234 TextColor="White" />
@@ -200,4 +238,4 @@ @@ -200,4 +238,4 @@
200 </Grid> 238 </Grid>
201 239
202 </Grid> 240 </Grid>
203 -</ContentPage>  
  241 +</ContentPage>
1 using IndustrialControl.Services; 1 using IndustrialControl.Services;
2 using IndustrialControl.ViewModels; 2 using IndustrialControl.ViewModels;
3 3
4 -namespace IndustrialControl.Pages 4 +namespace IndustrialControl.Pages;
  5 +
  6 +[QueryProperty(nameof(OutstockId), "outstockId")]
  7 +[QueryProperty(nameof(OutstockNo), "outstockNo")]
  8 +[QueryProperty(nameof(OrderType), "orderType")]
  9 +[QueryProperty(nameof(OrderTypeName), "orderTypeName")]
  10 +[QueryProperty(nameof(RequisitionMaterialNo), "requisitionMaterialNo")]
  11 +[QueryProperty(nameof(ReturnNo), "returnNo")]
  12 +[QueryProperty(nameof(DeliveryNo), "deliveryNo")]
  13 +[QueryProperty(nameof(CreatedTime), "createdTime")]
  14 +public partial class OutboundMaterialPage : ContentPage
5 { 15 {
6 - [QueryProperty(nameof(OutstockId), "outstockId")]  
7 - [QueryProperty(nameof(OutstockNo), "outstockNo")]  
8 - [QueryProperty(nameof(OrderType), "orderType")]  
9 - [QueryProperty(nameof(OrderTypeName), "orderTypeName")]  
10 - [QueryProperty(nameof(PurchaseNo), "purchaseNo")]  
11 - [QueryProperty(nameof(SupplierName), "supplierName")]  
12 - [QueryProperty(nameof(CreatedTime), "createdTime")]  
13 - public partial class OutboundMaterialPage : ContentPage 16 + private readonly ScanService _scanSvc;
  17 + private readonly OutboundMaterialViewModel _vm;
  18 + public string? OutstockId { get; set; }
  19 + public string? OutstockNo { get; set; }
  20 + public string? OrderType { get; set; }
  21 + public string? OrderTypeName { get; set; }
  22 + public string? RequisitionMaterialNo { get; set; }
  23 + public string? ReturnNo { get; set; }
  24 + public string? DeliveryNo { get; set; }
  25 + public string? CreatedTime { get; set; }
  26 + private readonly IDialogService _dialogs;
  27 +
  28 + public OutboundMaterialPage(OutboundMaterialViewModel vm, ScanService scanSvc, IDialogService dialogs)
14 { 29 {
15 - private readonly ScanService _scanSvc;  
16 - private readonly OutboundMaterialViewModel _vm;  
17 - public string? OutstockId { get; set; }  
18 - public string? OutstockNo { get; set; }  
19 - public string? OrderType { get; set; }  
20 - public string? OrderTypeName { get; set; }  
21 - public string? PurchaseNo { get; set; }  
22 - public string? SupplierName { get; set; }  
23 - public string? CreatedTime { get; set; }  
24 -  
25 -  
26 - public OutboundMaterialPage(OutboundMaterialViewModel vm, ScanService scanSvc)  
27 - {  
28 - InitializeComponent();  
29 - BindingContext = vm;  
30 - _scanSvc = scanSvc;  
31 - _vm = vm;  
32 - _scanSvc.Prefix = null; // 例如 "}q" 之类的前缀;没有就留 null  
33 - // _scanSvc.Suffix = "\n"; // 如果设备会附带换行,可去掉;没有就设 null  
34 - //_scanSvc.DebounceMs = 250;  
35 - _scanSvc.Suffix = null; // 先关掉  
36 - _scanSvc.DebounceMs = 0; // 先关掉  
37 - } 30 + InitializeComponent();
  31 + BindingContext = vm;
  32 + _scanSvc = scanSvc;
  33 + _vm = vm;
  34 + _dialogs = dialogs;
  35 + // 可选:配置前后缀与防抖
  36 + _scanSvc.Prefix = null; // 例如 "}q" 之类的前缀;没有就留 null
  37 + // _scanSvc.Suffix = "\n"; // 如果设备会附带换行,可去掉;没有就设 null
  38 + //_scanSvc.DebounceMs = 250;
  39 + _scanSvc.Suffix = null; // 先关掉
  40 + _scanSvc.DebounceMs = 0; // 先关掉
  41 +
  42 + }
  43 +
  44 + protected override async void OnAppearing()
  45 + {
  46 + base.OnAppearing();
38 47
39 - protected override async void OnAppearing() 48 + // ✅ 用搜索页带过来的基础信息初始化页面,并拉取两张表
  49 + if (!string.IsNullOrWhiteSpace(OutstockId))
40 { 50 {
41 - base.OnAppearing();  
42 -  
43 - // ✅ 用搜索页带过来的基础信息初始化页面,并拉取两张表  
44 - if (!string.IsNullOrWhiteSpace(OutstockId))  
45 - {  
46 - await _vm.InitializeFromSearchAsync(  
47 - outstockId: OutstockId ?? "",  
48 - outstockNo: OutstockNo ?? "",  
49 - orderType: OrderType ?? "",  
50 - orderTypeName: OrderTypeName ?? "",  
51 - purchaseNo: PurchaseNo ?? "",  
52 - supplierName: SupplierName ?? "",  
53 - createdTime: CreatedTime ?? ""  
54 - );  
55 - }  
56 -  
57 - _scanSvc.Scanned += OnScanned;  
58 - _scanSvc.StartListening();  
59 - _scanSvc.Attach(ScanEntry);  
60 - ScanEntry.Focus(); 51 + await _vm.InitializeFromSearchAsync(
  52 + outstockId: OutstockId ?? "",
  53 + outstockNo: OutstockNo ?? "",
  54 + orderType: OrderType ?? "",
  55 + orderTypeName: OrderTypeName ?? "",
  56 + requisitionMaterialNo: RequisitionMaterialNo ?? "",
  57 + returnNo: ReturnNo ?? "",
  58 + deliveryNo: DeliveryNo ?? "",
  59 + createdTime: CreatedTime ?? ""
  60 + );
61 } 61 }
62 - protected override void OnDisappearing() 62 +
  63 + _scanSvc.Scanned += OnScanned;
  64 + _scanSvc.StartListening();
  65 + _scanSvc.Attach(ScanEntry);
  66 + ScanEntry.Focus();
  67 + }
  68 +
  69 +
  70 + /// <summary>
  71 + /// 清空扫描记录
  72 + /// </summary>
  73 + void OnClearClicked(object sender, EventArgs e)
  74 + {
  75 + _vm.ClearScan();
  76 + ScanEntry.Text = string.Empty;
  77 + ScanEntry.Focus();
  78 + }
  79 +
  80 + protected override void OnDisappearing()
  81 + {
  82 + // 退出页面即注销(防止多个程序/页面抢处理)
  83 + _scanSvc.Scanned -= OnScanned;
  84 + _scanSvc.StopListening();
  85 +
  86 + base.OnDisappearing();
  87 + }
  88 +
  89 + private void OnScanned(string data, string type)
  90 + {
  91 + MainThread.BeginInvokeOnMainThread(async () =>
63 { 92 {
64 - // 退出页面即注销(防止多个程序/页面抢处理)  
65 - _scanSvc.Scanned -= OnScanned;  
66 - _scanSvc.StopListening(); 93 + // 常见处理:自动填入单号/条码并触发查询或加入明细
  94 + _vm.ScanCode = data;
  95 +
  96 + // 你原本的逻辑:若识别到是订单号 → 查询;若是包装码 → 加入列表等
  97 + await _vm.HandleScannedAsync(data, type);
  98 + });
  99 + }
67 100
68 - base.OnDisappearing();  
69 - }  
70 101
71 - private void OnScanned(string data, string type) 102 + /// <summary>
  103 + /// 确认入库按钮点击
  104 + /// </summary>
  105 + async void OnConfirmClicked(object sender, EventArgs e)
  106 + {
  107 + var ok = await _vm.ConfirmOutboundAsync();
  108 + if (ok)
72 { 109 {
73 - MainThread.BeginInvokeOnMainThread(async () =>  
74 - {  
75 - // 常见处理:自动填入单号/条码并触发查询或加入明细  
76 - _vm.ScanCode = data;  
77 -  
78 - // 你原本的逻辑:若识别到是订单号 → 查询;若是包装码 → 加入列表等  
79 - await _vm.HandleScannedAsync(data, type);  
80 - }); 110 + await DisplayAlert("提示", "入库成功", "确定");
  111 + _vm.ClearAll();
  112 +
  113 + // ✅ 返回到工单查询页面(InboundMaterialSearchPage)
  114 + await Shell.Current.GoToAsync($"//{nameof(OutboundMaterialSearchPage)}");
81 } 115 }
82 - // Tab切换  
83 - void OnPendingTabClicked(object sender, EventArgs e) 116 + else
84 { 117 {
85 - _vm.IsPendingVisible = true;  
86 - _vm.IsScannedVisible = false; 118 + await DisplayAlert("提示", "入库失败,请检查数据", "确定");
87 } 119 }
  120 + }
88 121
89 - void OnScannedTabClicked(object sender, EventArgs e)  
90 - {  
91 - _vm.IsPendingVisible = false;  
92 - _vm.IsScannedVisible = true;  
93 - }  
94 122
95 - // 清空扫描记录  
96 - void OnClearClicked(object sender, EventArgs e)  
97 - {  
98 - _vm.ClearScan();  
99 - ScanEntry.Text = string.Empty;  
100 - ScanEntry.Focus();  
101 - } 123 + private async void OnBinTapped(object? sender, TappedEventArgs e)
  124 + {
  125 + var bindable = sender as BindableObject;
  126 + var row = bindable?.BindingContext;
  127 + if (row == null) return;
102 128
103 - // 摄像头扫码按钮  
104 - async void OnScanClicked(object sender, EventArgs e)  
105 - {  
106 - await DisplayAlert("提示", "此按钮预留摄像头扫码;硬件扫描直接扣扳机。", "确定");  
107 - } 129 + var type = row.GetType();
  130 + var currentBin = type.GetProperty("Location")?.GetValue(row)?.ToString();
  131 +
  132 + // 1) 打开公共组件选择库位(你已完成的组件)
  133 + var picked = await BinPickerPage.ShowAsync(currentBin);
  134 + if (picked == null) return;
  135 +
  136 + // 2) 调用 VM:带上行对象 + 选中的 BinInfo,内部会调接口 & 回填行
  137 + await _vm.UpdateRowLocationAsync(row, picked);
  138 + }
108 139
  140 + private async void OnQtyCompleted(object sender, EventArgs e)
  141 + {
  142 + if (sender is not Entry entry) return;
  143 + if (entry.BindingContext is not IndustrialControl.ViewModels.OutScannedItem row) return;
109 144
  145 + // 只看 ScanStatus:未通过则不提交
  146 + if (!row.ScanStatus)
  147 + {
  148 + await DisplayAlert("提示", "该行尚未扫描通过,不能修改数量。", "确定");
  149 + return;
  150 + }
110 151
111 - 152 + await _vm.UpdateQuantityForRowAsync(row);
112 } 153 }
  154 +
113 } 155 }
1 <?xml version="1.0" encoding="utf-8" ?> 1 <?xml version="1.0" encoding="utf-8" ?>
2 -<ContentPage x:Name="Page" 2 +<ContentPage
  3 + x:Class="IndustrialControl.Pages.OutboundMaterialSearchPage"
3 xmlns="http://schemas.microsoft.com/dotnet/2021/maui" 4 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> 5 + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  6 + x:Name="Page"
  7 + Shell.NavBarIsVisible="True">
14 8
15 - <Grid RowDefinitions="Auto,*,Auto" Padding="16" BackgroundColor="#F6F7FB"> 9 + <ScrollView>
  10 + <VerticalStackLayout Spacing="12" Padding="12">
16 11
17 - <!-- 顶部:输入条件(第0行) -->  
18 - <VerticalStackLayout Grid.Row="0" Spacing="10"> 12 + <!-- 查询区域 -->
19 <Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,Auto" ColumnSpacing="8" RowSpacing="8"> 13 <Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,Auto" ColumnSpacing="8" RowSpacing="8">
20 <Entry x:Name="OrderEntry" 14 <Entry x:Name="OrderEntry"
21 Grid.Row="0" Grid.Column="0" 15 Grid.Row="0" Grid.Column="0"
22 - Placeholder="请输入出库单条码"  
23 - VerticalOptions="Center"  
24 - BackgroundColor="White" 16 + Placeholder="请扫描输入出库单条码"
25 Text="{Binding SearchOrderNo}" /> 17 Text="{Binding SearchOrderNo}" />
26 18
27 -  
28 - <!-- 开始日期 -->  
29 - <DatePicker Grid.Row="1" Grid.Column="0"  
30 - Date="{Binding StartDate}"  
31 - MinimumDate="2000-01-01"  
32 - MaximumDate="{Binding EndDate}" />  
33 -  
34 - <!-- 结束日期 -->  
35 - <DatePicker Grid.Row="1" Grid.Column="1"  
36 - Date="{Binding EndDate}"  
37 - MinimumDate="{Binding StartDate}" />  
38 - <Button Grid.Row="1" Grid.Column="1" 19 + <Button Grid.Row="0" Grid.Column="1"
39 Text="查询" 20 Text="查询"
40 Command="{Binding SearchCommand}" /> 21 Command="{Binding SearchCommand}" />
  22 +
  23 + <Grid Grid.Row="1" Grid.ColumnSpan="2" ColumnDefinitions="Auto,*,Auto,*" ColumnSpacing="8">
  24 + <Label Grid.Column="0" Text="开始:" VerticalTextAlignment="Center"/>
  25 + <DatePicker Grid.Column="1" Date="{Binding StartDate}" />
  26 + <Label Grid.Column="2" Text="结束:" VerticalTextAlignment="Center"/>
  27 + <DatePicker Grid.Column="3" Date="{Binding EndDate}" />
  28 + </Grid>
41 </Grid> 29 </Grid>
42 - </VerticalStackLayout>  
43 30
44 - <!-- 中部:结果列表(第1行) -->  
45 - <CollectionView Grid.Row="1"  
46 - ItemsSource="{Binding Orders}"  
47 - SelectionMode="Single"  
48 - SelectedItem="{Binding SelectedOrder, Mode=TwoWay}">  
49 - <CollectionView.ItemTemplate>  
50 - <DataTemplate>  
51 - <Frame Margin="0,8,0,0" Padding="12" HasShadow="True" CornerRadius="10">  
52 - <!-- ⭐ 点击整卡片触发命令 -->  
53 - <Frame.GestureRecognizers>  
54 - <TapGestureRecognizer  
55 - Command="{Binding BindingContext.GoOutboundCommand, Source={x:Reference Page}}"  
56 - CommandParameter="{Binding .}" />  
57 - </Frame.GestureRecognizers>  
58 - <Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="Auto,*" ColumnSpacing="8">  
59 - <Label Grid.Row="0" Grid.Column="0" Text="出库单号:" FontAttributes="Bold"/>  
60 - <Label Grid.Row="0" Grid.Column="1" Text="{Binding outstockNo}"/> 31 + <!-- 列表区域 -->
  32 + <CollectionView Grid.Row="1"
  33 + ItemsSource="{Binding Orders}"
  34 + SelectionMode="Single"
  35 + SelectedItem="{Binding SelectedOrder, Mode=TwoWay}">
  36 + <CollectionView.ItemTemplate>
  37 + <DataTemplate>
  38 + <Frame Margin="0,8,0,0" Padding="12" HasShadow="True" CornerRadius="10">
  39 + <!-- 点击整卡片:直接调用 VM 的 GoOutboundCommand,并把当前项作为参数 -->
  40 + <Frame.GestureRecognizers>
  41 + <TapGestureRecognizer
  42 + Command="{Binding BindingContext.GoOutboundCommand, Source={x:Reference Page}}"
  43 + CommandParameter="{Binding .}" />
  44 + </Frame.GestureRecognizers>
  45 +
  46 + <Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto"
  47 + ColumnDefinitions="Auto,*" ColumnSpacing="8">
  48 + <Label Grid.Row="0" Grid.Column="0" Text="出库单号:" FontAttributes="Bold"/>
  49 + <Label Grid.Row="0" Grid.Column="1" Text="{Binding outstockNo}"/>
61 50
62 - <Label Grid.Row="1" Grid.Column="0" Text="出库单类型:" FontAttributes="Bold"/>  
63 - <Label Grid.Row="1" Grid.Column="1" Text="{Binding orderTypeName}" /> 51 + <Label Grid.Row="1" Grid.Column="0" Text="出库单类型:" FontAttributes="Bold"/>
  52 + <Label Grid.Row="1" Grid.Column="1" Text="{Binding orderTypeName}" />
64 53
65 - <Label Grid.Row="2" Grid.Column="0" Text="关联领料单号:" FontAttributes="Bold"/>  
66 - <Label Grid.Row="2" Grid.Column="1" Text="{Binding requisitionMaterialNo}" /> 54 + <Label Grid.Row="2" Grid.Column="0" Text="关联领料单号:" FontAttributes="Bold"/>
  55 + <Label Grid.Row="2" Grid.Column="1" Text="{Binding requisitionMaterialNo}" />
67 56
68 - <Label Grid.Row="3" Grid.Column="0" Text="关联工单号:" FontAttributes="Bold"/>  
69 - <Label Grid.Row="3" Grid.Column="1" Text="{Binding workOrderNo}" /> 57 + <Label Grid.Row="3" Grid.Column="0" Text="关联工单号:" FontAttributes="Bold"/>
  58 + <Label Grid.Row="3" Grid.Column="1" Text="{Binding workOrderNo}" />
70 59
71 - <Label Grid.Row="4" Grid.Column="0" Text="创建日期:" FontAttributes="Bold"/>  
72 - <Label Grid.Row="4" Grid.Column="1" Text="{Binding createdTime}" />  
73 - </Grid>  
74 - </Frame>  
75 - </DataTemplate>  
76 - </CollectionView.ItemTemplate>  
77 - </CollectionView> 60 + <Label Grid.Row="4" Grid.Column="0" Text="创建日期:" FontAttributes="Bold"/>
  61 + <Label Grid.Row="4" Grid.Column="1" Text="{Binding createdTime}" />
  62 + </Grid>
  63 + </Frame>
  64 + </DataTemplate>
  65 + </CollectionView.ItemTemplate>
  66 + </CollectionView>
78 67
79 - <!-- 底部:操作(第2行) -->  
80 - <Grid Grid.Row="2" ColumnDefinitions="*,Auto" Padding="0,8,0,0">  
81 - <Label Text="{Binding Orders.Count, StringFormat='共 {0} 条'}"  
82 - VerticalTextAlignment="Center" />  
83 - </Grid>  
84 - </Grid> 68 + </VerticalStackLayout>
  69 + </ScrollView>
85 </ContentPage> 70 </ContentPage>
1 -using System.Threading;  
2 using IndustrialControl.Services; 1 using IndustrialControl.Services;
3 using IndustrialControl.ViewModels; 2 using IndustrialControl.ViewModels;
4 namespace IndustrialControl.Pages; 3 namespace IndustrialControl.Pages;
@@ -10,7 +9,7 @@ public partial class OutboundMaterialSearchPage : ContentPage @@ -10,7 +9,7 @@ public partial class OutboundMaterialSearchPage : ContentPage
10 public OutboundMaterialSearchPage(OutboundMaterialSearchViewModel vm, ScanService scanSvc) 9 public OutboundMaterialSearchPage(OutboundMaterialSearchViewModel vm, ScanService scanSvc)
11 { 10 {
12 _vm = vm; 11 _vm = vm;
13 - 12 +
14 BindingContext = vm; 13 BindingContext = vm;
15 _scanSvc = scanSvc; 14 _scanSvc = scanSvc;
16 InitializeComponent(); 15 InitializeComponent();
@@ -30,6 +29,8 @@ public partial class OutboundMaterialSearchPage : ContentPage @@ -30,6 +29,8 @@ public partial class OutboundMaterialSearchPage : ContentPage
30 //键盘输入 29 //键盘输入
31 _scanSvc.Attach(OrderEntry); 30 _scanSvc.Attach(OrderEntry);
32 OrderEntry.Focus(); 31 OrderEntry.Focus();
  32 +
  33 +
33 } 34 }
34 35
35 /// <summary> 36 /// <summary>
@@ -59,4 +60,5 @@ public partial class OutboundMaterialSearchPage : ContentPage @@ -59,4 +60,5 @@ public partial class OutboundMaterialSearchPage : ContentPage
59 }); 60 });
60 } 61 }
61 62
  63 +
62 } 64 }
1 using IndustrialControl.Models; 1 using IndustrialControl.Models;
2 using IndustrialControl.ViewModels; 2 using IndustrialControl.ViewModels;
3 3
4 -namespace IndustrialControl.Services 4 +namespace IndustrialControl.Services;
  5 +
  6 +public interface IOutboundMaterialService
5 { 7 {
6 - public interface IOutboundMaterialService  
7 - {  
8 - // NEW: 查询列表(图1)  
9 - Task<IEnumerable<OutboundOrderSummary>> ListOutboundOrdersAsync(  
10 - string? orderNoOrBarcode,  
11 - DateTime startDate,  
12 - DateTime endDate,  
13 - string orderType,  
14 - string[] orderTypeList,  
15 - CancellationToken ct = default);  
16 -  
17 - Task<IReadOnlyList<OutboundPendingRow>> GetOutStockDetailAsync(string outstockId, CancellationToken ct = default);  
18 - Task<IReadOnlyList<OutboundScannedRow>> GetOutStockScanDetailAsync(string outstockId, CancellationToken ct = default);  
19 - /// <summary>扫描条码入库</summary>  
20 - Task<SimpleOk> OutStockByBarcodeAsync(string outstockId, string barcode, CancellationToken ct = default);  
21 - /// <summary>PDA 扫描通过(确认当前入库单已扫描项)</summary>  
22 - Task<SimpleOk> ScanConfirmAsync(string outstockId, CancellationToken ct = default);  
23 -  
24 - Task<SimpleOk> CancelScanAsync(string outstockId, CancellationToken ct = default);  
25 - Task<SimpleOk> ConfirmOutstockAsync(string outstockId, CancellationToken ct = default);  
26 - /// <summary>判断入库单明细是否全部扫码确认</summary>  
27 - Task<bool> JudgeOutstockDetailScanAllAsync(string outstockId, CancellationToken ct = default);  
28 - } 8 +
  9 + // NEW: 查询列表(图1)
  10 + Task<IEnumerable<OutboundOrderSummary>> ListOutboundOrdersAsync(
  11 + string? orderNoOrBarcode,
  12 + DateTime startDate,
  13 + DateTime endDate,
  14 + string orderType,
  15 + string[] orderTypeList,
  16 + CancellationToken ct = default);
  17 +
  18 + Task<IReadOnlyList<OutboundPendingRow>> GetOutStockDetailAsync(string outstockId, CancellationToken ct = default);
  19 + Task<IReadOnlyList<OutboundScannedRow>> GetOutStockScanDetailAsync(string outstockId, CancellationToken ct = default);
  20 + /// <summary>扫描条码入库</summary>
  21 + Task<SimpleOk> OutStockByBarcodeAsync(string outstockId, string barcode, CancellationToken ct = default);
  22 + /// <summary>PDA 扫描通过(确认当前入库单已扫描项)</summary>
  23 + // IOutboundMaterialService.cs
  24 + Task<SimpleOk> ScanConfirmAsync(IEnumerable<(string barcode, string id)> items, CancellationToken ct = default);
  25 + Task<SimpleOk> CancelScanAsync(IEnumerable<(string barcode, string id)> items, CancellationToken ct = default);
  26 +
  27 + Task<SimpleOk> ConfirmOutstockAsync(string outstockId, CancellationToken ct = default);
  28 + /// <summary>判断入库单明细是否全部扫码确认</summary>
  29 + Task<bool> JudgeOutstockDetailScanAllAsync(string outstockId, CancellationToken ct = default);
  30 +
  31 +
  32 + Task<SimpleOk> UpdateOutstockLocationAsync(
  33 + string detailId,
  34 + string id,
  35 + string outstockWarehouse,
  36 + string outstockWarehouseCode,
  37 + string location,
  38 + CancellationToken ct = default);
  39 +
  40 + Task<SimpleOk> UpdateQuantityAsync(
  41 + string barcode, string detailId, string id, int quantity, CancellationToken ct = default);
  42 +
29 } 43 }
  44 +
  45 +
  46 +
  47 +
@@ -155,11 +155,13 @@ public sealed class InboundMaterialService : IInboundMaterialService @@ -155,11 +155,13 @@ public sealed class InboundMaterialService : IInboundMaterialService
155 purchaseNo: x.purchaseNo ?? "", 155 purchaseNo: x.purchaseNo ?? "",
156 arrivalNo: x.arrivalNo ?? "", 156 arrivalNo: x.arrivalNo ?? "",
157 supplierName: x.supplierName ?? "", 157 supplierName: x.supplierName ?? "",
  158 + workOrderNo: x.workOrderNo ?? "",
  159 + materialName: x.materialName ?? "",
  160 + instockQty: ToInt(x.instockQty),
158 createdTime: x.createdTime ?? "" 161 createdTime: x.createdTime ?? ""
159 )); 162 ));
160 } 163 }
161 164
162 -  
163 public async Task<IReadOnlyList<InboundPendingRow>> GetInStockDetailAsync( 165 public async Task<IReadOnlyList<InboundPendingRow>> GetInStockDetailAsync(
164 string instockId, CancellationToken ct = default) 166 string instockId, CancellationToken ct = default)
165 { 167 {
@@ -281,7 +283,7 @@ public sealed class InboundMaterialService : IInboundMaterialService @@ -281,7 +283,7 @@ public sealed class InboundMaterialService : IInboundMaterialService
281 283
282 public async Task<SimpleOk> ConfirmInstockAsync(string instockId, CancellationToken ct = default) 284 public async Task<SimpleOk> ConfirmInstockAsync(string instockId, CancellationToken ct = default)
283 { 285 {
284 - var bodyJson = JsonSerializer.Serialize(new { instockId }); 286 + var bodyJson = JsonSerializer.Serialize(new { id = instockId });
285 using var req = new HttpRequestMessage(HttpMethod.Post, _confirmInstockEndpoint) 287 using var req = new HttpRequestMessage(HttpMethod.Post, _confirmInstockEndpoint)
286 { 288 {
287 Content = new StringContent(bodyJson, Encoding.UTF8, "application/json") 289 Content = new StringContent(bodyJson, Encoding.UTF8, "application/json")
@@ -577,6 +579,9 @@ public class GetInStockRecord @@ -577,6 +579,9 @@ public class GetInStockRecord
577 public string? supplierName { get; set; } 579 public string? supplierName { get; set; }
578 public string? arrivalNo { get; set; } 580 public string? arrivalNo { get; set; }
579 public string? purchaseNo { get; set; } 581 public string? purchaseNo { get; set; }
  582 + public string? workOrderNo { get; set; }
  583 + public string? materialName { get; set; }
  584 + public decimal? instockQty { get; set; }
580 public string? createdTime { get; set; } 585 public string? createdTime { get; set; }
581 } 586 }
582 public sealed class GetInStockScanDetailResp 587 public sealed class GetInStockScanDetailResp
1 -// 文件:Services/WarehouseDataApiService.cs 1 +using IndustrialControl.Models;
  2 +using IndustrialControl.ViewModels;
2 using System.Net.Http; 3 using System.Net.Http;
3 using System.Text; 4 using System.Text;
4 using System.Text.Json; 5 using System.Text.Json;
5 using System.Text.Json.Nodes; 6 using System.Text.Json.Nodes;
6 -using IndustrialControl.Models;  
7 -using IndustrialControl.ViewModels; 7 +using System.Text.Json.Serialization;
8 8
9 namespace IndustrialControl.Services; 9 namespace IndustrialControl.Services;
10 10
@@ -13,23 +13,25 @@ namespace IndustrialControl.Services; @@ -13,23 +13,25 @@ namespace IndustrialControl.Services;
13 /// </summary> 13 /// </summary>
14 public sealed class OutboundMaterialService : IOutboundMaterialService 14 public sealed class OutboundMaterialService : IOutboundMaterialService
15 { 15 {
16 - private readonly HttpClient _http;  
17 - private readonly string _outboundListEndpoint;  
18 - private readonly string _detailEndpoint;  
19 - private readonly string _scanDetailEndpoint; 16 + public readonly HttpClient _http;
  17 + public readonly string _outboundListEndpoint;
  18 + public readonly string _detailEndpoint;
  19 + public readonly string _scanDetailEndpoint;
20 // 新增:扫码入库端点 20 // 新增:扫码入库端点
21 - private readonly string _scanByBarcodeEndpoint;  
22 - private readonly string _scanConfirmEndpoint;  
23 - private readonly string _cancelScanEndpoint;  
24 - private readonly string _confirmOutstockEndpoint;  
25 - private readonly string _judgeScanAllEndpoint; 21 + public readonly string _scanByBarcodeEndpoint;
  22 + public readonly string _scanConfirmEndpoint;
  23 + public readonly string _cancelScanEndpoint;
  24 + public readonly string _confirmOutstockEndpoint;
  25 + public readonly string _judgeScanAllEndpoint;
  26 + private readonly JsonSerializerOptions _opt;
  27 + private readonly JsonSerializerOptions _json = new() { PropertyNameCaseInsensitive = true };
26 28
27 public OutboundMaterialService(HttpClient http, IConfigLoader configLoader) 29 public OutboundMaterialService(HttpClient http, IConfigLoader configLoader)
28 { 30 {
29 _http = http; 31 _http = http;
30 -  
31 - // 和 WorkOrderApi 一样从 appconfig.json 读取端点,留兼容键名 + 兜底硬编码 32 + _opt = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
32 JsonNode cfg = configLoader.Load(); 33 JsonNode cfg = configLoader.Load();
  34 +
33 // ⭐ 新增:读取 baseUrl 或 ip+port 35 // ⭐ 新增:读取 baseUrl 或 ip+port
34 var baseUrl = 36 var baseUrl =
35 (string?)cfg?["server"]?["baseUrl"] 37 (string?)cfg?["server"]?["baseUrl"]
@@ -40,32 +42,43 @@ public sealed class OutboundMaterialService : IOutboundMaterialService @@ -40,32 +42,43 @@ public sealed class OutboundMaterialService : IOutboundMaterialService
40 42
41 if (_http.BaseAddress is null) 43 if (_http.BaseAddress is null)
42 _http.BaseAddress = new Uri(baseUrl, UriKind.Absolute); 44 _http.BaseAddress = new Uri(baseUrl, UriKind.Absolute);
  45 +
  46 + // 下面保持原来的相对路径读取(不变)
43 _outboundListEndpoint = 47 _outboundListEndpoint =
44 (string?)cfg?["apiEndpoints"]?["outbound"]?["list"] ?? 48 (string?)cfg?["apiEndpoints"]?["outbound"]?["list"] ??
45 (string?)cfg?["apiEndpoints"]?["getOutStock"] ?? 49 (string?)cfg?["apiEndpoints"]?["getOutStock"] ??
46 "/normalService/pda/wmsMaterialOutstock/getOutStock"; 50 "/normalService/pda/wmsMaterialOutstock/getOutStock";
47 - _detailEndpoint = (string?)cfg?["apiEndpoints"]?["outbound"]?["detail"]  
48 - ?? "/normalService/pda/wmsMaterialOutstock/getOutStockDetail";  
49 - _scanDetailEndpoint = (string?)cfg?["apiEndpoints"]?["outbound"]?["scanDetail"]  
50 - ?? "/normalService/pda/wmsMaterialOutstock/getOutStockScanDetail"; 51 +
  52 + _detailEndpoint =
  53 + (string?)cfg?["apiEndpoints"]?["outbound"]?["detail"] ??
  54 + "/normalService/pda/wmsMaterialOutstock/getOutStockDetail";
  55 +
  56 + _scanDetailEndpoint =
  57 + (string?)cfg?["apiEndpoints"]?["outbound"]?["scanDetail"] ??
  58 + "/normalService/pda/wmsMaterialOutstock/getOutStockScanDetail";
  59 +
51 _scanByBarcodeEndpoint = 60 _scanByBarcodeEndpoint =
52 - (string?)cfg?["apiEndpoints"]?["outbound"]?["scanByBarcode"]  
53 - ?? "/normalService/pda/wmsMaterialOutstock/getOutStockByBarcode"; 61 + (string?)cfg?["apiEndpoints"]?["outbound"]?["scanByBarcode"] ??
  62 + "/normalService/pda/wmsMaterialOutstock/getOutStockByBarcode";
  63 +
54 _scanConfirmEndpoint = 64 _scanConfirmEndpoint =
55 - (string?)cfg?["apiEndpoints"]?["outbound"]?["scanConfirm"]  
56 - ?? "/normalService/pda/wmsMaterialOutstock/scanConfirm"; 65 + (string?)cfg?["apiEndpoints"]?["outbound"]?["scanConfirm"] ??
  66 + "/normalService/pda/wmsMaterialOutstock/scanOutConfirm";
  67 +
57 _cancelScanEndpoint = 68 _cancelScanEndpoint =
58 - (string?)cfg?["apiEndpoints"]?["outbound"]?["cancelScan"]  
59 - ?? "/normalService/pda/wmsMaterialOutstock/cancelScan"; 69 + (string?)cfg?["apiEndpoints"]?["outbound"]?["cancelScan"] ??
  70 + "/normalService/pda/wmsMaterialOutstock/cancelOutScan";
  71 +
60 _confirmOutstockEndpoint = 72 _confirmOutstockEndpoint =
61 - (string?)cfg?["apiEndpoints"]?["outbound"]?["confirm"]  
62 - ?? "/normalService/pda/wmsMaterialOutstock/confirm"; 73 + (string?)cfg?["apiEndpoints"]?["outbound"]?["confirm"] ??
  74 + "/normalService/pda/wmsMaterialOutstock/confirm";
  75 +
63 _judgeScanAllEndpoint = 76 _judgeScanAllEndpoint =
64 - (string?)cfg?["apiEndpoints"]?["outbound"]?["judgeScanAll"]  
65 - ?? "/normalService/pda/wmsMaterialOutstock/judgeOutstockDetailScanAll"; 77 + (string?)cfg?["apiEndpoints"]?["outbound"]?["judgeScanAll"] ??
  78 + "/normalService/pda/wmsMaterialOutstock/judgeOutstockDetailScanAll";
66 } 79 }
67 80
68 - // ====== 你当前页面会调用的方法 ====== 81 + // ⭐ 新增:拼接 ip + port → baseUrl
69 private static string? BuildBaseUrl(JsonNode? ipNode, JsonNode? portNode) 82 private static string? BuildBaseUrl(JsonNode? ipNode, JsonNode? portNode)
70 { 83 {
71 string? ip = ipNode?.ToString().Trim(); 84 string? ip = ipNode?.ToString().Trim();
@@ -134,25 +147,21 @@ public sealed class OutboundMaterialService : IOutboundMaterialService @@ -134,25 +147,21 @@ public sealed class OutboundMaterialService : IOutboundMaterialService
134 147
135 return records.Select(x => new OutboundOrderSummary( 148 return records.Select(x => new OutboundOrderSummary(
136 outstockId: x.id ?? "", 149 outstockId: x.id ?? "",
  150 + outstockNo: x.outstockNo ?? "",
137 orderType: x.orderType ?? "", 151 orderType: x.orderType ?? "",
138 orderTypeName: x.orderTypeName ?? "", 152 orderTypeName: x.orderTypeName ?? "",
139 - purchaseNo: x.purchaseNo ?? "",  
140 - supplierName: x.supplierName ?? "",  
141 - arrivalNo: x.arrivalNo ?? "",  
142 - createdTime: x.createdTime ?? "",  
143 - deliveryNo: x.deliveryNo ?? "",  
144 - requisitionMaterialNo:x.requisitionMaterialNo ?? "",  
145 - returnNo: x.returnNo ?? "",  
146 - workOrderNo:x.workOrderNo ?? ""  
147 -  
148 - 153 + workOrderNo: x.workOrderNo ?? "",
  154 + requisitionMaterialNo: x.requisitionMaterialNo ?? "",
  155 + returnNo:x.returnNo ?? "",
  156 + deliveryNo:x.deliveryNo ?? "",
  157 + createdTime: x.createdTime ?? ""
149 )); 158 ));
150 } 159 }
151 160
152 -  
153 public async Task<IReadOnlyList<OutboundPendingRow>> GetOutStockDetailAsync( 161 public async Task<IReadOnlyList<OutboundPendingRow>> GetOutStockDetailAsync(
154 string outstockId, CancellationToken ct = default) 162 string outstockId, CancellationToken ct = default)
155 { 163 {
  164 + // ✅ 文档为 GET + x-www-form-urlencoded,参数名是小写 outstockId
156 var url = $"{_detailEndpoint}?outstockId={Uri.EscapeDataString(outstockId)}"; 165 var url = $"{_detailEndpoint}?outstockId={Uri.EscapeDataString(outstockId)}";
157 using var req = new HttpRequestMessage(HttpMethod.Get, url); 166 using var req = new HttpRequestMessage(HttpMethod.Get, url);
158 167
@@ -166,27 +175,22 @@ public sealed class OutboundMaterialService : IOutboundMaterialService @@ -166,27 +175,22 @@ public sealed class OutboundMaterialService : IOutboundMaterialService
166 if (dto?.success != true || dto.result is null || dto.result.Count == 0) 175 if (dto?.success != true || dto.result is null || dto.result.Count == 0)
167 return Array.Empty<OutboundPendingRow>(); 176 return Array.Empty<OutboundPendingRow>();
168 177
169 - static int ToIntSafe(string? s)  
170 - {  
171 - if (string.IsNullOrWhiteSpace(s)) return 0;  
172 - s = s.Trim().Replace(",", "");  
173 - return int.TryParse(s, out var v) ? v : 0;  
174 - }  
175 -  
176 // ⚠️ 接口没有 barcode,这里先用空串;如需展示可以改成 x.materialCode 或 x.stockBatch 178 // ⚠️ 接口没有 barcode,这里先用空串;如需展示可以改成 x.materialCode 或 x.stockBatch
177 var list = dto.result.Select(x => new OutboundPendingRow( 179 var list = dto.result.Select(x => new OutboundPendingRow(
178 - Barcode: string.Empty, // 或 $"{x.materialCode}" / $"{x.stockBatch}"  
179 - DetailId: x.id ?? string.Empty, // ← 改为接口的 id  
180 - Location: x.location ?? string.Empty,  
181 MaterialName: x.materialName ?? string.Empty, 180 MaterialName: x.materialName ?? string.Empty,
182 - PendingQty: ToIntSafe(x.outstockQty), // ← 预计数量  
183 - ScannedQty: ToIntSafe(x.qty), // ← 已扫描量  
184 - Spec: x.spec ?? string.Empty 181 + MaterialCode: x.materialCode ?? string.Empty,
  182 + Spec: x.spec ?? string.Empty,
  183 + Location: x.location ?? string.Empty,
  184 + ProductionBatch: x.productionBatch ?? string.Empty,
  185 + StockBatch: x.stockBatch ?? string.Empty,
  186 + OutstockQty: ToInt(x.outstockQty), // 此处再转 int
  187 + Qty: ToInt(x.qty) // ← 已扫描量
185 )).ToList(); 188 )).ToList();
186 189
187 return list; 190 return list;
188 } 191 }
189 192
  193 + static int ToInt(decimal? v) => v.HasValue ? (int)Math.Round(v.Value, MidpointRounding.AwayFromZero) : 0;
190 public async Task<IReadOnlyList<OutboundScannedRow>> GetOutStockScanDetailAsync( 194 public async Task<IReadOnlyList<OutboundScannedRow>> GetOutStockScanDetailAsync(
191 string outstockId, 195 string outstockId,
192 CancellationToken ct = default) 196 CancellationToken ct = default)
@@ -206,21 +210,13 @@ public sealed class OutboundMaterialService : IOutboundMaterialService @@ -206,21 +210,13 @@ public sealed class OutboundMaterialService : IOutboundMaterialService
206 if (dto?.success != true || dto.result is null || dto.result.Count == 0) 210 if (dto?.success != true || dto.result is null || dto.result.Count == 0)
207 return Array.Empty<OutboundScannedRow>(); 211 return Array.Empty<OutboundScannedRow>();
208 212
209 - static int ToIntSafe(string? s)  
210 - {  
211 - if (string.IsNullOrWhiteSpace(s)) return 0;  
212 - // 去除千分位、空格  
213 - s = s.Trim().Replace(",", "");  
214 - return int.TryParse(s, out var v) ? v : 0;  
215 - }  
216 -  
217 // 映射:OutstockId <- id(截图注释“入库单明细主键id”) 213 // 映射:OutstockId <- id(截图注释“入库单明细主键id”)
218 var list = dto.result.Select(x => new OutboundScannedRow( 214 var list = dto.result.Select(x => new OutboundScannedRow(
219 Barcode: (x.barcode ?? string.Empty).Trim(), 215 Barcode: (x.barcode ?? string.Empty).Trim(),
220 DetailId: (x.id ?? string.Empty).Trim(), 216 DetailId: (x.id ?? string.Empty).Trim(),
221 Location: (x.location ?? string.Empty).Trim(), 217 Location: (x.location ?? string.Empty).Trim(),
222 MaterialName: (x.materialName ?? string.Empty).Trim(), 218 MaterialName: (x.materialName ?? string.Empty).Trim(),
223 - Qty: ToIntSafe(x.qty), 219 + Qty: ToInt(x.qty),
224 Spec: (x.spec ?? string.Empty).Trim(), 220 Spec: (x.spec ?? string.Empty).Trim(),
225 ScanStatus: x.scanStatus ?? false, 221 ScanStatus: x.scanStatus ?? false,
226 WarehouseCode: x.warehouseCode?.Trim() 222 WarehouseCode: x.warehouseCode?.Trim()
@@ -228,10 +224,13 @@ public sealed class OutboundMaterialService : IOutboundMaterialService @@ -228,10 +224,13 @@ public sealed class OutboundMaterialService : IOutboundMaterialService
228 224
229 return list; 225 return list;
230 } 226 }
  227 +
231 // ========= 扫码入库实现 ========= 228 // ========= 扫码入库实现 =========
232 public async Task<SimpleOk> OutStockByBarcodeAsync(string outstockId, string barcode, CancellationToken ct = default) 229 public async Task<SimpleOk> OutStockByBarcodeAsync(string outstockId, string barcode, CancellationToken ct = default)
233 { 230 {
234 - var body = JsonSerializer.Serialize(new { barcode, outstockId }); 231 + // 注意:接口要的是 id 不是 outstockId
  232 + var body = JsonSerializer.Serialize(new { barcode, id = outstockId });
  233 +
235 using var req = new HttpRequestMessage(HttpMethod.Post, _scanByBarcodeEndpoint) 234 using var req = new HttpRequestMessage(HttpMethod.Post, _scanByBarcodeEndpoint)
236 { 235 {
237 Content = new StringContent(body, Encoding.UTF8, "application/json") 236 Content = new StringContent(body, Encoding.UTF8, "application/json")
@@ -239,16 +238,16 @@ public sealed class OutboundMaterialService : IOutboundMaterialService @@ -239,16 +238,16 @@ public sealed class OutboundMaterialService : IOutboundMaterialService
239 238
240 using var res = await _http.SendAsync(req, ct); 239 using var res = await _http.SendAsync(req, ct);
241 var json = await res.Content.ReadAsStringAsync(ct); 240 var json = await res.Content.ReadAsStringAsync(ct);
242 - var opt = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };  
243 - var dto = JsonSerializer.Deserialize<ScanByBarcodeResp>(json, opt);  
244 241
245 - // 按文档:以 success 判断;message 作为失败提示  
246 - var ok = dto?.success == true; 242 + var dto = JsonSerializer.Deserialize<ScanByBarcodeResp>(json, _opt);
  243 + var ok = dto?.success == true || dto?.result?.ToString() == "true";
247 return new SimpleOk(ok, dto?.message); 244 return new SimpleOk(ok, dto?.message);
248 } 245 }
249 - public async Task<SimpleOk> ScanConfirmAsync(string outstockId, CancellationToken ct = default) 246 +
  247 + public async Task<SimpleOk> ScanConfirmAsync(IEnumerable<(string barcode, string id)> items, CancellationToken ct = default)
250 { 248 {
251 - var bodyJson = JsonSerializer.Serialize(new { outstockId }); 249 + var payload = items.Select(x => new { barcode = x.barcode, id = x.id });
  250 + var bodyJson = JsonSerializer.Serialize(payload);
252 using var req = new HttpRequestMessage(HttpMethod.Post, _scanConfirmEndpoint) 251 using var req = new HttpRequestMessage(HttpMethod.Post, _scanConfirmEndpoint)
253 { 252 {
254 Content = new StringContent(bodyJson, Encoding.UTF8, "application/json") 253 Content = new StringContent(bodyJson, Encoding.UTF8, "application/json")
@@ -256,17 +255,16 @@ public sealed class OutboundMaterialService : IOutboundMaterialService @@ -256,17 +255,16 @@ public sealed class OutboundMaterialService : IOutboundMaterialService
256 255
257 using var res = await _http.SendAsync(req, ct); 256 using var res = await _http.SendAsync(req, ct);
258 var json = await res.Content.ReadAsStringAsync(ct); 257 var json = await res.Content.ReadAsStringAsync(ct);
  258 + var dto = JsonSerializer.Deserialize<ScanConfirmResp>(json, _opt);
259 259
260 - var opt = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };  
261 - var dto = JsonSerializer.Deserialize<ScanConfirmResp>(json, opt);  
262 -  
263 - var ok = dto?.success == true; 260 + var ok = dto?.success == true; // 你的接口:success=true 且 result=true
264 return new SimpleOk(ok, dto?.message); 261 return new SimpleOk(ok, dto?.message);
265 } 262 }
266 263
267 - public async Task<SimpleOk> CancelScanAsync(string outstockId, CancellationToken ct = default) 264 + public async Task<SimpleOk> CancelScanAsync(IEnumerable<(string barcode, string id)> items, CancellationToken ct = default)
268 { 265 {
269 - var bodyJson = JsonSerializer.Serialize(new { outstockId }); 266 + var payload = items.Select(x => new { barcode = x.barcode, id = x.id });
  267 + var bodyJson = JsonSerializer.Serialize(payload);
270 using var req = new HttpRequestMessage(HttpMethod.Post, _cancelScanEndpoint) 268 using var req = new HttpRequestMessage(HttpMethod.Post, _cancelScanEndpoint)
271 { 269 {
272 Content = new StringContent(bodyJson, Encoding.UTF8, "application/json") 270 Content = new StringContent(bodyJson, Encoding.UTF8, "application/json")
@@ -274,17 +272,16 @@ public sealed class OutboundMaterialService : IOutboundMaterialService @@ -274,17 +272,16 @@ public sealed class OutboundMaterialService : IOutboundMaterialService
274 272
275 using var res = await _http.SendAsync(req, ct); 273 using var res = await _http.SendAsync(req, ct);
276 var json = await res.Content.ReadAsStringAsync(ct); 274 var json = await res.Content.ReadAsStringAsync(ct);
277 -  
278 - var opt = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };  
279 - var dto = JsonSerializer.Deserialize<CancelScanResp>(json, opt); 275 + var dto = JsonSerializer.Deserialize<CancelScanResp>(json, _opt);
280 276
281 var ok = dto?.success == true; 277 var ok = dto?.success == true;
282 return new SimpleOk(ok, dto?.message); 278 return new SimpleOk(ok, dto?.message);
283 } 279 }
284 280
  281 +
285 public async Task<SimpleOk> ConfirmOutstockAsync(string outstockId, CancellationToken ct = default) 282 public async Task<SimpleOk> ConfirmOutstockAsync(string outstockId, CancellationToken ct = default)
286 { 283 {
287 - var bodyJson = JsonSerializer.Serialize(new { outstockId }); 284 + var bodyJson = JsonSerializer.Serialize(new { id = outstockId });
288 using var req = new HttpRequestMessage(HttpMethod.Post, _confirmOutstockEndpoint) 285 using var req = new HttpRequestMessage(HttpMethod.Post, _confirmOutstockEndpoint)
289 { 286 {
290 Content = new StringContent(bodyJson, Encoding.UTF8, "application/json") 287 Content = new StringContent(bodyJson, Encoding.UTF8, "application/json")
@@ -299,6 +296,12 @@ public sealed class OutboundMaterialService : IOutboundMaterialService @@ -299,6 +296,12 @@ public sealed class OutboundMaterialService : IOutboundMaterialService
299 var ok = dto?.success == true; 296 var ok = dto?.success == true;
300 return new SimpleOk(ok, dto?.message); 297 return new SimpleOk(ok, dto?.message);
301 } 298 }
  299 + /// <summary>
  300 + /// 判断入库单明细是否已全部扫描确认
  301 + /// </summary>
  302 + /// <param name="outstockId"></param>
  303 + /// <param name="ct"></param>
  304 + /// <returns></returns>
302 public async Task<bool> JudgeOutstockDetailScanAllAsync(string outstockId, CancellationToken ct = default) 305 public async Task<bool> JudgeOutstockDetailScanAllAsync(string outstockId, CancellationToken ct = default)
303 { 306 {
304 var url = $"{_judgeScanAllEndpoint}?id={Uri.EscapeDataString(outstockId)}"; 307 var url = $"{_judgeScanAllEndpoint}?id={Uri.EscapeDataString(outstockId)}";
@@ -313,116 +316,147 @@ public sealed class OutboundMaterialService : IOutboundMaterialService @@ -313,116 +316,147 @@ public sealed class OutboundMaterialService : IOutboundMaterialService
313 return dto?.result == true; 316 return dto?.result == true;
314 } 317 }
315 318
316 -}  
317 319
318 -public class GetOutStockReq  
319 -{  
320 - public string? createdTime { get; set; }  
321 - public string? endTime { get; set; }  
322 - public string? outstockNo { get; set; }  
323 - public string? orderType { get; set; }  
324 - public string? startTime { get; set; }  
325 -}  
326 -  
327 -public class GetOutStockResp  
328 -{  
329 - public int code { get; set; }  
330 - public long costTime { get; set; }  
331 - public string? message { get; set; }  
332 - public bool success { get; set; }  
333 - public List<GetOutStockItem>? result { get; set; }  
334 -} 320 + public async Task<SimpleOk> UpdateOutstockLocationAsync(
  321 + string detailId, string id, string outstockWarehouse, string outstockWarehouseCode, string location, CancellationToken ct = default)
  322 + {
  323 + var url = "/normalService/pda/wmsMaterialOutstock/updateLocation";
  324 + var payload = new
  325 + {
  326 + detailId,
  327 + id,
  328 + outstockWarehouse,
  329 + outstockWarehouseCode,
  330 + location
  331 + };
335 332
336 -public class GetOutStockItem  
337 -{  
338 - public string? arrivalNo { get; set; }  
339 - public string? createdTime { get; set; }  
340 - public string? outstockId { get; set; }  
341 - public string? outstockNo { get; set; }  
342 - public string? orderType { get; set; }  
343 - public string? purchaseNo { get; set; }  
344 - public string? supplierName { get; set; }  
345 -}  
346 -public sealed class GetOutStockDetailResp  
347 -{  
348 - public bool success { get; set; }  
349 - public string? message { get; set; }  
350 - public int? code { get; set; }  
351 - public List<GetOutStockDetailItem>? result { get; set; }  
352 - public int? costTime { get; set; }  
353 -}  
354 -public sealed class GetOutStockDetailItem  
355 -{  
356 - public string? id { get; set; } // 入库单明细主键id  
357 - public string? outstockNo { get; set; } // 入库单号  
358 - public string? materialCode { get; set; }  
359 - public string? materialName { get; set; }  
360 - public string? spec { get; set; }  
361 - public string? stockBatch { get; set; }  
362 - public string? outstockQty { get; set; } // 预计数量(字符串/可能为空)  
363 - public string? outstockWarehouseCode { get; set; } // 入库仓库编码  
364 - public string? location { get; set; } // 内点库位  
365 - public string? qty { get; set; } // 已扫描量(字符串/可能为空)  
366 -} 333 + var json = JsonSerializer.Serialize(payload);
  334 + using var req = new HttpRequestMessage(HttpMethod.Post, url)
  335 + {
  336 + Content = new StringContent(json, Encoding.UTF8, "application/json")
  337 + };
367 338
  339 + using var res = await _http.SendAsync(req, ct);
  340 + var body = await res.Content.ReadAsStringAsync(ct);
368 341
  342 + // 假设响应:{ code, message, result: true/false, success: true/false }
  343 + var dto = JsonSerializer.Deserialize<UpdateLocationResp>(body, _json);
  344 + var ok = dto?.success == true || dto?.result == true;
369 345
  346 + return new SimpleOk(ok, dto?.message);
  347 + }
370 348
  349 + public async Task<SimpleOk> UpdateQuantityAsync(
  350 + string barcode, string detailId, string id, int quantity, CancellationToken ct = default)
  351 + {
  352 + var url = "/normalService/pda/wmsMaterialOutstock/updateQuantity";
  353 + var payload = new { barcode, detailId, id, quantity };
  354 + var json = JsonSerializer.Serialize(payload);
  355 + using var req = new HttpRequestMessage(HttpMethod.Post, url)
  356 + {
  357 + Content = new StringContent(json, Encoding.UTF8, "application/json")
  358 + };
371 359
  360 + using var res = await _http.SendAsync(req, ct);
  361 + var body = await res.Content.ReadAsStringAsync(ct);
372 362
373 -public class GetOutStockPageResp  
374 -{  
375 - public int code { get; set; }  
376 - public long costTime { get; set; }  
377 - public string? message { get; set; }  
378 - public bool success { get; set; }  
379 - public GetOutStockPageData? result { get; set; }  
380 -} 363 + // 响应格式:{ success, message, code, result, ... }(与截图一致)
  364 + var dto = JsonSerializer.Deserialize<ConfirmResp>(body, _json);
  365 + var ok = dto?.success == true || dto?.result == true;
  366 + return new SimpleOk(ok, dto?.message);
  367 + }
381 368
382 -public class GetOutStockPageData  
383 -{  
384 - public int pageNo { get; set; }  
385 - public int pageSize { get; set; }  
386 - public long total { get; set; }  
387 - public List<GetOutStockRecord> records { get; set; } = new();  
388 -} 369 + public class GetOutStockItem
  370 + {
  371 + public string? arrivalNo { get; set; }
  372 + public string? createdTime { get; set; }
  373 + public string? outstockId { get; set; }
  374 + public string? outstockNo { get; set; }
  375 + public string? orderType { get; set; }
  376 + public string? purchaseNo { get; set; }
  377 + public string? supplierName { get; set; }
  378 + }
  379 + public sealed class GetOutStockDetailResp
  380 + {
  381 + public bool success { get; set; }
  382 + public string? message { get; set; }
  383 + public int? code { get; set; }
  384 + public List<GetOutStockDetailItem>? result { get; set; }
  385 + public int? costTime { get; set; }
  386 + }
  387 + public sealed class GetOutStockDetailItem
  388 + {
  389 + public string? id { get; set; } // 入库单明细主键id
  390 + public string? outstockNo { get; set; } // 入库单号
  391 + public string? materialName { get; set; }
  392 + public string? outstockWarehouseCode { get; set; } // 入库仓库编码
  393 + public string? materialCode { get; set; } //产品编码
  394 + public string? spec { get; set; } //规格
  395 + public string? location { get; set; } //出库库位
  396 + public string? productionBatch { get; set; } //生产批号
  397 +
  398 + public string? stockBatch { get; set; } //批次号
  399 + public int outstockQty { get; set; } //出库数量
  400 + public int qty { get; set; } //已扫描数
  401 + }
389 402
390 -public class GetOutStockRecord  
391 -{  
392 - public string? id { get; set; }  
393 - public string? outstockNo { get; set; }  
394 - public string? orderType { get; set; }  
395 - public string? orderTypeName { get; set; }  
396 - public string? supplierName { get; set; }  
397 - public string? arrivalNo { get; set; }  
398 - public string? purchaseNo { get; set; }  
399 - public string? createdTime { get; set; }  
400 - public string? deliveryNo { get; set; }  
401 - public string? requisitionMaterialNo { get; set; }  
402 - public string? returnNo { get; set; }  
403 - public string? workOrderNo { get; set; }  
404 -}  
405 -public sealed class GetOutStockScanDetailResp  
406 -{  
407 - public bool success { get; set; }  
408 - public string? message { get; set; }  
409 - public int? code { get; set; }  
410 - public List<GetOutStockScanDetailItem>? result { get; set; }  
411 - public int? costTime { get; set; }  
412 -} 403 + private sealed class UpdateLocationResp
  404 + {
  405 + public int code { get; set; }
  406 + public string? message { get; set; }
  407 + public bool? result { get; set; }
  408 + public bool? success { get; set; }
  409 + }
413 410
414 -public sealed class GetOutStockScanDetailItem  
415 -{  
416 - public string? id { get; set; } // 入库单明细主键 id  
417 - public string? barcode { get; set; }  
418 - public string? materialName { get; set; }  
419 - public string? spec { get; set; }  
420 - public string? qty { get; set; } // 可能是 null 或 “数字字符串”  
421 - public string? warehouseCode { get; set; }  
422 - public string? location { get; set; }  
423 - public bool? scanStatus { get; set; } // 可能为 null,按 false 处理  
424 -} 411 + public class GetOutStockPageResp
  412 + {
  413 + public int code { get; set; }
  414 + public long costTime { get; set; }
  415 + public string? message { get; set; }
  416 + public bool success { get; set; }
  417 + public GetOutStockPageData? result { get; set; }
  418 + }
425 419
  420 + public class GetOutStockPageData
  421 + {
  422 + public int pageNo { get; set; }
  423 + public int pageSize { get; set; }
  424 + public long total { get; set; }
  425 + public List<GetOutStockRecord> records { get; set; } = new();
  426 + }
426 427
  428 + public class GetOutStockRecord
  429 + {
  430 + public string? id { get; set; }
  431 + public string? outstockNo { get; set; }
  432 + public string? orderType { get; set; }
  433 + public string? orderTypeName { get; set; }
  434 + public string? workOrderNo { get; set; }
  435 + public string? materialName { get; set; }
  436 + public string? requisitionMaterialNo { get; set; }
  437 + public string? returnNo { get; set; }
  438 + public string? deliveryNo { get; set; }
  439 + public string? createdTime { get; set; }
  440 + }
  441 + public sealed class GetOutStockScanDetailResp
  442 + {
  443 + public bool success { get; set; }
  444 + public string? message { get; set; }
  445 + public int? code { get; set; }
  446 + public List<GetOutStockScanDetailItem>? result { get; set; }
  447 + public int? costTime { get; set; }
  448 + }
427 449
  450 + public class GetOutStockScanDetailItem
  451 + {
  452 + public string? id { get; set; } // 入库单明细主键 id
  453 + public string? barcode { get; set; }
  454 + public string? materialName { get; set; }
  455 + public string? spec { get; set; }
  456 + public decimal? qty { get; set; } // 可能是 null 或 “数字字符串”
  457 + public string? warehouseCode { get; set; }
  458 + public string? location { get; set; }
  459 + public bool? scanStatus { get; set; } // 可能为 null,按 false 处理
  460 + }
428 461
  462 +}
@@ -22,7 +22,7 @@ namespace IndustrialControl.ViewModels @@ -22,7 +22,7 @@ namespace IndustrialControl.ViewModels
22 // 列表数据源 22 // 列表数据源
23 public ObservableCollection<string> AvailableBins { get; } = new(); 23 public ObservableCollection<string> AvailableBins { get; } = new();
24 public ObservableCollection<OutScannedItem> ScannedList { get; } = new(); 24 public ObservableCollection<OutScannedItem> ScannedList { get; } = new();
25 - public ObservableCollection<PendingItem> PendingList { get; } = new(); 25 + public ObservableCollection<OutPendingItem> PendingList { get; } = new();
26 26
27 [ObservableProperty] private OutScannedItem? selectedScanItem; 27 [ObservableProperty] private OutScannedItem? selectedScanItem;
28 28
@@ -100,5 +100,8 @@ public record InboundOrderSummary( @@ -100,5 +100,8 @@ public record InboundOrderSummary(
100 string purchaseNo, 100 string purchaseNo,
101 string supplierName, 101 string supplierName,
102 string arrivalNo, 102 string arrivalNo,
  103 + string workOrderNo,
  104 + string materialName,
  105 + int instockQty,
103 string createdTime 106 string createdTime
104 ); 107 );
@@ -362,8 +362,11 @@ namespace IndustrialControl.ViewModels @@ -362,8 +362,11 @@ namespace IndustrialControl.ViewModels
362 // === 列表行模型 === 362 // === 列表行模型 ===
363 public class PendingItem 363 public class PendingItem
364 { 364 {
365 - public string Name { get; set; } = "";  
366 - public string Spec { get; set; } = ""; 365 + public string Name { get; set; } = ""; //产品名称
  366 + public string MaterialCode { get; set; } = ""; //产品编码
  367 + public string Spec { get; set; } = "";//规格
  368 + public string Location { get; set; } = "";//出库库位
  369 + public string ProductionBatch { get; set; } = "";//生产批号
367 public int PendingQty { get; set; } 370 public int PendingQty { get; set; }
368 public string Bin { get; set; } = "请选择"; 371 public string Bin { get; set; } = "请选择";
369 public int ScannedQty { get; set; } 372 public int ScannedQty { get; set; }
@@ -81,7 +81,10 @@ public partial class InboundProductionSearchViewModel : ObservableObject @@ -81,7 +81,10 @@ public partial class InboundProductionSearchViewModel : ObservableObject
81 ["purchaseNo"] = o.purchaseNo, 81 ["purchaseNo"] = o.purchaseNo,
82 ["supplierName"] = o.supplierName, 82 ["supplierName"] = o.supplierName,
83 ["arrivalNo"] = o.arrivalNo, 83 ["arrivalNo"] = o.arrivalNo,
84 - ["createdTime"] = o.createdTime 84 + ["instockQty"] = o.instockQty,
  85 + ["materialName"] = o.materialName,
  86 + ["workOrderNo"] = o.workOrderNo,
  87 + ["createdTime"] = o.createdTime
85 }); 88 });
86 89
87 } 90 }
@@ -18,6 +18,9 @@ namespace IndustrialControl.ViewModels @@ -18,6 +18,9 @@ namespace IndustrialControl.ViewModels
18 [ObservableProperty] private string? orderTypeName; 18 [ObservableProperty] private string? orderTypeName;
19 [ObservableProperty] private string? purchaseNo; 19 [ObservableProperty] private string? purchaseNo;
20 [ObservableProperty] private string? supplierName; 20 [ObservableProperty] private string? supplierName;
  21 + [ObservableProperty] private string? workOrderNo;
  22 + [ObservableProperty] private string? materialName;
  23 + [ObservableProperty] private int instockQty;
21 [ObservableProperty] private string? createdTime; 24 [ObservableProperty] private string? createdTime;
22 25
23 // 列表数据源 26 // 列表数据源
@@ -41,7 +44,7 @@ namespace IndustrialControl.ViewModels @@ -41,7 +44,7 @@ namespace IndustrialControl.ViewModels
41 // ================ 初始化入口(页面 OnAppearing 调用) ================ 44 // ================ 初始化入口(页面 OnAppearing 调用) ================
42 public async Task InitializeFromSearchAsync( 45 public async Task InitializeFromSearchAsync(
43 string instockId, string instockNo, string orderType, string orderTypeName, 46 string instockId, string instockNo, string orderType, string orderTypeName,
44 - string purchaseNo, string supplierName, string createdTime) 47 + string purchaseNo, string supplierName, string createdTime,string workOrderNo,string materialName,int instockQty)
45 { 48 {
46 // 1) 基础信息 49 // 1) 基础信息
47 InstockId = instockId; 50 InstockId = instockId;
@@ -51,7 +54,9 @@ namespace IndustrialControl.ViewModels @@ -51,7 +54,9 @@ namespace IndustrialControl.ViewModels
51 PurchaseNo = purchaseNo; 54 PurchaseNo = purchaseNo;
52 SupplierName = supplierName; 55 SupplierName = supplierName;
53 CreatedTime = createdTime; 56 CreatedTime = createdTime;
54 - 57 + WorkOrderNo = workOrderNo;
  58 + MaterialName = materialName;
  59 + InstockQty = instockQty;
55 // 2) 下拉库位(如无接口可留空或使用后端返回的 location 聚合) 60 // 2) 下拉库位(如无接口可留空或使用后端返回的 location 聚合)
56 AvailableBins.Clear(); 61 AvailableBins.Clear();
57 62
@@ -8,19 +8,20 @@ namespace IndustrialControl.ViewModels; @@ -8,19 +8,20 @@ namespace IndustrialControl.ViewModels;
8 public partial class OutboundFinishedSearchViewModel : ObservableObject 8 public partial class OutboundFinishedSearchViewModel : ObservableObject
9 { 9 {
10 private readonly IOutboundMaterialService _dataSvc; 10 private readonly IOutboundMaterialService _dataSvc;
11 - [ObservableProperty] private string? searchOrderNo; 11 +
  12 + [ObservableProperty] private string searchOrderNo;
12 [ObservableProperty] private DateTime startDate = DateTime.Today; 13 [ObservableProperty] private DateTime startDate = DateTime.Today;
13 [ObservableProperty] private DateTime endDate = DateTime.Today; 14 [ObservableProperty] private DateTime endDate = DateTime.Today;
14 - [ObservableProperty] private OutboundOrderSummary? selectedOrder;  
15 private CancellationTokenSource? _searchCts; 15 private CancellationTokenSource? _searchCts;
  16 + // 仅用于“高亮选中”
  17 + [ObservableProperty] private OutboundOrderSummary? selectedOrder;
  18 +
16 public OutboundFinishedSearchViewModel(IOutboundMaterialService dataSvc) 19 public OutboundFinishedSearchViewModel(IOutboundMaterialService dataSvc)
17 { 20 {
18 _dataSvc = dataSvc; 21 _dataSvc = dataSvc;
19 Orders = new ObservableCollection<OutboundOrderSummary>(); 22 Orders = new ObservableCollection<OutboundOrderSummary>();
20 } 23 }
21 24
22 -  
23 -  
24 public ObservableCollection<OutboundOrderSummary> Orders { get; } 25 public ObservableCollection<OutboundOrderSummary> Orders { get; }
25 26
26 [RelayCommand] 27 [RelayCommand]
@@ -31,9 +32,15 @@ public partial class OutboundFinishedSearchViewModel : ObservableObject @@ -31,9 +32,15 @@ public partial class OutboundFinishedSearchViewModel : ObservableObject
31 var ct = _searchCts.Token; 32 var ct = _searchCts.Token;
32 try 33 try
33 { 34 {
34 - var list = await _dataSvc.ListOutboundOrdersAsync(SearchOrderNo, startDate, endDate, "out_delivery", null,ct); 35 + var list = await _dataSvc.ListOutboundOrdersAsync(
  36 + searchOrderNo, // 单号/条码
  37 + startDate, // 开始日期
  38 + endDate, // 结束日期(Service 内会扩到 23:59:59)
  39 + "out_delivery", // 不传单值 orderType,用 null 更清晰
  40 + null, // 多类型数组
  41 + ct // ← 新增:取消令牌
  42 + );
35 43
36 - // ★ 在主线程更新 ObservableCollection,避免看起来“没刷新”  
37 await MainThread.InvokeOnMainThreadAsync(() => 44 await MainThread.InvokeOnMainThreadAsync(() =>
38 { 45 {
39 Orders.Clear(); 46 Orders.Clear();
@@ -44,7 +51,6 @@ public partial class OutboundFinishedSearchViewModel : ObservableObject @@ -44,7 +51,6 @@ public partial class OutboundFinishedSearchViewModel : ObservableObject
44 } 51 }
45 }); 52 });
46 53
47 - // (排查辅助)无数据时提示一下,确认命令确实执行了  
48 if (list == null || !list.Any()) 54 if (list == null || !list.Any())
49 await Shell.Current.DisplayAlert("提示", "未查询到任何入库单", "确定"); 55 await Shell.Current.DisplayAlert("提示", "未查询到任何入库单", "确定");
50 } 56 }
@@ -54,38 +60,34 @@ public partial class OutboundFinishedSearchViewModel : ObservableObject @@ -54,38 +60,34 @@ public partial class OutboundFinishedSearchViewModel : ObservableObject
54 } 60 }
55 } 61 }
56 62
57 - 63 + // === 方案A:命令接收“当前项”作为参数,不依赖 SelectedOrder ===
58 [RelayCommand(CanExecute = nameof(CanGoOutbound))] 64 [RelayCommand(CanExecute = nameof(CanGoOutbound))]
59 private async Task GoOutboundAsync(OutboundOrderSummary? item) 65 private async Task GoOutboundAsync(OutboundOrderSummary? item)
60 { 66 {
61 - if (SelectedOrder is null) return; 67 + if (item is null) return;
62 68
63 static string E(string? v) => Uri.EscapeDataString(v ?? ""); 69 static string E(string? v) => Uri.EscapeDataString(v ?? "");
64 70
65 - var o = SelectedOrder; // 确保 SelectedOrder 包含以下字段  
66 - var url =  
67 - $"//OutboundFinished" +  
68 - $"?outstockId={E(o.outstockId)}" +  
69 - $"&outstockNo={E(searchOrderNo)}" +  
70 - $"&orderType={E(o.orderType)}" +  
71 - $"&orderTypeName={E(o.orderTypeName)}" +  
72 - $"&purchaseNo={E(o.purchaseNo)}" +  
73 - $"&supplierName={E(o.supplierName)}" +  
74 - $"&createdTime={E(o.createdTime)}";  
75 -  
76 - await Shell.Current.GoToAsync(url); 71 + var o = item;
  72 +
  73 + await Shell.Current.GoToAsync(
  74 + nameof(Pages.OutboundFinishedPage),
  75 + new Dictionary<string, object>
  76 + {
  77 + ["outstockId"] = o.outstockId,
  78 + ["outstockNo"] = o.outstockNo,
  79 + ["orderType"] = o.orderType,
  80 + ["orderTypeName"] = o.orderTypeName,
  81 + ["requisitionMaterialNo"] = o.requisitionMaterialNo,
  82 + ["returnNo"] = o.returnNo,
  83 + ["deliveryNo"] = o.deliveryNo,
  84 + ["createdTime"] = o.createdTime
  85 + });
  86 +
77 } 87 }
78 88
  89 + // 与命令同签名的 CanExecute
79 private bool CanGoOutbound(OutboundOrderSummary? item) => item != null; 90 private bool CanGoOutbound(OutboundOrderSummary? item) => item != null;
80 -  
81 } 91 }
82 -public record OutboundFinishedSummary(  
83 - string outstockId,  
84 - string outstockNo,  
85 - string orderType,  
86 - string orderTypeName,  
87 - string purchaseNo,  
88 - string supplierName,  
89 - string createdTime  
90 -); 92 +
91 93
1 using CommunityToolkit.Mvvm.ComponentModel; 1 using CommunityToolkit.Mvvm.ComponentModel;
2 using CommunityToolkit.Mvvm.Input; 2 using CommunityToolkit.Mvvm.Input;
3 -using IndustrialControl.Services;  
4 using System.Collections.ObjectModel; 3 using System.Collections.ObjectModel;
5 -using System.Linq;  
6 -using System.Threading.Tasks; 4 +using IndustrialControl.Services;
  5 +using IndustrialControl.Models;
7 6
8 namespace IndustrialControl.ViewModels 7 namespace IndustrialControl.ViewModels
9 { 8 {
@@ -17,16 +16,15 @@ namespace IndustrialControl.ViewModels @@ -17,16 +16,15 @@ namespace IndustrialControl.ViewModels
17 [ObservableProperty] private string? outstockNo; 16 [ObservableProperty] private string? outstockNo;
18 [ObservableProperty] private string? orderType; 17 [ObservableProperty] private string? orderType;
19 [ObservableProperty] private string? orderTypeName; 18 [ObservableProperty] private string? orderTypeName;
20 - [ObservableProperty] private string? purchaseNo;  
21 - [ObservableProperty] private string? supplierName;  
22 - [ObservableProperty] private string? createdTime;  
23 [ObservableProperty] private string? requisitionMaterialNo; 19 [ObservableProperty] private string? requisitionMaterialNo;
24 [ObservableProperty] private string? returnNo; 20 [ObservableProperty] private string? returnNo;
25 - [ObservableProperty] private string? workOrderNo; 21 + [ObservableProperty] private string? deliveryNo;
  22 + [ObservableProperty] private string? createdTime;
  23 +
26 // 列表数据源 24 // 列表数据源
27 public ObservableCollection<string> AvailableBins { get; } = new(); 25 public ObservableCollection<string> AvailableBins { get; } = new();
28 public ObservableCollection<OutScannedItem> ScannedList { get; } = new(); 26 public ObservableCollection<OutScannedItem> ScannedList { get; } = new();
29 - public ObservableCollection<PendingItem> PendingList { get; } = new(); 27 + public ObservableCollection<OutPendingItem> PendingList { get; } = new();
30 28
31 [ObservableProperty] private OutScannedItem? selectedScanItem; 29 [ObservableProperty] private OutScannedItem? selectedScanItem;
32 30
@@ -43,28 +41,27 @@ namespace IndustrialControl.ViewModels @@ -43,28 +41,27 @@ namespace IndustrialControl.ViewModels
43 // 命令 41 // 命令
44 public IRelayCommand ShowPendingCommand { get; } 42 public IRelayCommand ShowPendingCommand { get; }
45 public IRelayCommand ShowScannedCommand { get; } 43 public IRelayCommand ShowScannedCommand { get; }
46 - public IAsyncRelayCommand ConfirmCommand { get; }  
47 44
48 - public OutboundFinishedViewModel(IOutboundMaterialService warehouseSvc) 45 + public OutboundFinishedViewModel(IOutboundMaterialService api)
49 { 46 {
50 - _api = warehouseSvc; 47 + _api = api;
51 ShowPendingCommand = new RelayCommand(() => SwitchTab(true)); 48 ShowPendingCommand = new RelayCommand(() => SwitchTab(true));
52 ShowScannedCommand = new RelayCommand(() => SwitchTab(false)); 49 ShowScannedCommand = new RelayCommand(() => SwitchTab(false));
53 - //ConfirmCommand = new AsyncRelayCommand(ConfirmInboundAsync);  
54 } 50 }
55 51
56 // ================ 初始化入口(页面 OnAppearing 调用) ================ 52 // ================ 初始化入口(页面 OnAppearing 调用) ================
57 public async Task InitializeFromSearchAsync( 53 public async Task InitializeFromSearchAsync(
58 string outstockId, string outstockNo, string orderType, string orderTypeName, 54 string outstockId, string outstockNo, string orderType, string orderTypeName,
59 - string purchaseNo, string supplierName, string createdTime) 55 + string requisitionMaterialNo, string returnNo, string deliveryNo, string createdTime)
60 { 56 {
61 // 1) 基础信息 57 // 1) 基础信息
62 OutstockId = outstockId; 58 OutstockId = outstockId;
63 OutstockNo = outstockNo; 59 OutstockNo = outstockNo;
64 OrderType = orderType; 60 OrderType = orderType;
65 OrderTypeName = orderTypeName; 61 OrderTypeName = orderTypeName;
66 - PurchaseNo = purchaseNo;  
67 - SupplierName = supplierName; 62 + RequisitionMaterialNo = requisitionMaterialNo;
  63 + ReturnNo = returnNo;
  64 + DeliveryNo = deliveryNo;
68 CreatedTime = createdTime; 65 CreatedTime = createdTime;
69 66
70 // 2) 下拉库位(如无接口可留空或使用后端返回的 location 聚合) 67 // 2) 下拉库位(如无接口可留空或使用后端返回的 location 聚合)
@@ -102,15 +99,19 @@ namespace IndustrialControl.ViewModels @@ -102,15 +99,19 @@ namespace IndustrialControl.ViewModels
102 var rows = await _api.GetOutStockDetailAsync(OutstockId!); 99 var rows = await _api.GetOutStockDetailAsync(OutstockId!);
103 foreach (var r in rows) 100 foreach (var r in rows)
104 { 101 {
105 - PendingList.Add(new PendingItem 102 + PendingList.Add(new OutPendingItem
106 { 103 {
107 Name = r.MaterialName ?? "", 104 Name = r.MaterialName ?? "",
  105 + MaterialCode = r.MaterialCode ?? "",
108 Spec = r.Spec ?? "", 106 Spec = r.Spec ?? "",
109 - PendingQty = r.PendingQty,  
110 - Bin = string.IsNullOrWhiteSpace(r.Location) ? "请选择" : r.Location!,  
111 - ScannedQty = r.ScannedQty 107 + Location = r.Location ?? "",
  108 + ProductionBatch = r.ProductionBatch ?? "",
  109 + StockBatch = r.StockBatch ?? "",
  110 + OutstockQty = r.OutstockQty,
  111 + Qty = r.Qty
112 }); 112 });
113 113
  114 +
114 // 聚合可选库位 115 // 聚合可选库位
115 if (!string.IsNullOrWhiteSpace(r.Location) && !AvailableBins.Contains(r.Location)) 116 if (!string.IsNullOrWhiteSpace(r.Location) && !AvailableBins.Contains(r.Location))
116 AvailableBins.Add(r.Location); 117 AvailableBins.Add(r.Location);
@@ -132,7 +133,11 @@ namespace IndustrialControl.ViewModels @@ -132,7 +133,11 @@ namespace IndustrialControl.ViewModels
132 Name = r.MaterialName ?? "", 133 Name = r.MaterialName ?? "",
133 Spec = r.Spec ?? "", 134 Spec = r.Spec ?? "",
134 Location = string.IsNullOrWhiteSpace(r.Location) ? "请选择" : r.Location!, 135 Location = string.IsNullOrWhiteSpace(r.Location) ? "请选择" : r.Location!,
135 - Qty = r.Qty 136 + Qty = r.Qty,
  137 + ScanStatus = r.ScanStatus,
  138 + WarehouseCode = r.WarehouseCode ?? "",
  139 + DetailId = r.DetailId,
  140 + Id = OutstockId
136 }); 141 });
137 142
138 if (!string.IsNullOrWhiteSpace(r.Location) && !AvailableBins.Contains(r.Location)) 143 if (!string.IsNullOrWhiteSpace(r.Location) && !AvailableBins.Contains(r.Location))
@@ -140,83 +145,48 @@ namespace IndustrialControl.ViewModels @@ -140,83 +145,48 @@ namespace IndustrialControl.ViewModels
140 } 145 }
141 } 146 }
142 147
  148 + // OutboundFinishedViewModel.cs
  149 +
143 [RelayCommand] 150 [RelayCommand]
144 private async Task PassScan() 151 private async Task PassScan()
145 { 152 {
146 - if (string.IsNullOrWhiteSpace(OutstockId))  
147 - {  
148 - await ShowTip("缺少 OutstockId,无法确认。请从查询页进入。");  
149 - return;  
150 - }  
151 -  
152 - // 依旧要求只能选中一行,作为“操作目标”的 UI 约束(接口本身仅需 instockId)  
153 - var selected = ScannedList.Where(x => x.IsSelected).ToList();  
154 - if (selected.Count != 1)  
155 - {  
156 - await ShowTip(selected.Count == 0 ? "请先勾选一条已扫描记录。" : "一次只能操作一条记录。");  
157 - return;  
158 - }  
159 - var selectedBarcode = selected[0].Barcode; 153 + var picks = ScannedList.Where(x => x.IsSelected).ToList();
  154 + if (picks.Count == 0) { await ShowTip("请先勾选至少一条已扫描记录。"); return; }
160 155
161 - // 调用确认接口  
162 - var resp = await _api.ScanConfirmAsync(OutstockId!); 156 + // 组装 [{ barcode, id }]
  157 + var items = picks.Select(x => (barcode: x.Barcode, id: x.Id)).ToList();
  158 + var resp = await _api.ScanConfirmAsync(items);
163 if (!resp.Succeeded) 159 if (!resp.Succeeded)
164 { 160 {
165 await ShowTip(string.IsNullOrWhiteSpace(resp.Message) ? "扫描通过失败,请重试。" : resp.Message!); 161 await ShowTip(string.IsNullOrWhiteSpace(resp.Message) ? "扫描通过失败,请重试。" : resp.Message!);
166 return; 162 return;
167 } 163 }
168 164
169 - // 成功:刷新两张表  
170 await LoadPendingAsync(); 165 await LoadPendingAsync();
171 await LoadScannedAsync(); 166 await LoadScannedAsync();
172 -  
173 - // 友好:恢复选中刚才那条(如果还在列表里)  
174 - var hit = ScannedList.FirstOrDefault(x =>  
175 - string.Equals(x.Barcode, selectedBarcode, StringComparison.OrdinalIgnoreCase));  
176 - if (hit != null) { hit.IsSelected = true; SelectedScanItem = hit; }  
177 -  
178 await ShowTip("已确认通过。"); 167 await ShowTip("已确认通过。");
179 } 168 }
180 169
181 -  
182 [RelayCommand] 170 [RelayCommand]
183 private async Task CancelScan() 171 private async Task CancelScan()
184 { 172 {
185 - if (string.IsNullOrWhiteSpace(OutstockId))  
186 - {  
187 - await ShowTip("缺少 OutstockId,无法取消。请从查询页进入。");  
188 - return;  
189 - }  
190 -  
191 - // 依旧限制一次只操作一条(接口本身只要 instockId,这里是 UI 规范)  
192 - var selected = ScannedList.Where(x => x.IsSelected).ToList();  
193 - if (selected.Count != 1)  
194 - {  
195 - await ShowTip(selected.Count == 0 ? "请先勾选一条已扫描记录。" : "一次只能操作一条记录。");  
196 - return;  
197 - }  
198 - var selectedBarcode = selected[0].Barcode;  
199 -  
200 - var resp = await _api.CancelScanAsync(OutstockId!); 173 + var picks = ScannedList.Where(x => x.IsSelected).ToList();
  174 + if (picks.Count == 0) { await ShowTip("请先勾选至少一条记录。"); return; }
  175 + var items = picks.Select(x => (barcode: x.Barcode, id: x.Id)).ToList();
  176 + var resp = await _api.CancelScanAsync(items);
201 if (!resp.Succeeded) 177 if (!resp.Succeeded)
202 { 178 {
203 await ShowTip(string.IsNullOrWhiteSpace(resp.Message) ? "取消扫描失败,请重试。" : resp.Message!); 179 await ShowTip(string.IsNullOrWhiteSpace(resp.Message) ? "取消扫描失败,请重试。" : resp.Message!);
204 return; 180 return;
205 } 181 }
206 182
207 - // 成功后以服务端为准刷新两张表  
208 await LoadPendingAsync(); 183 await LoadPendingAsync();
209 await LoadScannedAsync(); 184 await LoadScannedAsync();
210 -  
211 - // 友好:若那条还在列表里,恢复选中  
212 - var hit = ScannedList.FirstOrDefault(x =>  
213 - string.Equals(x.Barcode, selectedBarcode, StringComparison.OrdinalIgnoreCase));  
214 - if (hit != null) { hit.IsSelected = true; SelectedScanItem = hit; }  
215 -  
216 await ShowTip("已取消扫描。"); 185 await ShowTip("已取消扫描。");
217 } 186 }
218 187
219 188
  189 +
220 public async Task HandleScannedAsync(string data, string symbology) 190 public async Task HandleScannedAsync(string data, string symbology)
221 { 191 {
222 var barcode = (data ?? string.Empty).Trim(); 192 var barcode = (data ?? string.Empty).Trim();
@@ -279,7 +249,12 @@ namespace IndustrialControl.ViewModels @@ -279,7 +249,12 @@ namespace IndustrialControl.ViewModels
279 } 249 }
280 } 250 }
281 251
282 - public async Task<bool> ConfirmInboundAsync() 252 + // OutboundFinishedViewModel.cs
  253 +
  254 + private Task<bool> AskAsync(string title, string message, string ok = "是", string cancel = "否") =>
  255 + Shell.Current?.DisplayAlert(title, message, ok, cancel) ?? Task.FromResult(false);
  256 +
  257 + public async Task<bool> ConfirmOutboundAsync()
283 { 258 {
284 if (string.IsNullOrWhiteSpace(OutstockId)) 259 if (string.IsNullOrWhiteSpace(OutstockId))
285 { 260 {
@@ -287,6 +262,17 @@ namespace IndustrialControl.ViewModels @@ -287,6 +262,17 @@ namespace IndustrialControl.ViewModels
287 return false; 262 return false;
288 } 263 }
289 264
  265 + // ② 服务端权威校验:是否全部扫码确认,后端接口
  266 + bool serverAllOk = await _api.JudgeOutstockDetailScanAllAsync(OutstockId!);
  267 +
  268 + // 任意一处不一致 → 提示是否继续
  269 + if (!serverAllOk)
  270 + {
  271 + bool goOn = await AskAsync("提示", "已扫描列表与待入库数量不一致,是否继续入库?");
  272 + if (!goOn) return false;
  273 + }
  274 +
  275 + // ③ 调用确认入库接口
290 var r = await _api.ConfirmOutstockAsync(OutstockId!); 276 var r = await _api.ConfirmOutstockAsync(OutstockId!);
291 if (!r.Succeeded) 277 if (!r.Succeeded)
292 { 278 {
@@ -294,12 +280,87 @@ namespace IndustrialControl.ViewModels @@ -294,12 +280,87 @@ namespace IndustrialControl.ViewModels
294 return false; 280 return false;
295 } 281 }
296 282
297 - // 成功:可选刷新一次,以服务端为准;随后按钮事件里会 ClearAll()  
298 - await LoadPendingAsync();  
299 - await LoadScannedAsync();  
300 return true; 283 return true;
301 } 284 }
302 285
  286 + public async Task<bool> UpdateRowLocationAsync(object row, BinInfo bin, CancellationToken ct = default)
  287 + {
  288 + if (row is null || bin is null) return false;
  289 +
  290 + // 通过反射从行对象里取必要字段(兼容不同命名)
  291 + var t = row.GetType();
  292 + string detailId =
  293 + t.GetProperty("DetailId")?.GetValue(row)?.ToString()
  294 + ?? t.GetProperty("detailId")?.GetValue(row)?.ToString()
  295 + ?? string.Empty;
  296 +
  297 + string id =
  298 + t.GetProperty("Id")?.GetValue(row)?.ToString()
  299 + ?? t.GetProperty("id")?.GetValue(row)?.ToString()
  300 + ?? string.Empty;
  301 +
  302 + if (string.IsNullOrWhiteSpace(detailId) || string.IsNullOrWhiteSpace(id))
  303 + {
  304 + await MainThread.InvokeOnMainThreadAsync(async () =>
  305 + await Application.Current.MainPage.DisplayAlert("提示", "缺少必要字段:detailId 或 id。", "确定"));
  306 + return false;
  307 + }
  308 +
  309 + // 组织参数
  310 + var outstockWarehouse = bin.WarehouseName ?? "";
  311 + var outstockWarehouseCode = bin.WarehouseCode ?? "";
  312 + var location = bin.Location ?? "";
  313 +
  314 + // 调用接口
  315 + var ok = await _api.UpdateOutstockLocationAsync(
  316 + detailId, id, outstockWarehouse, outstockWarehouseCode, location, ct);
  317 +
  318 + if (!ok.Succeeded)
  319 + {
  320 + await MainThread.InvokeOnMainThreadAsync(async () =>
  321 + await Application.Current.MainPage.DisplayAlert("提示", ok.Message ?? "更新库位失败", "确定"));
  322 + return false;
  323 + }
  324 +
  325 + return true;
  326 + }
  327 + public async Task<bool> UpdateQuantityForRowAsync(OutScannedItem row, CancellationToken ct = default)
  328 + {
  329 + if (row is null) return false;
  330 + if (!row.ScanStatus)
  331 + {
  332 + await ShowTip("该行尚未扫描通过,不能修改数量。");
  333 + return false;
  334 + }
  335 + if (row.Qty < 0)
  336 + {
  337 + await ShowTip("数量不能为负数。");
  338 + return false;
  339 + }
  340 +
  341 + var resp = await _api.UpdateQuantityAsync(row.Barcode, row.DetailId, row.Id, row.Qty, ct);
  342 + if (!resp.Succeeded)
  343 + {
  344 + await ShowTip(string.IsNullOrWhiteSpace(resp.Message) ? "更新数量失败" : resp.Message!);
  345 + return false;
  346 + }
  347 +
  348 + // ✅ 成功:先提示,再刷新两张表
  349 + await ShowTip("数量修改成功");
  350 +
  351 + // 记录一下当前行用于刷新后恢复选中
  352 + var keepBarcode = row.Barcode;
  353 +
  354 + await LoadPendingAsync(); // 刷新“待入库明细”
  355 + await LoadScannedAsync(); // 刷新“已扫描明细”
  356 +
  357 + var hit = ScannedList.FirstOrDefault(x => string.Equals(x.Barcode, keepBarcode, StringComparison.OrdinalIgnoreCase));
  358 + if (hit != null) { hit.IsSelected = true; SelectedScanItem = hit; }
  359 +
  360 + return true;
  361 + }
  362 +
  363 +
303 } 364 }
304 365
305 366
1 using CommunityToolkit.Mvvm.ComponentModel; 1 using CommunityToolkit.Mvvm.ComponentModel;
2 using CommunityToolkit.Mvvm.Input; 2 using CommunityToolkit.Mvvm.Input;
3 -using IndustrialControl.Pages;  
4 using IndustrialControl.Services; 3 using IndustrialControl.Services;
5 -using System;  
6 using System.Collections.ObjectModel; 4 using System.Collections.ObjectModel;
7 5
8 namespace IndustrialControl.ViewModels; 6 namespace IndustrialControl.ViewModels;
@@ -10,19 +8,20 @@ namespace IndustrialControl.ViewModels; @@ -10,19 +8,20 @@ namespace IndustrialControl.ViewModels;
10 public partial class OutboundMaterialSearchViewModel : ObservableObject 8 public partial class OutboundMaterialSearchViewModel : ObservableObject
11 { 9 {
12 private readonly IOutboundMaterialService _dataSvc; 10 private readonly IOutboundMaterialService _dataSvc;
13 - [ObservableProperty] private string? searchOrderNo; 11 +
  12 + [ObservableProperty] private string searchOrderNo;
14 [ObservableProperty] private DateTime startDate = DateTime.Today; 13 [ObservableProperty] private DateTime startDate = DateTime.Today;
15 [ObservableProperty] private DateTime endDate = DateTime.Today; 14 [ObservableProperty] private DateTime endDate = DateTime.Today;
16 - [ObservableProperty] private OutboundOrderSummary? selectedOrder;  
17 private CancellationTokenSource? _searchCts; 15 private CancellationTokenSource? _searchCts;
  16 + // 仅用于“高亮选中”
  17 + [ObservableProperty] private OutboundOrderSummary? selectedOrder;
  18 +
18 public OutboundMaterialSearchViewModel(IOutboundMaterialService dataSvc) 19 public OutboundMaterialSearchViewModel(IOutboundMaterialService dataSvc)
19 { 20 {
20 _dataSvc = dataSvc; 21 _dataSvc = dataSvc;
21 Orders = new ObservableCollection<OutboundOrderSummary>(); 22 Orders = new ObservableCollection<OutboundOrderSummary>();
22 } 23 }
23 24
24 -  
25 -  
26 public ObservableCollection<OutboundOrderSummary> Orders { get; } 25 public ObservableCollection<OutboundOrderSummary> Orders { get; }
27 26
28 [RelayCommand] 27 [RelayCommand]
@@ -33,10 +32,16 @@ public partial class OutboundMaterialSearchViewModel : ObservableObject @@ -33,10 +32,16 @@ public partial class OutboundMaterialSearchViewModel : ObservableObject
33 var ct = _searchCts.Token; 32 var ct = _searchCts.Token;
34 try 33 try
35 { 34 {
36 - var orderTypeList = new[] { "out_return", "out_return", "out_other" };  
37 - var list = await _dataSvc.ListOutboundOrdersAsync(SearchOrderNo, startDate, endDate, null, orderTypeList,ct); 35 + var orderTypeList = new[] { "out_return", "out_requisition", "out_other" };
  36 + var list = await _dataSvc.ListOutboundOrdersAsync(
  37 + searchOrderNo, // 单号/条码
  38 + startDate, // 开始日期
  39 + endDate, // 结束日期(Service 内会扩到 23:59:59)
  40 + null, // 不传单值 orderType,用 null 更清晰
  41 + orderTypeList, // 多类型数组
  42 + ct // ← 新增:取消令牌
  43 + );
38 44
39 - // ★ 在主线程更新 ObservableCollection,避免看起来“没刷新”  
40 await MainThread.InvokeOnMainThreadAsync(() => 45 await MainThread.InvokeOnMainThreadAsync(() =>
41 { 46 {
42 Orders.Clear(); 47 Orders.Clear();
@@ -47,7 +52,6 @@ public partial class OutboundMaterialSearchViewModel : ObservableObject @@ -47,7 +52,6 @@ public partial class OutboundMaterialSearchViewModel : ObservableObject
47 } 52 }
48 }); 53 });
49 54
50 - // (排查辅助)无数据时提示一下,确认命令确实执行了  
51 if (list == null || !list.Any()) 55 if (list == null || !list.Any())
52 await Shell.Current.DisplayAlert("提示", "未查询到任何入库单", "确定"); 56 await Shell.Current.DisplayAlert("提示", "未查询到任何入库单", "确定");
53 } 57 }
@@ -57,40 +61,45 @@ public partial class OutboundMaterialSearchViewModel : ObservableObject @@ -57,40 +61,45 @@ public partial class OutboundMaterialSearchViewModel : ObservableObject
57 } 61 }
58 } 62 }
59 63
60 - 64 + // === 方案A:命令接收“当前项”作为参数,不依赖 SelectedOrder ===
61 [RelayCommand(CanExecute = nameof(CanGoOutbound))] 65 [RelayCommand(CanExecute = nameof(CanGoOutbound))]
62 private async Task GoOutboundAsync(OutboundOrderSummary? item) 66 private async Task GoOutboundAsync(OutboundOrderSummary? item)
63 { 67 {
64 - if (SelectedOrder is null) return; 68 + if (item is null) return;
65 69
66 static string E(string? v) => Uri.EscapeDataString(v ?? ""); 70 static string E(string? v) => Uri.EscapeDataString(v ?? "");
67 71
68 - var o = SelectedOrder; // 确保 SelectedOrder 包含以下字段  
69 - var url =  
70 - $"//OutboundMaterial" +  
71 - $"?outstockId={E(o.outstockId)}" +  
72 - $"&outstockNo={E(searchOrderNo)}" +  
73 - $"&orderType={E(o.orderType)}" +  
74 - $"&orderTypeName={E(o.orderTypeName)}" +  
75 - $"&purchaseNo={E(o.purchaseNo)}" +  
76 - $"&supplierName={E(o.supplierName)}" +  
77 - $"&createdTime={E(o.createdTime)}"; 72 + var o = item;
  73 +
  74 + await Shell.Current.GoToAsync(
  75 + nameof(Pages.OutboundMaterialPage),
  76 + new Dictionary<string, object>
  77 + {
  78 + ["outstockId"] = o.outstockId,
  79 + ["outstockNo"] = o.outstockNo,
  80 + ["orderType"] = o.orderType,
  81 + ["orderTypeName"] = o.orderTypeName,
  82 + ["requisitionMaterialNo"] = o.requisitionMaterialNo,
  83 + ["returnNo"] = o.returnNo,
  84 + ["deliveryNo"] = o.deliveryNo,
  85 + ["createdTime"] = o.createdTime
  86 + });
78 87
79 - await Shell.Current.GoToAsync(url);  
80 } 88 }
  89 +
  90 + // 与命令同签名的 CanExecute
81 private bool CanGoOutbound(OutboundOrderSummary? item) => item != null; 91 private bool CanGoOutbound(OutboundOrderSummary? item) => item != null;
82 } 92 }
  93 +
  94 +// 用于列表显示的精简 DTO
83 public record OutboundOrderSummary( 95 public record OutboundOrderSummary(
84 string outstockId, 96 string outstockId,
  97 + string outstockNo,
85 string orderType, 98 string orderType,
86 string orderTypeName, 99 string orderTypeName,
87 - string purchaseNo,  
88 - string supplierName,  
89 - string arrivalNo,  
90 - string createdTime, 100 + string workOrderNo,
  101 + string returnNo,
91 string deliveryNo, 102 string deliveryNo,
92 string requisitionMaterialNo, 103 string requisitionMaterialNo,
93 - string returnNo,  
94 - string workOrderNo 104 + string createdTime
95 ); 105 );
96 -  
1 using CommunityToolkit.Mvvm.ComponentModel; 1 using CommunityToolkit.Mvvm.ComponentModel;
2 using CommunityToolkit.Mvvm.Input; 2 using CommunityToolkit.Mvvm.Input;
3 -using IndustrialControl.Services;  
4 using System.Collections.ObjectModel; 3 using System.Collections.ObjectModel;
  4 +using IndustrialControl.Services;
  5 +using IndustrialControl.Models;
5 6
6 namespace IndustrialControl.ViewModels 7 namespace IndustrialControl.ViewModels
7 { 8 {
@@ -15,17 +16,15 @@ namespace IndustrialControl.ViewModels @@ -15,17 +16,15 @@ namespace IndustrialControl.ViewModels
15 [ObservableProperty] private string? outstockNo; 16 [ObservableProperty] private string? outstockNo;
16 [ObservableProperty] private string? orderType; 17 [ObservableProperty] private string? orderType;
17 [ObservableProperty] private string? orderTypeName; 18 [ObservableProperty] private string? orderTypeName;
18 - [ObservableProperty] private string? purchaseNo;  
19 - [ObservableProperty] private string? supplierName;  
20 - [ObservableProperty] private string? createdTime;  
21 [ObservableProperty] private string? requisitionMaterialNo; 19 [ObservableProperty] private string? requisitionMaterialNo;
22 [ObservableProperty] private string? returnNo; 20 [ObservableProperty] private string? returnNo;
23 - [ObservableProperty] private string? workOrderNo; 21 + [ObservableProperty] private string? deliveryNo;
  22 + [ObservableProperty] private string? createdTime;
24 23
25 // 列表数据源 24 // 列表数据源
26 public ObservableCollection<string> AvailableBins { get; } = new(); 25 public ObservableCollection<string> AvailableBins { get; } = new();
27 public ObservableCollection<OutScannedItem> ScannedList { get; } = new(); 26 public ObservableCollection<OutScannedItem> ScannedList { get; } = new();
28 - public ObservableCollection<PendingItem> PendingList { get; } = new(); 27 + public ObservableCollection<OutPendingItem> PendingList { get; } = new();
29 28
30 [ObservableProperty] private OutScannedItem? selectedScanItem; 29 [ObservableProperty] private OutScannedItem? selectedScanItem;
31 30
@@ -42,28 +41,27 @@ namespace IndustrialControl.ViewModels @@ -42,28 +41,27 @@ namespace IndustrialControl.ViewModels
42 // 命令 41 // 命令
43 public IRelayCommand ShowPendingCommand { get; } 42 public IRelayCommand ShowPendingCommand { get; }
44 public IRelayCommand ShowScannedCommand { get; } 43 public IRelayCommand ShowScannedCommand { get; }
45 - public IAsyncRelayCommand ConfirmCommand { get; }  
46 44
47 - public OutboundMaterialViewModel(IOutboundMaterialService warehouseSvc) 45 + public OutboundMaterialViewModel(IOutboundMaterialService api)
48 { 46 {
49 - _api = warehouseSvc; 47 + _api = api;
50 ShowPendingCommand = new RelayCommand(() => SwitchTab(true)); 48 ShowPendingCommand = new RelayCommand(() => SwitchTab(true));
51 ShowScannedCommand = new RelayCommand(() => SwitchTab(false)); 49 ShowScannedCommand = new RelayCommand(() => SwitchTab(false));
52 - //ConfirmCommand = new AsyncRelayCommand(ConfirmInboundAsync);  
53 } 50 }
54 51
55 // ================ 初始化入口(页面 OnAppearing 调用) ================ 52 // ================ 初始化入口(页面 OnAppearing 调用) ================
56 public async Task InitializeFromSearchAsync( 53 public async Task InitializeFromSearchAsync(
57 string outstockId, string outstockNo, string orderType, string orderTypeName, 54 string outstockId, string outstockNo, string orderType, string orderTypeName,
58 - string purchaseNo, string supplierName, string createdTime) 55 + string requisitionMaterialNo, string returnNo,string deliveryNo, string createdTime)
59 { 56 {
60 // 1) 基础信息 57 // 1) 基础信息
61 OutstockId = outstockId; 58 OutstockId = outstockId;
62 OutstockNo = outstockNo; 59 OutstockNo = outstockNo;
63 OrderType = orderType; 60 OrderType = orderType;
64 OrderTypeName = orderTypeName; 61 OrderTypeName = orderTypeName;
65 - PurchaseNo = purchaseNo;  
66 - SupplierName = supplierName; 62 + RequisitionMaterialNo = requisitionMaterialNo;
  63 + ReturnNo = returnNo;
  64 + DeliveryNo = deliveryNo;
67 CreatedTime = createdTime; 65 CreatedTime = createdTime;
68 66
69 // 2) 下拉库位(如无接口可留空或使用后端返回的 location 聚合) 67 // 2) 下拉库位(如无接口可留空或使用后端返回的 location 聚合)
@@ -101,13 +99,16 @@ namespace IndustrialControl.ViewModels @@ -101,13 +99,16 @@ namespace IndustrialControl.ViewModels
101 var rows = await _api.GetOutStockDetailAsync(OutstockId!); 99 var rows = await _api.GetOutStockDetailAsync(OutstockId!);
102 foreach (var r in rows) 100 foreach (var r in rows)
103 { 101 {
104 - PendingList.Add(new PendingItem 102 + PendingList.Add(new OutPendingItem
105 { 103 {
106 Name = r.MaterialName ?? "", 104 Name = r.MaterialName ?? "",
  105 + MaterialCode = r.MaterialCode ?? "",
107 Spec = r.Spec ?? "", 106 Spec = r.Spec ?? "",
108 - PendingQty = r.PendingQty,  
109 - Bin = string.IsNullOrWhiteSpace(r.Location) ? "请选择" : r.Location!,  
110 - ScannedQty = r.ScannedQty 107 + Location = r.Location,
  108 + ProductionBatch = r.ProductionBatch,
  109 + StockBatch = r.StockBatch,
  110 + OutstockQty = r.OutstockQty,
  111 + Qty = r.Qty
111 }); 112 });
112 113
113 // 聚合可选库位 114 // 聚合可选库位
@@ -132,6 +133,8 @@ namespace IndustrialControl.ViewModels @@ -132,6 +133,8 @@ namespace IndustrialControl.ViewModels
132 Spec = r.Spec ?? "", 133 Spec = r.Spec ?? "",
133 Location = string.IsNullOrWhiteSpace(r.Location) ? "请选择" : r.Location!, 134 Location = string.IsNullOrWhiteSpace(r.Location) ? "请选择" : r.Location!,
134 Qty = r.Qty, 135 Qty = r.Qty,
  136 + ScanStatus = r.ScanStatus,
  137 + WarehouseCode = r.WarehouseCode ?? "",
135 DetailId = r.DetailId, 138 DetailId = r.DetailId,
136 Id = OutstockId 139 Id = OutstockId
137 }); 140 });
@@ -141,83 +144,48 @@ namespace IndustrialControl.ViewModels @@ -141,83 +144,48 @@ namespace IndustrialControl.ViewModels
141 } 144 }
142 } 145 }
143 146
  147 + // OutboundMaterialViewModel.cs
  148 +
144 [RelayCommand] 149 [RelayCommand]
145 private async Task PassScan() 150 private async Task PassScan()
146 { 151 {
147 - if (string.IsNullOrWhiteSpace(OutstockId))  
148 - {  
149 - await ShowTip("缺少 OutstockId,无法确认。请从查询页进入。");  
150 - return;  
151 - }  
152 -  
153 - // 依旧要求只能选中一行,作为“操作目标”的 UI 约束(接口本身仅需 outstockId)  
154 - var selected = ScannedList.Where(x => x.IsSelected).ToList();  
155 - if (selected.Count != 1)  
156 - {  
157 - await ShowTip(selected.Count == 0 ? "请先勾选一条已扫描记录。" : "一次只能操作一条记录。");  
158 - return;  
159 - }  
160 - var selectedBarcode = selected[0].Barcode; 152 + var picks = ScannedList.Where(x => x.IsSelected).ToList();
  153 + if (picks.Count == 0) { await ShowTip("请先勾选至少一条已扫描记录。"); return; }
161 154
162 - // 调用确认接口  
163 - var resp = await _api.ScanConfirmAsync(OutstockId!); 155 + // 组装 [{ barcode, id }]
  156 + var items = picks.Select(x => (barcode: x.Barcode, id: x.Id)).ToList();
  157 + var resp = await _api.ScanConfirmAsync(items);
164 if (!resp.Succeeded) 158 if (!resp.Succeeded)
165 { 159 {
166 await ShowTip(string.IsNullOrWhiteSpace(resp.Message) ? "扫描通过失败,请重试。" : resp.Message!); 160 await ShowTip(string.IsNullOrWhiteSpace(resp.Message) ? "扫描通过失败,请重试。" : resp.Message!);
167 return; 161 return;
168 } 162 }
169 163
170 - // 成功:刷新两张表  
171 await LoadPendingAsync(); 164 await LoadPendingAsync();
172 await LoadScannedAsync(); 165 await LoadScannedAsync();
173 -  
174 - // 友好:恢复选中刚才那条(如果还在列表里)  
175 - var hit = ScannedList.FirstOrDefault(x =>  
176 - string.Equals(x.Barcode, selectedBarcode, StringComparison.OrdinalIgnoreCase));  
177 - if (hit != null) { hit.IsSelected = true; SelectedScanItem = hit; }  
178 -  
179 await ShowTip("已确认通过。"); 166 await ShowTip("已确认通过。");
180 } 167 }
181 168
182 -  
183 [RelayCommand] 169 [RelayCommand]
184 private async Task CancelScan() 170 private async Task CancelScan()
185 { 171 {
186 - if (string.IsNullOrWhiteSpace(OutstockId))  
187 - {  
188 - await ShowTip("缺少 OutstockId,无法取消。请从查询页进入。");  
189 - return;  
190 - }  
191 -  
192 - // 依旧限制一次只操作一条(接口本身只要 outstockId,这里是 UI 规范)  
193 - var selected = ScannedList.Where(x => x.IsSelected).ToList();  
194 - if (selected.Count != 1)  
195 - {  
196 - await ShowTip(selected.Count == 0 ? "请先勾选一条已扫描记录。" : "一次只能操作一条记录。");  
197 - return;  
198 - }  
199 - var selectedBarcode = selected[0].Barcode;  
200 -  
201 - var resp = await _api.CancelScanAsync(OutstockId!); 172 + var picks = ScannedList.Where(x => x.IsSelected).ToList();
  173 + if (picks.Count == 0) { await ShowTip("请先勾选至少一条记录。"); return; }
  174 + var items = picks.Select(x => (barcode: x.Barcode, id: x.Id)).ToList();
  175 + var resp = await _api.CancelScanAsync(items);
202 if (!resp.Succeeded) 176 if (!resp.Succeeded)
203 { 177 {
204 await ShowTip(string.IsNullOrWhiteSpace(resp.Message) ? "取消扫描失败,请重试。" : resp.Message!); 178 await ShowTip(string.IsNullOrWhiteSpace(resp.Message) ? "取消扫描失败,请重试。" : resp.Message!);
205 return; 179 return;
206 } 180 }
207 181
208 - // 成功后以服务端为准刷新两张表  
209 await LoadPendingAsync(); 182 await LoadPendingAsync();
210 await LoadScannedAsync(); 183 await LoadScannedAsync();
211 -  
212 - // 友好:若那条还在列表里,恢复选中  
213 - var hit = ScannedList.FirstOrDefault(x =>  
214 - string.Equals(x.Barcode, selectedBarcode, StringComparison.OrdinalIgnoreCase));  
215 - if (hit != null) { hit.IsSelected = true; SelectedScanItem = hit; }  
216 -  
217 await ShowTip("已取消扫描。"); 184 await ShowTip("已取消扫描。");
218 } 185 }
219 186
220 187
  188 +
221 public async Task HandleScannedAsync(string data, string symbology) 189 public async Task HandleScannedAsync(string data, string symbology)
222 { 190 {
223 var barcode = (data ?? string.Empty).Trim(); 191 var barcode = (data ?? string.Empty).Trim();
@@ -280,7 +248,12 @@ namespace IndustrialControl.ViewModels @@ -280,7 +248,12 @@ namespace IndustrialControl.ViewModels
280 } 248 }
281 } 249 }
282 250
283 - public async Task<bool> ConfirmInboundAsync() 251 + // OutboundMaterialViewModel.cs
  252 +
  253 + private Task<bool> AskAsync(string title, string message, string ok = "是", string cancel = "否") =>
  254 + Shell.Current?.DisplayAlert(title, message, ok, cancel) ?? Task.FromResult(false);
  255 +
  256 + public async Task<bool> ConfirmOutboundAsync()
284 { 257 {
285 if (string.IsNullOrWhiteSpace(OutstockId)) 258 if (string.IsNullOrWhiteSpace(OutstockId))
286 { 259 {
@@ -288,6 +261,17 @@ namespace IndustrialControl.ViewModels @@ -288,6 +261,17 @@ namespace IndustrialControl.ViewModels
288 return false; 261 return false;
289 } 262 }
290 263
  264 + // ② 服务端权威校验:是否全部扫码确认,后端接口
  265 + bool serverAllOk = await _api.JudgeOutstockDetailScanAllAsync(OutstockId!);
  266 +
  267 + // 任意一处不一致 → 提示是否继续
  268 + if (!serverAllOk)
  269 + {
  270 + bool goOn = await AskAsync("提示", "已扫描列表与待入库数量不一致,是否继续入库?");
  271 + if (!goOn) return false;
  272 + }
  273 +
  274 + // ③ 调用确认入库接口
291 var r = await _api.ConfirmOutstockAsync(OutstockId!); 275 var r = await _api.ConfirmOutstockAsync(OutstockId!);
292 if (!r.Succeeded) 276 if (!r.Succeeded)
293 { 277 {
@@ -295,13 +279,101 @@ namespace IndustrialControl.ViewModels @@ -295,13 +279,101 @@ namespace IndustrialControl.ViewModels
295 return false; 279 return false;
296 } 280 }
297 281
298 - // 成功:可选刷新一次,以服务端为准;随后按钮事件里会 ClearAll()  
299 - await LoadPendingAsync();  
300 - await LoadScannedAsync();  
301 return true; 282 return true;
302 } 283 }
303 284
304 - } 285 + public async Task<bool> UpdateRowLocationAsync(object row, BinInfo bin, CancellationToken ct = default)
  286 + {
  287 + if (row is null || bin is null) return false;
305 288
  289 + // 通过反射从行对象里取必要字段(兼容不同命名)
  290 + var t = row.GetType();
  291 + string detailId =
  292 + t.GetProperty("DetailId")?.GetValue(row)?.ToString()
  293 + ?? t.GetProperty("detailId")?.GetValue(row)?.ToString()
  294 + ?? string.Empty;
306 295
  296 + string id =
  297 + t.GetProperty("Id")?.GetValue(row)?.ToString()
  298 + ?? t.GetProperty("id")?.GetValue(row)?.ToString()
  299 + ?? string.Empty;
  300 +
  301 + if (string.IsNullOrWhiteSpace(detailId) || string.IsNullOrWhiteSpace(id))
  302 + {
  303 + await MainThread.InvokeOnMainThreadAsync(async () =>
  304 + await Application.Current.MainPage.DisplayAlert("提示", "缺少必要字段:detailId 或 id。", "确定"));
  305 + return false;
  306 + }
  307 +
  308 + // 组织参数
  309 + var outstockWarehouse = bin.WarehouseName ?? "";
  310 + var outstockWarehouseCode = bin.WarehouseCode ?? "";
  311 + var location = bin.Location ?? "";
  312 +
  313 + // 调用接口
  314 + var ok = await _api.UpdateOutstockLocationAsync(
  315 + detailId, id, outstockWarehouse, outstockWarehouseCode, location, ct);
  316 +
  317 + if (!ok.Succeeded)
  318 + {
  319 + await MainThread.InvokeOnMainThreadAsync(async () =>
  320 + await Application.Current.MainPage.DisplayAlert("提示", ok.Message ?? "更新库位失败", "确定"));
  321 + return false;
  322 + }
  323 +
  324 + return true;
  325 + }
  326 + public async Task<bool> UpdateQuantityForRowAsync(OutScannedItem row, CancellationToken ct = default)
  327 + {
  328 + if (row is null) return false;
  329 + if (!row.ScanStatus)
  330 + {
  331 + await ShowTip("该行尚未扫描通过,不能修改数量。");
  332 + return false;
  333 + }
  334 + if (row.Qty < 0)
  335 + {
  336 + await ShowTip("数量不能为负数。");
  337 + return false;
  338 + }
  339 +
  340 + var resp = await _api.UpdateQuantityAsync(row.Barcode, row.DetailId, row.Id, row.Qty, ct);
  341 + if (!resp.Succeeded)
  342 + {
  343 + await ShowTip(string.IsNullOrWhiteSpace(resp.Message) ? "更新数量失败" : resp.Message!);
  344 + return false;
  345 + }
  346 +
  347 + // ✅ 成功:先提示,再刷新两张表
  348 + await ShowTip("数量修改成功");
  349 +
  350 + // 记录一下当前行用于刷新后恢复选中
  351 + var keepBarcode = row.Barcode;
  352 +
  353 + await LoadPendingAsync(); // 刷新“待入库明细”
  354 + await LoadScannedAsync(); // 刷新“已扫描明细”
  355 +
  356 + var hit = ScannedList.FirstOrDefault(x => string.Equals(x.Barcode, keepBarcode, StringComparison.OrdinalIgnoreCase));
  357 + if (hit != null) { hit.IsSelected = true; SelectedScanItem = hit; }
  358 +
  359 + return true;
  360 + }
  361 +
  362 +
  363 + }
  364 +
  365 + public class OutPendingItem
  366 + {
  367 + public string Name { get; set; } = ""; //产品名称
  368 + public string MaterialCode { get; set; } = ""; //产品编码
  369 + public string Spec { get; set; } = "";//规格
  370 + public string Location { get; set; } = "";//出库库位
  371 + public string ProductionBatch { get; set; } = "";//生产批号
  372 +
  373 + public string StockBatch { get; set; } = "";//批次号
  374 + public int OutstockQty { get; set; } //出库数量
  375 + public int Qty { get; set; } //已扫描数
  376 + public string Bin { get; set; } = "请选择";
  377 +
  378 + }
307 } 379 }