作者 李壮

outbound

正在显示 45 个修改的文件 包含 448 行增加429 行删除
using Microsoft.Extensions.DependencyInjection;
namespace IndustrialControl;
public partial class App : Application
... ...
using Microsoft.Extensions.DependencyInjection;
namespace IndustrialControl;
namespace IndustrialControl;
public partial class AppShell : Shell
{
... ...
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks>
<TargetFrameworks>net8.0-android</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
... ... @@ -36,6 +36,7 @@
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
<SkipValidateMauiImplicitPackageReferences>true</SkipValidateMauiImplicitPackageReferences>
</PropertyGroup>
<!-- Debug 下:关裁剪、关AOT、关链接器;仅安卓生效 -->
<PropertyGroup Condition="'$(Configuration)'=='Debug' and $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
... ...
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using CommunityToolkit.Maui;
using IndustrialControl.Services;
using CommunityToolkit.Maui;
using IndustrialControl.ViewModels;
using Microsoft.Extensions.Logging;
namespace IndustrialControl
{
... ... @@ -27,7 +26,7 @@ namespace IndustrialControl
builder.Services.AddSingleton<IConfigLoader, ConfigLoader>();
builder.Services.AddSingleton<LogService>();
builder.Services.AddSingleton<IDialogService, DialogService>();
// 扫码服务
builder.Services.AddSingleton<ScanService>();
... ...
... ... @@ -7,7 +7,7 @@ public class ServerSettings
}
public class ApiEndpoints
{
public string Login { get; set; }
public string Login { get; set; }
}
public class LoggingSettings
{
... ...
... ... @@ -51,7 +51,7 @@ public record InboundScannedRow(
string MaterialName,
int Qty,
string Spec,
bool ScanStatus,
bool ScanStatus,
string? WarehouseCode
);
... ...
namespace IndustrialControl.Pages;
public partial class AdminPage : ContentPage
{ public AdminPage(ViewModels.AdminViewModel vm)
namespace IndustrialControl.Pages;
public partial class AdminPage : ContentPage
{
public AdminPage(ViewModels.AdminViewModel vm)
{ InitializeComponent(); BindingContext = vm; }
}
... ...
... ... @@ -60,7 +60,7 @@
await Shell.Current.GoToAsync(nameof(WorkOrderSearchPage));
((CheckBox)sender).IsChecked = false;
}
// 新增:退出登录
private async void OnLogoutClicked(object? sender, EventArgs e)
{
... ...
namespace IndustrialControl.Pages;
public partial class LoginPage : ContentPage
{ public LoginPage(ViewModels.LoginViewModel vm)
namespace IndustrialControl.Pages;
public partial class LoginPage : ContentPage
{
public LoginPage(ViewModels.LoginViewModel vm)
{ InitializeComponent(); BindingContext = vm; }
}
... ...
... ... @@ -2,7 +2,7 @@ namespace IndustrialControl.Pages;
public partial class LogsPage : ContentPage
{
public LogsPage(ViewModels.LogsViewModel vm){ InitializeComponent(); BindingContext = vm; }
protected override void OnAppearing(){ base.OnAppearing(); if(BindingContext is ViewModels.LogsViewModel vm) vm.OnAppearing(); }
protected override void OnDisappearing(){ base.OnDisappearing(); if(BindingContext is ViewModels.LogsViewModel vm) vm.OnDisappearing(); }
public LogsPage(ViewModels.LogsViewModel vm) { InitializeComponent(); BindingContext = vm; }
protected override void OnAppearing() { base.OnAppearing(); if (BindingContext is ViewModels.LogsViewModel vm) vm.OnAppearing(); }
protected override void OnDisappearing() { base.OnDisappearing(); if (BindingContext is ViewModels.LogsViewModel vm) vm.OnDisappearing(); }
}
... ...
... ... @@ -99,7 +99,7 @@ public partial class OutboundMoldPage : ContentPage
/// <summary>
/// 确认入库按钮点击
/// </summary>
async void OnConfirmClicked(object sender, EventArgs e)
async void OnConfirmClicked(object sender, EventArgs e)
{
var ok = await _vm.ConfirmOutboundAsync();
if (ok)
... ...
... ... @@ -50,29 +50,29 @@ public partial class OutboundMoldSearchPage : ContentPage
// 点整张卡片跳转(推荐)
private async void OnOrderTapped(object sender, TappedEventArgs e)
{
try
private async void OnOrderTapped(object sender, TappedEventArgs e)
{
if (e.Parameter is not MoldDto item) return;
try
{
if (e.Parameter is not MoldDto item) return;
var json = JsonSerializer.Serialize(item);
var json = JsonSerializer.Serialize(item);
await Shell.Current.GoToAsync(
nameof(MoldOutboundExecutePage),
new Dictionary<string, object?>
{
// 执行页用 IQueryAttributable 接收:key 必须叫 "orderDto"
["orderDto"] = json
});
}
catch (Exception ex)
{
await DisplayAlert("导航失败", ex.Message, "确定");
await Shell.Current.GoToAsync(
nameof(MoldOutboundExecutePage),
new Dictionary<string, object?>
{
// 执行页用 IQueryAttributable 接收:key 必须叫 "orderDto"
["orderDto"] = json
});
}
catch (Exception ex)
{
await DisplayAlert("导航失败", ex.Message, "确定");
}
}
}
private async void OnScanHintClicked(object sender, EventArgs e)
=> await DisplayAlert("提示", "此按钮预留摄像头扫码;硬件扫描直接扣扳机。", "确定");
private async void OnScanHintClicked(object sender, EventArgs e)
=> await DisplayAlert("提示", "此按钮预留摄像头扫码;硬件扫描直接扣扳机。", "确定");
}
... ...
using System.Text.Json;
using IndustrialControl.Models;
using IndustrialControl.Models;
using IndustrialControl.ViewModels;
using System.Text.Json;
namespace IndustrialControl.Pages;
... ...
... ... @@ -50,29 +50,29 @@ public partial class WorkOrderSearchPage : ContentPage
// 点整张卡片跳转(推荐)
private async void OnOrderTapped(object sender, TappedEventArgs e)
{
try
private async void OnOrderTapped(object sender, TappedEventArgs e)
{
if (e.Parameter is not WorkOrderDto item) return;
try
{
if (e.Parameter is not WorkOrderDto item) return;
var json = JsonSerializer.Serialize(item);
var json = JsonSerializer.Serialize(item);
await Shell.Current.GoToAsync(
nameof(MoldOutboundExecutePage),
new Dictionary<string, object?>
{
// 执行页用 IQueryAttributable 接收:key 必须叫 "orderDto"
["orderDto"] = json
});
}
catch (Exception ex)
{
await DisplayAlert("导航失败", ex.Message, "确定");
await Shell.Current.GoToAsync(
nameof(MoldOutboundExecutePage),
new Dictionary<string, object?>
{
// 执行页用 IQueryAttributable 接收:key 必须叫 "orderDto"
["orderDto"] = json
});
}
catch (Exception ex)
{
await DisplayAlert("导航失败", ex.Message, "确定");
}
}
}
private async void OnScanHintClicked(object sender, EventArgs e)
=> await DisplayAlert("提示", "此按钮预留摄像头扫码;硬件扫描直接扣扳机。", "确定");
private async void OnScanHintClicked(object sender, EventArgs e)
=> await DisplayAlert("提示", "此按钮预留摄像头扫码;硬件扫描直接扣扳机。", "确定");
}
... ...
using System.Collections.ObjectModel;
using IndustrialControl.Models;
using IndustrialControl.Services;
using IndustrialControl.Models;
using System.Collections.ObjectModel;
namespace IndustrialControl.Pages;
... ...
using IndustrialControl.Models;
using IndustrialControl.Services;
using IndustrialControl.ViewModels;
namespace IndustrialControl.Pages;
... ...
... ... @@ -9,7 +9,7 @@ public partial class InboundMaterialSearchPage : ContentPage
public InboundMaterialSearchPage(InboundMaterialSearchViewModel vm, ScanService scanSvc)
{
_vm = vm;
BindingContext = vm;
_scanSvc = scanSvc;
InitializeComponent();
... ...
... ... @@ -55,7 +55,7 @@
<Label.FormattedText>
<FormattedString>
<Span Text="客户:" FontAttributes="Bold"/>
<Span Text="{Binding PurchaseNo}"/>
<Span Text="{Binding Customer}"/>
</FormattedString>
</Label.FormattedText>
</Label>
... ... @@ -65,7 +65,7 @@
<Label.FormattedText>
<FormattedString>
<Span Text="要求发货时间:" FontAttributes="Bold"/>
<Span Text="{Binding SupplierName}"/>
<Span Text="{Binding ExpectedDeliveryTime}"/>
</FormattedString>
</Label.FormattedText>
</Label>
... ... @@ -74,7 +74,7 @@
<Label.FormattedText>
<FormattedString>
<Span Text="关联销售单:" FontAttributes="Bold"/>
<Span Text="{Binding SupplierName}"/>
<Span Text="{Binding saleNo}"/>
</FormattedString>
</Label.FormattedText>
</Label>
... ... @@ -83,7 +83,7 @@
<Label.FormattedText>
<FormattedString>
<Span Text="发货单备注:" FontAttributes="Bold"/>
<Span Text="{Binding SupplierName}"/>
<Span Text="{Binding DeliveryMemo}"/>
</FormattedString>
</Label.FormattedText>
</Label>
... ... @@ -150,11 +150,12 @@
</CollectionView>
<!-- 扫描明细表头 -->
<Grid Grid.Row="1" ColumnDefinitions="40,*,*,*" BackgroundColor="#F2F2F2" IsVisible="{Binding IsScannedVisible}" Padding="8">
<Grid Grid.Row="1" ColumnDefinitions="40,*,*,*,*" BackgroundColor="#F2F2F2" IsVisible="{Binding IsScannedVisible}" Padding="8">
<Label Text="选择" FontAttributes="Bold" />
<Label Grid.Column="1" Text="物料名称" FontAttributes="Bold" />
<Label Grid.Column="2" Text="条码" FontAttributes="Bold" />
<Label Grid.Column="3" Text="数量" FontAttributes="Bold" />
<Label Grid.Column="3" Text="出库数量" FontAttributes="Bold" />
<Label Grid.Column="4" Text="数量" FontAttributes="Bold" />
</Grid>
<!-- 扫描明细列表 -->
... ... @@ -165,7 +166,7 @@
SelectedItem="{Binding SelectedScanItem, Mode=TwoWay}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid ColumnDefinitions="40,*,*,*" Padding="8" BackgroundColor="White">
<Grid ColumnDefinitions="40,*,*,*,*" Padding="8" BackgroundColor="White">
<Grid.Triggers>
<!-- ScanStatus = true → 绿色 -->
<DataTrigger TargetType="Grid" Binding="{Binding ScanStatus}" Value="True">
... ... @@ -176,7 +177,8 @@
<CheckBox IsChecked="{Binding IsSelected}" />
<Label Grid.Column="1" Text="{Binding Name}" />
<Label Grid.Column="2" Text="{Binding Barcode}" />
<Entry Grid.Column="3"
<Label Grid.Column="3" Text="{Binding OutstockQty}" />
<Entry Grid.Column="4"
Keyboard="Numeric"
HorizontalTextAlignment="Center"
WidthRequest="64"
... ...
... ... @@ -5,24 +5,22 @@ namespace IndustrialControl.Pages;
[QueryProperty(nameof(OutstockId), "outstockId")]
[QueryProperty(nameof(OutstockNo), "outstockNo")]
[QueryProperty(nameof(OrderType), "orderType")]
[QueryProperty(nameof(OrderTypeName), "orderTypeName")]
[QueryProperty(nameof(RequisitionMaterialNo), "requisitionMaterialNo")]
[QueryProperty(nameof(ReturnNo), "returnNo")]
[QueryProperty(nameof(DeliveryNo), "deliveryNo")]
[QueryProperty(nameof(CreatedTime), "createdTime")]
[QueryProperty(nameof(Customer), "customer")]
[QueryProperty(nameof(ExpectedDeliveryTime), "expectedDeliveryTime")]
[QueryProperty(nameof(SaleNo), "saleNo")]
[QueryProperty(nameof(DeliveryMemo), "deliveryMemo")]
public partial class OutboundFinishedPage : ContentPage
{
private readonly ScanService _scanSvc;
private readonly OutboundFinishedViewModel _vm;
public string? OutstockId { get; set; }
public string? OutstockNo { get; set; }
public string? OrderType { get; set; }
public string? OrderTypeName { get; set; }
public string? RequisitionMaterialNo { get; set; }
public string? ReturnNo { get; set; }
public string? Customer { get; set; }
public string? ExpectedDeliveryTime { get; set; }
public string? DeliveryNo { get; set; }
public string? CreatedTime { get; set; }
public string? SaleNo { get; set; }
public string? DeliveryMemo { get; set; }
private readonly IDialogService _dialogs;
public OutboundFinishedPage(OutboundFinishedViewModel vm, ScanService scanSvc, IDialogService dialogs)
... ... @@ -51,12 +49,11 @@ public partial class OutboundFinishedPage : ContentPage
await _vm.InitializeFromSearchAsync(
outstockId: OutstockId ?? "",
outstockNo: OutstockNo ?? "",
orderType: OrderType ?? "",
orderTypeName: OrderTypeName ?? "",
requisitionMaterialNo: RequisitionMaterialNo ?? "",
returnNo: ReturnNo ?? "",
deliveryNo: DeliveryNo ?? "",
createdTime: CreatedTime ?? ""
customer: Customer ?? "",
expectedDeliveryTime: ExpectedDeliveryTime ?? "",
saleNo: SaleNo ?? "",
deliveryMemo: DeliveryMemo ?? ""
);
}
... ...
... ... @@ -55,7 +55,7 @@
<Label Grid.Row="2" Grid.Column="1" Text="{Binding deliveryNo}" />
<Label Grid.Row="3" Grid.Column="0" Text="关联销售号:" FontAttributes="Bold"/>
<Label Grid.Row="3" Grid.Column="1" Text="{Binding arrivalNo}" />
<Label Grid.Row="3" Grid.Column="1" Text="{Binding saleNo}" />
<Label Grid.Row="4" Grid.Column="0" Text="创建日期:" FontAttributes="Bold"/>
<Label Grid.Row="4" Grid.Column="1" Text="{Binding createdTime}" />
... ...
... ... @@ -65,7 +65,7 @@
<Label.FormattedText>
<FormattedString>
<Span Text="出库单备注:" FontAttributes="Bold"/>
<Span Text="{Binding SupplierName}"/>
<Span Text="{Binding Memo}"/>
</FormattedString>
</Label.FormattedText>
</Label>
... ... @@ -133,11 +133,12 @@
</CollectionView>
<!-- 扫描明细表头 -->
<Grid Grid.Row="1" ColumnDefinitions="40,*,*,*" BackgroundColor="#F2F2F2" IsVisible="{Binding IsScannedVisible}" Padding="8">
<Grid Grid.Row="1" ColumnDefinitions="40,*,*,*,*" BackgroundColor="#F2F2F2" IsVisible="{Binding IsScannedVisible}" Padding="8">
<Label Text="选择" FontAttributes="Bold" />
<Label Grid.Column="1" Text="物料名称" FontAttributes="Bold" />
<Label Grid.Column="2" Text="条码" FontAttributes="Bold" />
<Label Grid.Column="3" Text="数量" FontAttributes="Bold" />
<Label Grid.Column="3" Text="出库数量" FontAttributes="Bold" />
<Label Grid.Column="4" Text="数量" FontAttributes="Bold" />
</Grid>
<!-- 扫描明细列表 -->
... ... @@ -148,7 +149,7 @@
SelectedItem="{Binding SelectedScanItem, Mode=TwoWay}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid ColumnDefinitions="40,*,*,*" Padding="8" BackgroundColor="White">
<Grid ColumnDefinitions="40,*,*,*,*" Padding="8" BackgroundColor="White">
<Grid.Triggers>
<!-- ScanStatus = true → 绿色 -->
<DataTrigger TargetType="Grid" Binding="{Binding ScanStatus}" Value="True">
... ... @@ -159,7 +160,8 @@
<CheckBox IsChecked="{Binding IsSelected}" />
<Label Grid.Column="1" Text="{Binding Name}" />
<Label Grid.Column="2" Text="{Binding Barcode}" />
<Entry Grid.Column="3"
<Label Grid.Column="3" Text="{Binding OutstockQty}" />
<Entry Grid.Column="4"
Keyboard="Numeric"
HorizontalTextAlignment="Center"
WidthRequest="64"
... ...
... ... @@ -5,24 +5,18 @@ namespace IndustrialControl.Pages;
[QueryProperty(nameof(OutstockId), "outstockId")]
[QueryProperty(nameof(OutstockNo), "outstockNo")]
[QueryProperty(nameof(OrderType), "orderType")]
[QueryProperty(nameof(OrderTypeName), "orderTypeName")]
[QueryProperty(nameof(RequisitionMaterialNo), "requisitionMaterialNo")]
[QueryProperty(nameof(ReturnNo), "returnNo")]
[QueryProperty(nameof(DeliveryNo), "deliveryNo")]
[QueryProperty(nameof(CreatedTime), "createdTime")]
[QueryProperty(nameof(WorkOrderNo), "workOrderNo")]
[QueryProperty(nameof(Memo), "memo")]
public partial class OutboundMaterialPage : ContentPage
{
private readonly ScanService _scanSvc;
private readonly OutboundMaterialViewModel _vm;
public string? OutstockId { get; set; }
public string? OutstockNo { get; set; }
public string? OrderType { get; set; }
public string? OrderTypeName { get; set; }
public string? RequisitionMaterialNo { get; set; }
public string? ReturnNo { get; set; }
public string? DeliveryNo { get; set; }
public string? CreatedTime { get; set; }
public string? WorkOrderNo { get; set; }
public string? Memo { get; set; }
private readonly IDialogService _dialogs;
public OutboundMaterialPage(OutboundMaterialViewModel vm, ScanService scanSvc, IDialogService dialogs)
... ... @@ -51,12 +45,9 @@ public partial class OutboundMaterialPage : ContentPage
await _vm.InitializeFromSearchAsync(
outstockId: OutstockId ?? "",
outstockNo: OutstockNo ?? "",
orderType: OrderType ?? "",
orderTypeName: OrderTypeName ?? "",
requisitionMaterialNo: RequisitionMaterialNo ?? "",
returnNo: ReturnNo ?? "",
deliveryNo: DeliveryNo ?? "",
createdTime: CreatedTime ?? ""
workOrderNo: WorkOrderNo ?? "",
memo: Memo ?? ""
);
}
... ...
using Microsoft.UI.Xaml;
// To learn more about WinUI, the WinUI project structure,
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace IndustrialControl.WinUI
... ...
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>bin\Release\net8.0-android\publish\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
</PropertyGroup>
</Project>
\ No newline at end of file
... ...
// Services/AuthHeaderHandler.cs
using System.Net;
using System.Net.Http.Headers;
namespace IndustrialControl.Services
... ...
using System.Text.Json;
using IndustrialControl.Models;
using System.Text.Json;
namespace IndustrialControl.Services;
... ...
using Microsoft.Maui.Dispatching;
using IndustrialControl.Models;
using IndustrialControl.Pages;
using IndustrialControl.Models;
namespace IndustrialControl.Services;
... ...
... ... @@ -14,7 +14,7 @@ public interface IInboundMaterialService
string orderType,
string[] orderTypeList,
CancellationToken ct = default);
Task<IReadOnlyList<InboundPendingRow>> GetInStockDetailAsync(string instockId, CancellationToken ct = default);
Task<IReadOnlyList<InboundScannedRow>> GetInStockScanDetailAsync(string instockId, CancellationToken ct = default);
/// <summary>扫描条码入库</summary>
... ... @@ -32,10 +32,10 @@ public interface IInboundMaterialService
// 新增:按你截图的真实接口返回结构(树形)
Task<List<LocationNodeDto>> GetLocationTreeAsync(CancellationToken ct = default);
Task<List<BinInfo>> GetBinsByLayerAsync(
string warehouseCode, string layer,
int pageNo = 1, int pageSize = 50, int status = 1,
CancellationToken ct = default);
Task<List<BinInfo>> GetBinsByLayerAsync(
string warehouseCode, string layer,
int pageNo = 1, int pageSize = 50, int status = 1,
CancellationToken ct = default);
/// <summary>更新扫描明细的库位(/normalService/pda/wmsMaterialInstock/updateLocation)</summary>
Task<SimpleOk> UpdateInstockLocationAsync(
... ...
using IndustrialControl.Models;
using IndustrialControl.ViewModels;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
... ... @@ -16,13 +14,13 @@ namespace IndustrialControl.Services
Task<DictBundle> GetMoldDictsAsync(CancellationToken ct = default);
Task<WorkflowResp?> GetMoldWorkflowAsync(string id, CancellationToken ct = default);
Task<PageResp<ProcessTask>?> PageWorkProcessTasksAsync(string workOrderNo, int pageNo = 1, int pageSize = 50, CancellationToken ct = default);
// Task<IEnumerable<MoldOrderSummary>> ListInboundOrdersAsync(
//string? orderNoOrBarcode,
//DateTime startDate,
//DateTime endDate,
//string orderType,
//string[] orderTypeList,
//CancellationToken ct = default);
// Task<IEnumerable<MoldOrderSummary>> ListInboundOrdersAsync(
//string? orderNoOrBarcode,
//DateTime startDate,
//DateTime endDate,
//string orderType,
//string[] orderTypeList,
//CancellationToken ct = default);
Task<IReadOnlyList<InboundScannedRow>> GetInStockScanDetailAsync(string instockId, CancellationToken ct = default);
/// <summary>扫描条码入库</summary>
... ... @@ -92,10 +90,10 @@ namespace IndustrialControl.Services
}
private static string BuildQuery(IDictionary<string, string> p)
=> string.Join("&", p.Select(kv => $"{Uri.EscapeDataString(kv.Key)}={Uri.EscapeDataString(kv.Value)}"));
// using System.Text.Json;
// using System.Text;
// using System.Text.Json;
// using System.Text;
public async Task<WorkOrderPageResult> GetMoldsAsync(MoldQuery q, CancellationToken ct = default)
public async Task<WorkOrderPageResult> GetMoldsAsync(MoldQuery q, CancellationToken ct = default)
{
// 1) 先把所有要传的参数放进字典(只加有值的)
var p = new Dictionary<string, string>
... ... @@ -141,76 +139,76 @@ namespace IndustrialControl.Services
}
/// <summary>
/// 工单流程:/getMoldWorkflow?id=...
/// 返回 result 为数组(statusValue/statusName/statusTime)
/// </summary>
public async Task<WorkflowResp?> GetMoldWorkflowAsync(string id, CancellationToken ct = default)
{
var p = new Dictionary<string, string> { ["id"] = id?.Trim() ?? "" };
var url = _workflowEndpoint + "?" + BuildQuery(p);
/// <summary>
/// 工单流程:/getMoldWorkflow?id=...
/// 返回 result 为数组(statusValue/statusName/statusTime)
/// </summary>
public async Task<WorkflowResp?> GetMoldWorkflowAsync(string id, CancellationToken ct = default)
{
var p = new Dictionary<string, string> { ["id"] = id?.Trim() ?? "" };
var url = _workflowEndpoint + "?" + BuildQuery(p);
using var req = new HttpRequestMessage(HttpMethod.Get, url);
System.Diagnostics.Debug.WriteLine("[MoldApi] GET " + url);
using var req = new HttpRequestMessage(HttpMethod.Get, url);
System.Diagnostics.Debug.WriteLine("[MoldApi] GET " + url);
using var httpResp = await _http.SendAsync(req, ct);
var json = await httpResp.Content.ReadAsStringAsync(ct);
System.Diagnostics.Debug.WriteLine("[MoldApi] Resp(getMoldWorkflow): " + json[..Math.Min(300, json.Length)] + "...");
using var httpResp = await _http.SendAsync(req, ct);
var json = await httpResp.Content.ReadAsStringAsync(ct);
System.Diagnostics.Debug.WriteLine("[MoldApi] Resp(getMoldWorkflow): " + json[..Math.Min(300, json.Length)] + "...");
if (!httpResp.IsSuccessStatusCode)
return new WorkflowResp { success = false, message = $"HTTP {(int)httpResp.StatusCode}" };
if (!httpResp.IsSuccessStatusCode)
return new WorkflowResp { success = false, message = $"HTTP {(int)httpResp.StatusCode}" };
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var resp = JsonSerializer.Deserialize<WorkflowResp>(json, options) ?? new WorkflowResp();
return resp;
}
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var resp = JsonSerializer.Deserialize<WorkflowResp>(json, options) ?? new WorkflowResp();
return resp;
}
/// <summary>
/// 工序分页:/pageWorkProcessTasks?pageNo=&pageSize=&workOrderNo=
/// 返回分页结构,数据在 result.records[]
/// </summary>
public async Task<PageResp<ProcessTask>?> PageWorkProcessTasksAsync(
string workOrderNo, int pageNo = 1, int pageSize = 50, CancellationToken ct = default)
{
var p = new Dictionary<string, string>
/// <summary>
/// 工序分页:/pageWorkProcessTasks?pageNo=&pageSize=&workOrderNo=
/// 返回分页结构,数据在 result.records[]
/// </summary>
public async Task<PageResp<ProcessTask>?> PageWorkProcessTasksAsync(
string workOrderNo, int pageNo = 1, int pageSize = 50, CancellationToken ct = default)
{
["pageNo"] = pageNo.ToString(),
["pageSize"] = pageSize.ToString()
};
if (!string.IsNullOrWhiteSpace(workOrderNo)) p["workOrderNo"] = workOrderNo.Trim();
var p = new Dictionary<string, string>
{
["pageNo"] = pageNo.ToString(),
["pageSize"] = pageSize.ToString()
};
if (!string.IsNullOrWhiteSpace(workOrderNo)) p["workOrderNo"] = workOrderNo.Trim();
var url = _processTasksEndpoint + "?" + BuildQuery(p);
var url = _processTasksEndpoint + "?" + BuildQuery(p);
using var req = new HttpRequestMessage(HttpMethod.Get, url);
System.Diagnostics.Debug.WriteLine("[MoldApi] GET " + url);
using var req = new HttpRequestMessage(HttpMethod.Get, url);
System.Diagnostics.Debug.WriteLine("[MoldApi] GET " + url);
using var httpResp = await _http.SendAsync(req, ct);
var json = await httpResp.Content.ReadAsStringAsync(ct);
System.Diagnostics.Debug.WriteLine("[MoldApi] Resp(pageWorkProcessTasks): " + json[..Math.Min(300, json.Length)] + "...");
using var httpResp = await _http.SendAsync(req, ct);
var json = await httpResp.Content.ReadAsStringAsync(ct);
System.Diagnostics.Debug.WriteLine("[MoldApi] Resp(pageWorkProcessTasks): " + json[..Math.Min(300, json.Length)] + "...");
if (!httpResp.IsSuccessStatusCode)
return new PageResp<ProcessTask> { success = false, message = $"HTTP {(int)httpResp.StatusCode}" };
if (!httpResp.IsSuccessStatusCode)
return new PageResp<ProcessTask> { success = false, message = $"HTTP {(int)httpResp.StatusCode}" };
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var resp = JsonSerializer.Deserialize<PageResp<ProcessTask>>(json, options) ?? new PageResp<ProcessTask>();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var resp = JsonSerializer.Deserialize<PageResp<ProcessTask>>(json, options) ?? new PageResp<ProcessTask>();
// 兼容 result.records(你的实际返回就是 records,结构示例如你发的 JSON)
// 如果后端某些场景包在 result.list.records,也一并兼容
var nested = resp.result?.records ?? resp.result?.records;
if (nested is not null && resp.result is not null)
{
if (resp.result.records is null || resp.result.records.Count == 0)
resp.result.records = nested;
// 兼容 result.records(你的实际返回就是 records,结构示例如你发的 JSON)
// 如果后端某些场景包在 result.list.records,也一并兼容
var nested = resp.result?.records ?? resp.result?.records;
if (nested is not null && resp.result is not null)
{
if (resp.result.records is null || resp.result.records.Count == 0)
resp.result.records = nested;
if (resp.result.pageNo == 0 && resp.result is not null) resp.result.pageNo = resp.result.pageNo;
if (resp.result.pageSize == 0 && resp.result is not null) resp.result.pageSize = resp.result.pageSize;
if (resp.result.total == 0 && resp.result is not null) resp.result.total = resp.result.total;
}
if (resp.result.pageNo == 0 && resp.result is not null) resp.result.pageNo = resp.result.pageNo;
if (resp.result.pageSize == 0 && resp.result is not null) resp.result.pageSize = resp.result.pageSize;
if (resp.result.total == 0 && resp.result is not null) resp.result.total = resp.result.total;
}
return resp;
}
return resp;
}
public async Task<DictBundle> GetMoldDictsAsync(CancellationToken ct = default)
public async Task<DictBundle> GetMoldDictsAsync(CancellationToken ct = default)
{
using var req = new HttpRequestMessage(HttpMethod.Get, _dictEndpoint);
using var res = await _http.SendAsync(req, ct);
... ... @@ -385,7 +383,7 @@ public async Task<DictBundle> GetMoldDictsAsync(CancellationToken ct = default)
public string? lineName { get; set; }
public string? workShop { get; set; }
public string? workShopName { get; set; }
public string? urgent { get; set; }
public string? urgent { get; set; }
// ★ 这些时间都是 "yyyy-MM-dd HH:mm:ss" 字符串
public string? schemeStartDate { get; set; }
... ...
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace IndustrialControl.Services
... ... @@ -52,12 +51,12 @@ namespace IndustrialControl.Services
(string?)cfg?["apiEndpoints"]?["workOrder"]?["dictList"]
?? "/normalService/pda/pmsWorkOrder/getWorkOrderDictList";
}
private static string BuildQuery(IDictionary<string, string> p)
=> string.Join("&", p.Select(kv => $"{Uri.EscapeDataString(kv.Key)}={Uri.EscapeDataString(kv.Value)}"));
// using System.Text.Json;
// using System.Text;
private static string BuildQuery(IDictionary<string, string> p)
=> string.Join("&", p.Select(kv => $"{Uri.EscapeDataString(kv.Key)}={Uri.EscapeDataString(kv.Value)}"));
// using System.Text.Json;
// using System.Text;
public async Task<WorkOrderPageResult> GetWorkOrdersAsync(WorkOrderQuery q, CancellationToken ct = default)
public async Task<WorkOrderPageResult> GetWorkOrdersAsync(WorkOrderQuery q, CancellationToken ct = default)
{
// 1) 先把所有要传的参数放进字典(只加有值的)
var p = new Dictionary<string, string>
... ... @@ -103,76 +102,76 @@ namespace IndustrialControl.Services
}
/// <summary>
/// 工单流程:/getWorkOrderWorkflow?id=...
/// 返回 result 为数组(statusValue/statusName/statusTime)
/// </summary>
public async Task<WorkflowResp?> GetWorkOrderWorkflowAsync(string id, CancellationToken ct = default)
{
var p = new Dictionary<string, string> { ["id"] = id?.Trim() ?? "" };
var url = _workflowEndpoint + "?" + BuildQuery(p);
/// <summary>
/// 工单流程:/getWorkOrderWorkflow?id=...
/// 返回 result 为数组(statusValue/statusName/statusTime)
/// </summary>
public async Task<WorkflowResp?> GetWorkOrderWorkflowAsync(string id, CancellationToken ct = default)
{
var p = new Dictionary<string, string> { ["id"] = id?.Trim() ?? "" };
var url = _workflowEndpoint + "?" + BuildQuery(p);
using var req = new HttpRequestMessage(HttpMethod.Get, url);
System.Diagnostics.Debug.WriteLine("[WorkOrderApi] GET " + url);
using var req = new HttpRequestMessage(HttpMethod.Get, url);
System.Diagnostics.Debug.WriteLine("[WorkOrderApi] GET " + url);
using var httpResp = await _http.SendAsync(req, ct);
var json = await httpResp.Content.ReadAsStringAsync(ct);
System.Diagnostics.Debug.WriteLine("[WorkOrderApi] Resp(getWorkOrderWorkflow): " + json[..Math.Min(300, json.Length)] + "...");
using var httpResp = await _http.SendAsync(req, ct);
var json = await httpResp.Content.ReadAsStringAsync(ct);
System.Diagnostics.Debug.WriteLine("[WorkOrderApi] Resp(getWorkOrderWorkflow): " + json[..Math.Min(300, json.Length)] + "...");
if (!httpResp.IsSuccessStatusCode)
return new WorkflowResp { success = false, message = $"HTTP {(int)httpResp.StatusCode}" };
if (!httpResp.IsSuccessStatusCode)
return new WorkflowResp { success = false, message = $"HTTP {(int)httpResp.StatusCode}" };
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var resp = JsonSerializer.Deserialize<WorkflowResp>(json, options) ?? new WorkflowResp();
return resp;
}
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var resp = JsonSerializer.Deserialize<WorkflowResp>(json, options) ?? new WorkflowResp();
return resp;
}
/// <summary>
/// 工序分页:/pageWorkProcessTasks?pageNo=&pageSize=&workOrderNo=
/// 返回分页结构,数据在 result.records[]
/// </summary>
public async Task<PageResp<ProcessTask>?> PageWorkProcessTasksAsync(
string workOrderNo, int pageNo = 1, int pageSize = 50, CancellationToken ct = default)
{
var p = new Dictionary<string, string>
/// <summary>
/// 工序分页:/pageWorkProcessTasks?pageNo=&pageSize=&workOrderNo=
/// 返回分页结构,数据在 result.records[]
/// </summary>
public async Task<PageResp<ProcessTask>?> PageWorkProcessTasksAsync(
string workOrderNo, int pageNo = 1, int pageSize = 50, CancellationToken ct = default)
{
["pageNo"] = pageNo.ToString(),
["pageSize"] = pageSize.ToString()
};
if (!string.IsNullOrWhiteSpace(workOrderNo)) p["workOrderNo"] = workOrderNo.Trim();
var p = new Dictionary<string, string>
{
["pageNo"] = pageNo.ToString(),
["pageSize"] = pageSize.ToString()
};
if (!string.IsNullOrWhiteSpace(workOrderNo)) p["workOrderNo"] = workOrderNo.Trim();
var url = _processTasksEndpoint + "?" + BuildQuery(p);
var url = _processTasksEndpoint + "?" + BuildQuery(p);
using var req = new HttpRequestMessage(HttpMethod.Get, url);
System.Diagnostics.Debug.WriteLine("[WorkOrderApi] GET " + url);
using var req = new HttpRequestMessage(HttpMethod.Get, url);
System.Diagnostics.Debug.WriteLine("[WorkOrderApi] GET " + url);
using var httpResp = await _http.SendAsync(req, ct);
var json = await httpResp.Content.ReadAsStringAsync(ct);
System.Diagnostics.Debug.WriteLine("[WorkOrderApi] Resp(pageWorkProcessTasks): " + json[..Math.Min(300, json.Length)] + "...");
using var httpResp = await _http.SendAsync(req, ct);
var json = await httpResp.Content.ReadAsStringAsync(ct);
System.Diagnostics.Debug.WriteLine("[WorkOrderApi] Resp(pageWorkProcessTasks): " + json[..Math.Min(300, json.Length)] + "...");
if (!httpResp.IsSuccessStatusCode)
return new PageResp<ProcessTask> { success = false, message = $"HTTP {(int)httpResp.StatusCode}" };
if (!httpResp.IsSuccessStatusCode)
return new PageResp<ProcessTask> { success = false, message = $"HTTP {(int)httpResp.StatusCode}" };
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var resp = JsonSerializer.Deserialize<PageResp<ProcessTask>>(json, options) ?? new PageResp<ProcessTask>();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var resp = JsonSerializer.Deserialize<PageResp<ProcessTask>>(json, options) ?? new PageResp<ProcessTask>();
// 兼容 result.records(你的实际返回就是 records,结构示例如你发的 JSON)
// 如果后端某些场景包在 result.list.records,也一并兼容
var nested = resp.result?.records ?? resp.result?.records;
if (nested is not null && resp.result is not null)
{
if (resp.result.records is null || resp.result.records.Count == 0)
resp.result.records = nested;
// 兼容 result.records(你的实际返回就是 records,结构示例如你发的 JSON)
// 如果后端某些场景包在 result.list.records,也一并兼容
var nested = resp.result?.records ?? resp.result?.records;
if (nested is not null && resp.result is not null)
{
if (resp.result.records is null || resp.result.records.Count == 0)
resp.result.records = nested;
if (resp.result.pageNo == 0 && resp.result is not null) resp.result.pageNo = resp.result.pageNo;
if (resp.result.pageSize == 0 && resp.result is not null) resp.result.pageSize = resp.result.pageSize;
if (resp.result.total == 0 && resp.result is not null) resp.result.total = resp.result.total;
}
if (resp.result.pageNo == 0 && resp.result is not null) resp.result.pageNo = resp.result.pageNo;
if (resp.result.pageSize == 0 && resp.result is not null) resp.result.pageSize = resp.result.pageSize;
if (resp.result.total == 0 && resp.result is not null) resp.result.total = resp.result.total;
return resp;
}
return resp;
}
public async Task<DictBundle> GetWorkOrderDictsAsync(CancellationToken ct = default)
public async Task<DictBundle> GetWorkOrderDictsAsync(CancellationToken ct = default)
{
using var req = new HttpRequestMessage(HttpMethod.Get, _dictEndpoint);
using var res = await _http.SendAsync(req, ct);
... ... @@ -257,7 +256,7 @@ public async Task<DictBundle> GetWorkOrderDictsAsync(CancellationToken ct = defa
public string? lineName { get; set; }
public string? workShop { get; set; }
public string? workShopName { get; set; }
public string? urgent { get; set; }
public string? urgent { get; set; }
// ★ 这些时间都是 "yyyy-MM-dd HH:mm:ss" 字符串
public string? schemeStartDate { get; set; }
... ... @@ -337,22 +336,22 @@ public async Task<DictBundle> GetWorkOrderDictsAsync(CancellationToken ct = defa
public List<DictItem> AuditStatus { get; set; } = new();
public List<DictItem> Urgent { get; set; } = new();
}
public sealed class WorkflowResp { public bool success { get; set; } public string? message { get; set; } public int code { get; set; } public List<WorkflowItem>? result { get; set; } }
public sealed class WorkflowItem { public string? statusValue { get; set; } public string? statusName { get; set; } public string? statusTime { get; set; } }
public sealed class WorkflowResp { public bool success { get; set; } public string? message { get; set; } public int code { get; set; } public List<WorkflowItem>? result { get; set; } }
public sealed class WorkflowItem { public string? statusValue { get; set; } public string? statusName { get; set; } public string? statusTime { get; set; } }
public sealed class PageResp<T> { public bool success { get; set; } public string? message { get; set; } public int code { get; set; } public PageResult<T>? result { get; set; } }
public sealed class PageResult<T> { public int pageNo { get; set; } public int pageSize { get; set; } public int total { get; set; } public List<T>? records { get; set; } }
public sealed class PageResp<T> { public bool success { get; set; } public string? message { get; set; } public int code { get; set; } public PageResult<T>? result { get; set; } }
public sealed class PageResult<T> { public int pageNo { get; set; } public int pageSize { get; set; } public int total { get; set; } public List<T>? records { get; set; } }
public sealed class ProcessTask
{
public string? id { get; set; }
public string? processCode { get; set; }
public string? processName { get; set; }
public decimal? scheQty { get; set; }
public decimal? completedQty { get; set; }
public string? startDate { get; set; }
public string? endDate { get; set; }
public int? sortNumber { get; set; }
public string? auditStatus { get; set; }
}
public sealed class ProcessTask
{
public string? id { get; set; }
public string? processCode { get; set; }
public string? processName { get; set; }
public decimal? scheQty { get; set; }
public decimal? completedQty { get; set; }
public string? startDate { get; set; }
public string? endDate { get; set; }
public int? sortNumber { get; set; }
public string? auditStatus { get; set; }
}
}
... ...
using IndustrialControl.Models;
using IndustrialControl.ViewModels;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace IndustrialControl.Services;
... ... @@ -153,11 +151,11 @@ public sealed class InboundMaterialService : IInboundMaterialService
orderType: x.orderType ?? "",
orderTypeName: x.orderTypeName ?? "",
purchaseNo: x.purchaseNo ?? "",
arrivalNo: x.arrivalNo ?? "",
supplierName: x.supplierName ?? "",
workOrderNo: x.workOrderNo ?? "",
materialName: x.materialName ?? "",
instockQty: ToInt(x.instockQty),
arrivalNo: x.arrivalNo ?? "",
workOrderNo: x.workOrderNo ?? "",
materialName: x.materialName ?? "",
instockQty: ToInt(x.instockQty),
createdTime: x.createdTime ?? ""
));
}
... ... @@ -178,7 +176,7 @@ public sealed class InboundMaterialService : IInboundMaterialService
if (dto?.success != true || dto.result is null || dto.result.Count == 0)
return Array.Empty<InboundPendingRow>();
// ⚠️ 接口没有 barcode,这里先用空串;如需展示可以改成 x.materialCode 或 x.stockBatch
var list = dto.result.Select(x => new InboundPendingRow(
Barcode: string.Empty, // 或 $"{x.materialCode}" / $"{x.stockBatch}"
... ... @@ -220,8 +218,8 @@ public sealed class InboundMaterialService : IInboundMaterialService
MaterialName: (x.materialName ?? string.Empty).Trim(),
Qty: ToInt(x.qty),
Spec: (x.spec ?? string.Empty).Trim(),
ScanStatus :x.scanStatus ?? false,
WarehouseCode :x.warehouseCode?.Trim()
ScanStatus: x.scanStatus ?? false,
WarehouseCode: x.warehouseCode?.Trim()
)).ToList();
return list;
... ... @@ -452,33 +450,33 @@ public sealed class InboundMaterialService : IInboundMaterialService
// ====== DTO(按接口示例字段) ======
public class GetInStockReq
{
public string? createdTime { get; set; }
public string? endTime { get; set; }
public string? instockNo { get; set; }
public string? orderType { get; set; }
public string? startTime { get; set; }
}
{
public string? createdTime { get; set; }
public string? endTime { get; set; }
public string? instockNo { get; set; }
public string? orderType { get; set; }
public string? startTime { get; set; }
}
public class GetInStockResp
{
public int code { get; set; }
public long costTime { get; set; }
public string? message { get; set; }
public bool success { get; set; }
public List<GetInStockItem>? result { get; set; }
}
public class GetInStockResp
{
public int code { get; set; }
public long costTime { get; set; }
public string? message { get; set; }
public bool success { get; set; }
public List<GetInStockItem>? result { get; set; }
}
public class GetInStockItem
{
public string? arrivalNo { get; set; }
public string? createdTime { get; set; }
public string? instockId { get; set; }
public string? instockNo { get; set; }
public string? orderType { get; set; }
public string? purchaseNo { get; set; }
public string? supplierName { get; set; }
}
public class GetInStockItem
{
public string? arrivalNo { get; set; }
public string? createdTime { get; set; }
public string? instockId { get; set; }
public string? instockNo { get; set; }
public string? orderType { get; set; }
public string? purchaseNo { get; set; }
public string? supplierName { get; set; }
}
public sealed class GetInStockDetailResp
{
public bool success { get; set; }
... ... @@ -504,55 +502,55 @@ public sealed class GetInStockDetailItem
public class ScanRow
{
public string? barcode { get; set; }
public string? instockId { get; set; }
public string? location { get; set; }
public string? materialName { get; set; }
public string? qty { get; set; }
public string? spec { get; set; }
}
{
public string? barcode { get; set; }
public string? instockId { get; set; }
public string? location { get; set; }
public string? materialName { get; set; }
public string? qty { get; set; }
public string? spec { get; set; }
}
public class ScanByBarcodeResp
{
public int code { get; set; }
public long costTime { get; set; }
public string? message { get; set; }
public object? result { get; set; } // 文档里 result 只是 bool/无结构,这里占位
public bool success { get; set; }
}
public class ScanConfirmResp
{
public int code { get; set; }
public long costTime { get; set; }
public string? message { get; set; }
public object? result { get; set; }
public bool success { get; set; }
}
public class CancelScanResp
{
public int code { get; set; }
public long costTime { get; set; }
public string? message { get; set; }
public object? result { get; set; }
public bool success { get; set; }
}
public class ConfirmResp
{
public int code { get; set; }
public long costTime { get; set; }
public string? message { get; set; }
public bool? result { get; set; }
public bool? success { get; set; }
}
public class JudgeScanAllResp
{
public int code { get; set; }
public long costTime { get; set; }
public string? message { get; set; }
public bool success { get; set; }
public bool? result { get; set; } // 文档中为布尔
}
public class ScanByBarcodeResp
{
public int code { get; set; }
public long costTime { get; set; }
public string? message { get; set; }
public object? result { get; set; } // 文档里 result 只是 bool/无结构,这里占位
public bool success { get; set; }
}
public class ScanConfirmResp
{
public int code { get; set; }
public long costTime { get; set; }
public string? message { get; set; }
public object? result { get; set; }
public bool success { get; set; }
}
public class CancelScanResp
{
public int code { get; set; }
public long costTime { get; set; }
public string? message { get; set; }
public object? result { get; set; }
public bool success { get; set; }
}
public class ConfirmResp
{
public int code { get; set; }
public long costTime { get; set; }
public string? message { get; set; }
public bool? result { get; set; }
public bool? success { get; set; }
}
public class JudgeScanAllResp
{
public int code { get; set; }
public long costTime { get; set; }
public string? message { get; set; }
public bool success { get; set; }
public bool? result { get; set; } // 文档中为布尔
}
public class GetInStockPageResp
{
public int code { get; set; }
... ...
using IndustrialControl.Models;
using IndustrialControl.ViewModels;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace IndustrialControl.Services;
... ... @@ -151,9 +149,14 @@ public sealed class OutboundMaterialService : IOutboundMaterialService
orderType: x.orderType ?? "",
orderTypeName: x.orderTypeName ?? "",
workOrderNo: x.workOrderNo ?? "",
returnNo: x.returnNo ?? "",
deliveryNo: x.deliveryNo ?? "",
requisitionMaterialNo: x.requisitionMaterialNo ?? "",
returnNo:x.returnNo ?? "",
deliveryNo:x.deliveryNo ?? "",
customer: x.customer ?? "",
deliveryMemo: x.deliveryMemo ?? "",
expectedDeliveryTime: x.expectedDeliveryTime ?? "",
memo: x.memo ?? "",
saleNo: x.saleNo ?? "",
createdTime: x.createdTime ?? ""
));
}
... ... @@ -436,6 +439,11 @@ public sealed class OutboundMaterialService : IOutboundMaterialService
public string? requisitionMaterialNo { get; set; }
public string? returnNo { get; set; }
public string? deliveryNo { get; set; }
public string? customer { get; set; }
public string? deliveryMemo { get; set; }
public string? expectedDeliveryTime { get; set; }
public string? memo { get; set; }
public string? saleNo { get; set; }
public string? createdTime { get; set; }
}
public sealed class GetOutStockScanDetailResp
... ...
using System;
// File: Services/ScanService.cs
using System;
using Microsoft.Maui.Controls;
#if ANDROID
using Android.Content;
using Android.Util; // ✅ 用于 Log
using IndustrialControl.Droid; // 需要 DynamicScanReceiver
using Android.Util;
using IndustrialControl.Droid; // 引用 DynamicScanReceiver
#endif
namespace IndustrialControl.Services
{
/// <summary>
/// 统一的扫码服务:支持软键盘回车、手动发布、以及 Android 广播动态接收
/// </summary>
public class ScanService
{
public event Action<string, string>? Scanned;
public event Action<string, string?>? Scanned;
/// <summary>可选:前缀过滤(匹配到则裁剪)</summary>
public string? Prefix { get; set; }
/// <summary>可选:后缀过滤(匹配到则裁剪)</summary>
public string? Suffix { get; set; }
/// <summary>去抖间隔(毫秒):相同码在该间隔内只触发一次</summary>
public int DebounceMs { get; set; } = 250;
private string? _lastData;
private DateTime _lastAt = DateTime.MinValue;
// 约定的广播/键名(与你的设备或发送方对齐)
public const string BroadcastAction = "lc";
public const string DataKey = "data";
public const string TypeKey = "SCAN_BARCODE_TYPE_NAME";
/// <summary>
/// 绑定一个 Entry:回车或换行时触发扫码
/// </summary>
public void Attach(Entry entry)
{
// 回车(Completed)
entry.Completed += (s, e) =>
{
var data = entry.Text?.Trim();
... ... @@ -34,11 +48,12 @@ namespace IndustrialControl.Services
#if ANDROID
Log.Info("ScanService", $"[Attach] Entry.Completed -> {data}");
#endif
Scanned?.Invoke(data, "kbd");
FilterAndRaise(data, "kbd");
entry.Text = string.Empty;
}
};
// 文本变化:遇到 \n/\r 也触发
entry.TextChanged += (s, e) =>
{
if (string.IsNullOrEmpty(e.NewTextValue)) return;
... ... @@ -49,24 +64,30 @@ namespace IndustrialControl.Services
#if ANDROID
Log.Info("ScanService", $"[Attach] Entry.TextChanged -> {data}");
#endif
Scanned?.Invoke(data, "kbd");
FilterAndRaise(data, "kbd");
entry.Text = string.Empty;
}
};
}
public void Publish(string code, string type = "")
/// <summary>
/// 代码侧模拟一次扫码(用于调试/联调)
/// </summary>
public void Publish(string code, string? type = null)
{
#if ANDROID
Log.Info("ScanService", $"[Publish] 模拟扫码 -> {code}, type={type}");
#endif
FilterAndRaise(code, type);
FilterAndRaise(code, type ?? string.Empty);
}
/// <summary>
/// 开始监听 Android 广播(动态注册)
/// </summary>
public void StartListening()
{
#if ANDROID
Android.Util.Log.Info("ScanService", "[StartListening] ENTER");
Log.Info("ScanService", "[StartListening] ENTER");
if (_receiver != null) return;
_receiver = new DynamicScanReceiver();
... ... @@ -79,6 +100,9 @@ namespace IndustrialControl.Services
#endif
}
/// <summary>
/// 停止监听 Android 广播(反注册)
/// </summary>
public void StopListening()
{
#if ANDROID
... ... @@ -100,19 +124,22 @@ namespace IndustrialControl.Services
#endif
}
private bool FilterAndRaise(string data, string type)
/// <summary>
/// 统一过滤 + 去抖 + 触发事件
/// </summary>
private bool FilterAndRaise(string data, string? type)
{
if (!string.IsNullOrEmpty(Prefix) && data.StartsWith(Prefix))
if (!string.IsNullOrEmpty(Prefix) && data.StartsWith(Prefix, StringComparison.Ordinal))
data = data.Substring(Prefix.Length);
if (!string.IsNullOrEmpty(Suffix) && data.EndsWith(Suffix))
if (!string.IsNullOrEmpty(Suffix) && data.EndsWith(Suffix, StringComparison.Ordinal))
data = data.Substring(0, data.Length - Suffix.Length);
var now = DateTime.UtcNow;
if (_lastData == data && (now - _lastAt).TotalMilliseconds < DebounceMs)
{
#if ANDROID
Log.Info("ScanService", $"[FilterAndRaise] 数据去抖: {data}");
Log.Info("ScanService", $"[FilterAndRaise] 去抖丢弃: {data}");
#endif
return false;
}
... ... @@ -121,7 +148,7 @@ namespace IndustrialControl.Services
_lastAt = now;
#if ANDROID
Log.Info("ScanService", $"[FilterAndRaise] 最终触发 -> {data}, type={type}");
Log.Info("ScanService", $"[FilterAndRaise] 触发 -> {data}, type={type}");
#endif
Scanned?.Invoke(data, type);
return true;
... ... @@ -131,11 +158,11 @@ namespace IndustrialControl.Services
private DynamicScanReceiver? _receiver;
private IntentFilter? _filter;
private void OnScannedFromPlatform(string data, string type)
private void OnScannedFromPlatform(string data, string? type)
{
Log.Info("ScanService", $"[OnScannedFromPlatform] 原始数据 -> {data}, type={type}");
Log.Info("ScanService", $"[OnScannedFromPlatform] 原始 -> {data}, type={type}");
FilterAndRaise(data, type);
}
#endif
}
}
}
\ No newline at end of file
... ...
// Utils/TokenStorage.cs
using Microsoft.Maui.Storage;
namespace IndustrialControl;
public static class TokenStorage
... ...
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
using IndustrialControl.Services;
using IndustrialControl.Pages; // 用于弹出 BinPickerPage / BinListPage
using IndustrialControl.Services;
using System.Collections.ObjectModel;
using ConfirmDetail = IndustrialControl.Services.InStockDetail;
// 使用服务层 DTO,避免 VM 内重复定义
using ConfirmReq = IndustrialControl.Services.InStockConfirmReq;
using ConfirmDetail = IndustrialControl.Services.InStockDetail;
namespace IndustrialControl.ViewModels
{
... ...
... ... @@ -130,7 +130,7 @@ namespace IndustrialControl.ViewModels
LineName = r.lineName ?? "",
Status = statusName,
Urgent = urgentName,
CurQty =(int?)r.curQty,
CurQty = (int?)r.curQty,
CreateDate = createdAt?.ToString("yyyy-MM-dd") ?? (r.createdTime ?? ""),
BomCode = r.bomCode, // e.g. "BOM00000006"
RouteName = r.routeName, // e.g. "午餐肉罐头测试工序调整"
... ...
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
using IndustrialControl.Services;
using System.Collections.ObjectModel;
namespace IndustrialControl.ViewModels
{
... ... @@ -108,7 +108,7 @@ namespace IndustrialControl.ViewModels
return;
}
await ShowTip("已取消扫描。");
}
... ... @@ -137,7 +137,7 @@ namespace IndustrialControl.ViewModels
return;
}
}
... ... @@ -162,7 +162,7 @@ namespace IndustrialControl.ViewModels
return false;
}
return true;
}
... ...
... ... @@ -129,7 +129,7 @@ namespace IndustrialControl.ViewModels
LineName = r.lineName ?? "",
Status = statusName,
Urgent = urgentName,
CurQty =(int?)r.curQty,
CurQty = (int?)r.curQty,
CreateDate = createdAt?.ToString("yyyy-MM-dd") ?? (r.createdTime ?? ""),
BomCode = r.bomCode, // e.g. "BOM00000006"
RouteName = r.routeName, // e.g. "午餐肉罐头测试工序调整"
... ...
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
using IndustrialControl.Services;
using IndustrialControl.Models;
using IndustrialControl.Services;
using System.Collections.ObjectModel;
namespace IndustrialControl.ViewModels
{
... ...
... ... @@ -82,9 +82,9 @@ public partial class InboundProductionSearchViewModel : ObservableObject
["supplierName"] = o.supplierName,
["arrivalNo"] = o.arrivalNo,
["instockQty"] = o.instockQty,
["materialName"] = o.materialName,
["workOrderNo"] = o.workOrderNo,
["createdTime"] = o.createdTime
["materialName"] = o.materialName,
["workOrderNo"] = o.workOrderNo,
["createdTime"] = o.createdTime
});
}
... ...
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
using IndustrialControl.Services;
using IndustrialControl.Models;
using IndustrialControl.Services;
using System.Collections.ObjectModel;
namespace IndustrialControl.ViewModels
{
... ... @@ -44,7 +44,7 @@ namespace IndustrialControl.ViewModels
// ================ 初始化入口(页面 OnAppearing 调用) ================
public async Task InitializeFromSearchAsync(
string instockId, string instockNo, string orderType, string orderTypeName,
string purchaseNo, string supplierName, string createdTime,string workOrderNo,string materialName,int instockQty)
string purchaseNo, string supplierName, string createdTime, string workOrderNo, string materialName, int instockQty)
{
// 1) 基础信息
InstockId = instockId;
... ...
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
using IndustrialControl.Services;
using IndustrialControl.Models;
using IndustrialControl.Services;
using System.Collections.ObjectModel;
namespace IndustrialControl.ViewModels
{
... ... @@ -14,12 +14,11 @@ namespace IndustrialControl.ViewModels
// === 基础信息(由搜索页带入) ===
[ObservableProperty] private string? outstockId;
[ObservableProperty] private string? outstockNo;
[ObservableProperty] private string? orderType;
[ObservableProperty] private string? orderTypeName;
[ObservableProperty] private string? requisitionMaterialNo;
[ObservableProperty] private string? returnNo;
[ObservableProperty] private string? deliveryNo;
[ObservableProperty] private string? createdTime;
[ObservableProperty] private string? customer;
[ObservableProperty] private string? expectedDeliveryTime;
[ObservableProperty] private string? saleNo;
[ObservableProperty] private string? deliveryMemo;
// 列表数据源
public ObservableCollection<string> AvailableBins { get; } = new();
... ... @@ -51,18 +50,17 @@ namespace IndustrialControl.ViewModels
// ================ 初始化入口(页面 OnAppearing 调用) ================
public async Task InitializeFromSearchAsync(
string outstockId, string outstockNo, string orderType, string orderTypeName,
string requisitionMaterialNo, string returnNo, string deliveryNo, string createdTime)
string outstockId, string outstockNo, string deliveryNo, string customer,
string expectedDeliveryTime, string saleNo, string deliveryMemo)
{
// 1) 基础信息
OutstockId = outstockId;
OutstockNo = outstockNo;
OrderType = orderType;
OrderTypeName = orderTypeName;
RequisitionMaterialNo = requisitionMaterialNo;
ReturnNo = returnNo;
DeliveryNo = deliveryNo;
CreatedTime = createdTime;
Customer = customer;
ExpectedDeliveryTime = expectedDeliveryTime;
SaleNo = saleNo;
DeliveryMemo = deliveryMemo;
// 2) 下拉库位(如无接口可留空或使用后端返回的 location 聚合)
AvailableBins.Clear();
... ... @@ -111,7 +109,7 @@ namespace IndustrialControl.ViewModels
Qty = r.Qty
});
// 聚合可选库位
if (!string.IsNullOrWhiteSpace(r.Location) && !AvailableBins.Contains(r.Location))
AvailableBins.Add(r.Location);
... ...
... ... @@ -77,12 +77,9 @@ public partial class OutboundMaterialSearchViewModel : ObservableObject
{
["outstockId"] = o.outstockId,
["outstockNo"] = o.outstockNo,
["orderType"] = o.orderType,
["orderTypeName"] = o.orderTypeName,
["requisitionMaterialNo"] = o.requisitionMaterialNo,
["returnNo"] = o.returnNo,
["deliveryNo"] = o.deliveryNo,
["createdTime"] = o.createdTime
["workOrderNo"] = o.workOrderNo,
["memo"] = o.memo
});
}
... ... @@ -101,5 +98,10 @@ public record OutboundOrderSummary(
string returnNo,
string deliveryNo,
string requisitionMaterialNo,
string customer,
string deliveryMemo,
string expectedDeliveryTime,
string memo,
string saleNo,
string createdTime
);
... ...
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
using IndustrialControl.Services;
using IndustrialControl.Models;
using IndustrialControl.Services;
using System.Collections.ObjectModel;
namespace IndustrialControl.ViewModels
{
... ... @@ -14,12 +14,9 @@ namespace IndustrialControl.ViewModels
// === 基础信息(由搜索页带入) ===
[ObservableProperty] private string? outstockId;
[ObservableProperty] private string? outstockNo;
[ObservableProperty] private string? orderType;
[ObservableProperty] private string? orderTypeName;
[ObservableProperty] private string? requisitionMaterialNo;
[ObservableProperty] private string? returnNo;
[ObservableProperty] private string? deliveryNo;
[ObservableProperty] private string? createdTime;
[ObservableProperty] private string? workOrderNo;
[ObservableProperty] private string? memo;
// 列表数据源
public ObservableCollection<string> AvailableBins { get; } = new();
... ... @@ -51,18 +48,17 @@ namespace IndustrialControl.ViewModels
// ================ 初始化入口(页面 OnAppearing 调用) ================
public async Task InitializeFromSearchAsync(
string outstockId, string outstockNo, string orderType, string orderTypeName,
string requisitionMaterialNo, string returnNo,string deliveryNo, string createdTime)
string outstockId, string outstockNo,
string requisitionMaterialNo, string workOrderNo, string memo)
{
// 1) 基础信息
OutstockId = outstockId;
OutstockNo = outstockNo;
OrderType = orderType;
OrderTypeName = orderTypeName;
RequisitionMaterialNo = requisitionMaterialNo;
ReturnNo = returnNo;
DeliveryNo = deliveryNo;
CreatedTime = createdTime;
WorkOrderNo = workOrderNo;
Memo = memo;
// 2) 下拉库位(如无接口可留空或使用后端返回的 location 聚合)
AvailableBins.Clear();
... ... @@ -374,6 +370,6 @@ namespace IndustrialControl.ViewModels
public int OutstockQty { get; set; } //出库数量
public int Qty { get; set; } //已扫描数
public string Bin { get; set; } = "请选择";
}
}
... ...
{ "sdk": { "version": "8.0.414" } }
... ...