作者 李壮

Initial commit

正在显示 55 个修改的文件 包含 3083 行增加0 行删除
  1 +## 忽略编译输出
  2 +bin/
  3 +obj/
  4 +[Bb]uild/
  5 +[Bb]in/
  6 +[Oo]bj/
  7 +
  8 +## Visual Studio 相关
  9 +.vs/
  10 +*.user
  11 +*.suo
  12 +*.userosscache
  13 +*.sln.docstates
  14 +
  15 +## Rider / JetBrains
  16 +.idea/
  17 +*.sln.iml
  18 +
  19 +## MAUI 平台输出
  20 +# Android
  21 +*.apk
  22 +*.aab
  23 +platforms/android/
  24 +**/bin/Debug/
  25 +**/bin/Release/
  26 +
  27 +# iOS / MacCatalyst
  28 +*.app
  29 +*.ipa
  30 +*.dSYM
  31 +platforms/ios/
  32 +platforms/maccatalyst/
  33 +
  34 +# Windows
  35 +*.appx
  36 +*.appxbundle
  37 +*.msix
  38 +*.msixbundle
  39 +AppPackages/
  40 +
  41 +## 日志文件
  42 +*.log
  43 +
  44 +## 临时文件
  45 +~*
  46 +*.tmp
  47 +*.temp
  48 +
  49 +## 备份文件
  50 +*.bak
  51 +*.backup
  52 +
  53 +## NuGet
  54 +*.nupkg
  55 +*.snupkg
  56 +# 包缓存目录
  57 +packages/
  58 +# NuGet 本地源
  59 +.nuget/
  60 +
  61 +## 发布/打包文件
  62 +publish/
  63 +artifacts/
  64 +
  65 +## 配置文件(用户环境相关)
  66 +appsettings.Development.json
  67 +appsettings.Local.json
  68 +local.settings.json
  69 +
  70 +## 其他
  71 +# macOS
  72 +.DS_Store
  73 +# Windows 缩略图
  74 +Thumbs.db
  1 +<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
  2 + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  3 + x:Class="IndustrialControl.App">
  4 + <Application.Resources>
  5 + <ResourceDictionary>
  6 + <Style x:Key="BlueCardStyle" TargetType="Frame">
  7 + <Setter Property="CornerRadius" Value="12"/>
  8 + <Setter Property="Padding" Value="15"/>
  9 + <Setter Property="HasShadow" Value="True"/>
  10 + <Setter Property="BackgroundColor" Value="#1976D2"/>
  11 + <Setter Property="VisualStateManager.VisualStateGroups">
  12 + <Setter.Value>
  13 + <VisualStateGroupList>
  14 + <VisualStateGroup Name="CommonStates">
  15 + <VisualState Name="Normal">
  16 + <VisualState.Setters>
  17 + <Setter Property="BackgroundColor" Value="#1976D2"/>
  18 + </VisualState.Setters>
  19 + </VisualState>
  20 + <VisualState Name="Pressed">
  21 + <VisualState.Setters>
  22 + <Setter Property="BackgroundColor" Value="#2196F3"/>
  23 + </VisualState.Setters>
  24 + </VisualState>
  25 + </VisualStateGroup>
  26 + </VisualStateGroupList>
  27 + </Setter.Value>
  28 + </Setter>
  29 + </Style>
  30 +
  31 + </ResourceDictionary>
  32 + </Application.Resources>
  33 +</Application>
  1 +namespace IndustrialControl;
  2 +
  3 +public partial class App : Application
  4 +{
  5 + public App(AppShell shell)
  6 + {
  7 + InitializeComponent();
  8 + MainPage = shell;
  9 + }
  10 +}
  1 +<?xml version="1.0" encoding="UTF-8" ?>
  2 +<Shell
  3 + x:Class="IndustrialControl.AppShell"
  4 + xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
  5 + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  6 + Shell.FlyoutBehavior="Disabled"
  7 + Title="IndustrialControl" />
  1 +using Microsoft.Extensions.DependencyInjection;
  2 +using Microsoft.Maui.Controls;
  3 +
  4 +namespace IndustrialControl
  5 +{
  6 + public partial class AppShell : Shell
  7 + {
  8 + public AppShell(IServiceProvider sp)
  9 + {
  10 + InitializeComponent();
  11 +
  12 + // Tab: 登录
  13 + var login = new ShellContent
  14 + {
  15 + Title = "登录",
  16 + Route = "Login",
  17 + ContentTemplate = new DataTemplate(() => sp.GetRequiredService<Pages.LoginPage>())
  18 + };
  19 +
  20 + // Tab: 主页
  21 + var home = new ShellContent
  22 + {
  23 + Title = "主页",
  24 + Route = "Home",
  25 + ContentTemplate = new DataTemplate(() => sp.GetRequiredService<Pages.HomePage>())
  26 + };
  27 +
  28 + // Tab: 管理员
  29 + var admin = new ShellContent
  30 + {
  31 + Title = "管理员",
  32 + Route = "Admin",
  33 + ContentTemplate = new DataTemplate(() => sp.GetRequiredService<Pages.AdminPage>())
  34 + };
  35 +
  36 + // Tab: 日志
  37 + var logs = new ShellContent
  38 + {
  39 + Title = "日志",
  40 + Route = "Logs",
  41 + ContentTemplate = new DataTemplate(() => sp.GetRequiredService<Pages.LogsPage>())
  42 + };
  43 +
  44 + var tabBar = new TabBar();
  45 + tabBar.Items.Add(login);
  46 + tabBar.Items.Add(home);
  47 + tabBar.Items.Add(admin);
  48 + tabBar.Items.Add(logs);
  49 +
  50 + Items.Add(tabBar);
  51 +
  52 + Routing.RegisterRoute(nameof(Pages.InboundMaterialPage), typeof(Pages.InboundMaterialPage));
  53 + Routing.RegisterRoute(nameof(Pages.InboundProductionPage), typeof(Pages.InboundProductionPage));
  54 + Routing.RegisterRoute(nameof(Pages.OutboundMaterialPage), typeof(Pages.OutboundMaterialPage));
  55 + Routing.RegisterRoute(nameof(Pages.OutboundFinishedPage), typeof(Pages.OutboundFinishedPage));
  56 + }
  57 + }
  58 +}
  1 +<Project Sdk="Microsoft.NET.Sdk">
  2 +
  3 + <PropertyGroup>
  4 + <TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks>
  5 + <TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>
  6 + <!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
  7 + <!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
  8 +
  9 + <!-- Note for MacCatalyst:
  10 + The default runtime is maccatalyst-x64, except in Release config, in which case the default is maccatalyst-x64;maccatalyst-arm64.
  11 + When specifying both architectures, use the plural <RuntimeIdentifiers> instead of the singular <RuntimeIdentifier>.
  12 + The Mac App Store will NOT accept apps with ONLY maccatalyst-arm64 indicated;
  13 + either BOTH runtimes must be indicated or ONLY macatalyst-x64. -->
  14 + <!-- For example: <RuntimeIdentifiers>maccatalyst-x64;maccatalyst-arm64</RuntimeIdentifiers> -->
  15 +
  16 + <OutputType>Exe</OutputType>
  17 + <RootNamespace>IndustrialControl</RootNamespace>
  18 + <UseMaui>true</UseMaui>
  19 + <SingleProject>true</SingleProject>
  20 + <ImplicitUsings>enable</ImplicitUsings>
  21 + <Nullable>enable</Nullable>
  22 +
  23 + <!-- Display name -->
  24 + <ApplicationTitle>IndustrialControl</ApplicationTitle>
  25 +
  26 + <!-- App Identifier -->
  27 + <ApplicationId>com.companyname.industrialcontrol</ApplicationId>
  28 +
  29 + <!-- Versions -->
  30 + <ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
  31 + <ApplicationVersion>1</ApplicationVersion>
  32 +
  33 + <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">11.0</SupportedOSPlatformVersion>
  34 + <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">13.1</SupportedOSPlatformVersion>
  35 + <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
  36 + <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
  37 + <TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
  38 + <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
  39 + </PropertyGroup>
  40 +
  41 + <ItemGroup>
  42 + <!-- App Icon -->
  43 + <MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />
  44 +
  45 + <!-- Splash Screen -->
  46 + <MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />
  47 +
  48 + <!-- Images -->
  49 + <MauiImage Include="Resources\Images\*" />
  50 + <MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="300,185" />
  51 +
  52 + <!-- Custom Fonts -->
  53 + <MauiFont Include="Resources\Fonts\*" />
  54 +
  55 + <!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
  56 + <MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
  57 + </ItemGroup>
  58 +
  59 + <ItemGroup>
  60 + <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
  61 + <PackageReference Include="Microsoft.Maui.Controls" Version="8.0.0"/>
  62 + <PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.0" />
  63 + <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.1" />
  64 + </ItemGroup>
  65 +
  66 + <ItemGroup>
  67 + <MauiXaml Update="App.xaml">
  68 + <Generator>MSBuild:Compile</Generator>
  69 + </MauiXaml>
  70 + <MauiXaml Update="Pages\AdminPage.xaml">
  71 + <Generator>MSBuild:Compile</Generator>
  72 + </MauiXaml>
  73 + <MauiXaml Update="Pages\HomePage.xaml">
  74 + <Generator>MSBuild:Compile</Generator>
  75 + </MauiXaml>
  76 + <MauiXaml Update="Pages\InboundMaterialPage.xaml">
  77 + <Generator>MSBuild:Compile</Generator>
  78 + </MauiXaml>
  79 + <MauiXaml Update="Pages\InboundProductionPage.xaml">
  80 + <Generator>MSBuild:Compile</Generator>
  81 + </MauiXaml>
  82 + <MauiXaml Update="Pages\LoginPage.xaml">
  83 + <Generator>MSBuild:Compile</Generator>
  84 + </MauiXaml>
  85 + <MauiXaml Update="Pages\LogsPage.xaml">
  86 + <Generator>MSBuild:Compile</Generator>
  87 + </MauiXaml>
  88 + <MauiXaml Update="Pages\OutboundFinishedPage.xaml">
  89 + <Generator>MSBuild:Compile</Generator>
  90 + </MauiXaml>
  91 + <MauiXaml Update="Pages\OutboundMaterialPage.xaml">
  92 + <Generator>MSBuild:Compile</Generator>
  93 + </MauiXaml>
  94 + </ItemGroup>
  95 +
  96 +</Project>
  1 +
  2 +Microsoft Visual Studio Solution File, Format Version 12.00
  3 +# Visual Studio Version 17
  4 +VisualStudioVersion = 17.0.31903.59
  5 +MinimumVisualStudioVersion = 10.0.40219.1
  6 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IndustrialControl", "IndustrialControl.csproj", "{A54387CA-553D-4CE0-B86C-08A45497D703}"
  7 +EndProject
  8 +Global
  9 + GlobalSection(SolutionConfigurationPlatforms) = preSolution
  10 + Debug|Any CPU = Debug|Any CPU
  11 + Debug|x64 = Debug|x64
  12 + Debug|x86 = Debug|x86
  13 + Release|Any CPU = Release|Any CPU
  14 + Release|x64 = Release|x64
  15 + Release|x86 = Release|x86
  16 + EndGlobalSection
  17 + GlobalSection(ProjectConfigurationPlatforms) = postSolution
  18 + {A54387CA-553D-4CE0-B86C-08A45497D703}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
  19 + {A54387CA-553D-4CE0-B86C-08A45497D703}.Debug|Any CPU.Build.0 = Debug|Any CPU
  20 + {A54387CA-553D-4CE0-B86C-08A45497D703}.Debug|x64.ActiveCfg = Debug|Any CPU
  21 + {A54387CA-553D-4CE0-B86C-08A45497D703}.Debug|x64.Build.0 = Debug|Any CPU
  22 + {A54387CA-553D-4CE0-B86C-08A45497D703}.Debug|x86.ActiveCfg = Debug|Any CPU
  23 + {A54387CA-553D-4CE0-B86C-08A45497D703}.Debug|x86.Build.0 = Debug|Any CPU
  24 + {A54387CA-553D-4CE0-B86C-08A45497D703}.Release|Any CPU.ActiveCfg = Release|Any CPU
  25 + {A54387CA-553D-4CE0-B86C-08A45497D703}.Release|Any CPU.Build.0 = Release|Any CPU
  26 + {A54387CA-553D-4CE0-B86C-08A45497D703}.Release|x64.ActiveCfg = Release|Any CPU
  27 + {A54387CA-553D-4CE0-B86C-08A45497D703}.Release|x64.Build.0 = Release|Any CPU
  28 + {A54387CA-553D-4CE0-B86C-08A45497D703}.Release|x86.ActiveCfg = Release|Any CPU
  29 + {A54387CA-553D-4CE0-B86C-08A45497D703}.Release|x86.Build.0 = Release|Any CPU
  30 + EndGlobalSection
  31 + GlobalSection(SolutionProperties) = preSolution
  32 + HideSolutionNode = FALSE
  33 + EndGlobalSection
  34 +EndGlobal
  1 +using Microsoft.Extensions.Logging;
  2 +using Microsoft.Extensions.DependencyInjection;
  3 +using IndustrialControl.Services;
  4 +
  5 +namespace IndustrialControl
  6 +{
  7 + public static class MauiProgram
  8 + {
  9 + public static MauiApp CreateMauiApp()
  10 + {
  11 + var builder = MauiApp.CreateBuilder();
  12 + builder
  13 + .UseMauiApp<App>()
  14 + .ConfigureFonts(fonts =>
  15 + {
  16 + fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
  17 + fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
  18 + });
  19 + builder.Services.AddSingleton<AppShell>();
  20 + // 注册 ConfigLoader
  21 + builder.Services.AddSingleton<Services.ConfigLoader>();
  22 + builder.Services.AddSingleton<Services.LogService>();
  23 + builder.Services.AddSingleton<IWarehouseDataService, MockWarehouseDataService>();
  24 +
  25 + // 扫码服务
  26 + builder.Services.AddSingleton<ScanService>();
  27 +
  28 +
  29 + // ===== 注册 ViewModels =====
  30 + builder.Services.AddTransient<ViewModels.LoginViewModel>();
  31 + builder.Services.AddTransient<ViewModels.HomeViewModel>();
  32 + builder.Services.AddTransient<ViewModels.AdminViewModel>();
  33 + builder.Services.AddTransient<ViewModels.LogsViewModel>();
  34 + builder.Services.AddTransient<ViewModels.InboundMaterialViewModel>();
  35 + builder.Services.AddTransient<ViewModels.InboundProductionViewModel>();
  36 + builder.Services.AddTransient<ViewModels.OutboundMaterialViewModel>();
  37 + builder.Services.AddTransient<ViewModels.OutboundFinishedViewModel>();
  38 +
  39 + // ===== 注册 Pages(DI 创建)=====
  40 + builder.Services.AddTransient<Pages.LoginPage>();
  41 + builder.Services.AddTransient<Pages.HomePage>();
  42 + builder.Services.AddTransient<Pages.AdminPage>();
  43 + builder.Services.AddTransient<Pages.LogsPage>();
  44 +
  45 + // 注册需要路由的页面
  46 + builder.Services.AddTransient<Pages.InboundMaterialPage>();
  47 + builder.Services.AddTransient<Pages.InboundProductionPage>();
  48 + builder.Services.AddTransient<Pages.OutboundMaterialPage>();
  49 + builder.Services.AddTransient<Pages.OutboundFinishedPage>();
  50 +
  51 +#if DEBUG
  52 + builder.Logging.AddDebug();
  53 +#endif
  54 +
  55 + return builder.Build();
  56 + }
  57 + }
  58 +}
  1 +namespace IndustrialControl.Models;
  2 +
  3 +public class ServerSettings
  4 +{
  5 + public string IpAddress { get; set; } = "192.168.1.100";
  6 + public int Port { get; set; } = 8080;
  7 +}
  8 +public class ApiEndpoints
  9 +{
  10 + public string Login { get; set; } = "/sso/login";
  11 +}
  12 +public class LoggingSettings
  13 +{
  14 + public string Level { get; set; } = "Information";
  15 +}
  16 +public class AppConfig
  17 +{
  18 + public int SchemaVersion { get; set; } = 3;
  19 + public ServerSettings Server { get; set; } = new();
  20 + public ApiEndpoints ApiEndpoints { get; set; } = new();
  21 + public LoggingSettings Logging { get; set; } = new();
  22 +}
  1 +namespace IndustrialControl.Models;
  2 +public record ScanLine(int Index, string Barcode, string? Bin, int Qty);
  1 +<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
  2 + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  3 + x:Class="IndustrialControl.Pages.AdminPage"
  4 + Title="管理员设置">
  5 + <ScrollView>
  6 + <VerticalStackLayout Padding="24" Spacing="12">
  7 + <Label Text="配置版本(只读)" />
  8 + <Entry Text="{Binding SchemaVersion}" IsReadOnly="True"/>
  9 + <Label Text="后端 IP 地址" />
  10 + <Entry Text="{Binding IpAddress}" Keyboard="Numeric"/>
  11 + <Label Text="端口号" />
  12 + <Entry Text="{Binding Port}" Keyboard="Numeric"/>
  13 + <Frame BackgroundColor="#eef" Padding="8">
  14 + <Label Text="{Binding BaseUrl}" />
  15 + </Frame>
  16 + <HorizontalStackLayout Spacing="12" Margin="0,12,0,0">
  17 + <Button Text="保存" Command="{Binding SaveCommand}"/>
  18 + <Button Text="从包内默认重载" Command="{Binding ResetToPackageCommand}"/>
  19 + </HorizontalStackLayout>
  20 + </VerticalStackLayout>
  21 + </ScrollView>
  22 +</ContentPage>
  1 +namespace IndustrialControl.Pages;
  2 +public partial class AdminPage : ContentPage
  3 +{ public AdminPage(ViewModels.AdminViewModel vm)
  4 + { InitializeComponent(); BindingContext = vm; }
  5 +}
  1 +<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
  2 + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  3 + x:Class="IndustrialControl.Pages.HomePage"
  4 + Title="仓储作业">
  5 +
  6 + <VerticalStackLayout Padding="24" Spacing="16">
  7 + <Frame Style="{StaticResource BlueCardStyle}">
  8 + <Label Text="物料入库" FontSize="18" TextColor="White" VerticalTextAlignment="Center"/>
  9 + <Frame.GestureRecognizers>
  10 + <TapGestureRecognizer Tapped="OnInMat" NumberOfTapsRequired="1" />
  11 + </Frame.GestureRecognizers>
  12 + </Frame>
  13 +
  14 + <Frame Style="{StaticResource BlueCardStyle}">
  15 + <Label Text="生产入库" FontSize="18" TextColor="White" VerticalTextAlignment="Center"/>
  16 + <Frame.GestureRecognizers>
  17 + <TapGestureRecognizer Tapped="OnInProd" NumberOfTapsRequired="1" />
  18 + </Frame.GestureRecognizers>
  19 + </Frame>
  20 +
  21 + <Frame Style="{StaticResource BlueCardStyle}">
  22 + <Label Text="物料出库" FontSize="18" TextColor="White" VerticalTextAlignment="Center"/>
  23 + <Frame.GestureRecognizers>
  24 + <TapGestureRecognizer Tapped="OnOutMat" NumberOfTapsRequired="1" />
  25 + </Frame.GestureRecognizers>
  26 + </Frame>
  27 +
  28 + <Frame Style="{StaticResource BlueCardStyle}">
  29 + <Label Text="成品出库/装货" FontSize="18" TextColor="White" VerticalTextAlignment="Center"/>
  30 + <Frame.GestureRecognizers>
  31 + <TapGestureRecognizer Tapped="OnOutFinished" NumberOfTapsRequired="1" />
  32 + </Frame.GestureRecognizers>
  33 + </Frame>
  34 +
  35 + </VerticalStackLayout>
  36 +</ContentPage>
  1 +namespace IndustrialControl.Pages
  2 +{
  3 + public partial class HomePage : ContentPage
  4 + {
  5 + public HomePage()
  6 + {
  7 + InitializeComponent();
  8 + }
  9 +
  10 + private async void OnInMat(object sender, EventArgs e)
  11 + {
  12 + await Shell.Current.GoToAsync(nameof(InboundMaterialPage));
  13 + }
  14 +
  15 + private async void OnInProd(object sender, EventArgs e)
  16 + {
  17 + await Shell.Current.GoToAsync(nameof(InboundProductionPage));
  18 + }
  19 +
  20 + private async void OnOutMat(object sender, EventArgs e)
  21 + {
  22 + await Shell.Current.GoToAsync(nameof(OutboundMaterialPage));
  23 + }
  24 +
  25 + private async void OnOutFinished(object sender, EventArgs e)
  26 + {
  27 + await Shell.Current.GoToAsync(nameof(OutboundFinishedPage));
  28 + }
  29 + }
  30 +}
  1 +<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
  2 + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  3 + x:Class="IndustrialControl.Pages.InboundMaterialPage"
  4 + BackgroundColor="White">
  5 +
  6 + <Grid RowDefinitions="Auto,Auto,Auto,*,Auto">
  7 +
  8 + <!-- 顶部蓝色标题栏 -->
  9 + <Grid BackgroundColor="#007BFF" HeightRequest="60" Padding="16,0">
  10 + <Label Text="仓储管理系统"
  11 + VerticalOptions="Center"
  12 + TextColor="White"
  13 + FontSize="18"
  14 + FontAttributes="Bold"/>
  15 + </Grid>
  16 +
  17 + <!-- 入库单/条码扫描 -->
  18 + <Grid Grid.Row="1" ColumnDefinitions="*,60" Padding="16,8">
  19 + <Entry x:Name="ScanEntry"
  20 + Placeholder="请输入/扫描单号/物料/包装条码"
  21 + FontSize="14"
  22 + VerticalOptions="Center"
  23 + BackgroundColor="White"
  24 + HeightRequest="40"
  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"/>
  32 + </Grid>
  33 +
  34 + <!-- 基础信息 -->
  35 + <Frame Grid.Row="2" Margin="16,0" Padding="8" BorderColor="#CCCCCC" BackgroundColor="White">
  36 + <Grid RowDefinitions="Auto,Auto" ColumnDefinitions="Auto,*,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="TailTruncation" />
  41 + <Label Grid.Row="0" Grid.Column="2" Text="关联到货单号:" FontAttributes="Bold" />
  42 + <Label Grid.Row="0" Grid.Column="3" Text="{Binding LinkedDeliveryNo}" LineBreakMode="TailTruncation" />
  43 +
  44 + <!-- 第二行 -->
  45 + <Label Grid.Row="1" Grid.Column="0" Text="关联采购单:" FontAttributes="Bold" />
  46 + <Label Grid.Row="1" Grid.Column="1" Text="{Binding LinkedPurchaseNo}" LineBreakMode="TailTruncation" />
  47 + <Label Grid.Row="1" Grid.Column="2" Text="供应商:" FontAttributes="Bold" />
  48 + <Label Grid.Row="1" Grid.Column="3" Text="{Binding Supplier}" LineBreakMode="TailTruncation" />
  49 + </Grid>
  50 + </Frame>
  51 +
  52 + <!-- Tab切换 -->
  53 + <Grid Grid.Row="3" RowDefinitions="Auto,Auto,*" Margin="0">
  54 + <Grid ColumnDefinitions="*,*" BackgroundColor="White">
  55 + <Button Text="待入库明细"
  56 + Command="{Binding ShowPendingCommand}"
  57 + BackgroundColor="{Binding PendingTabColor}"
  58 + TextColor="{Binding PendingTextColor}" />
  59 + <Button Text="扫描明细"
  60 + Grid.Column="1"
  61 + Command="{Binding ShowScannedCommand}"
  62 + BackgroundColor="{Binding ScannedTabColor}"
  63 + TextColor="{Binding ScannedTextColor}" />
  64 + </Grid>
  65 +
  66 + <!-- 待入库明细表头 -->
  67 + <Grid Grid.Row="1" ColumnDefinitions="*,*,*,*,*" BackgroundColor="#F2F2F2" IsVisible="{Binding IsPendingVisible}" Padding="8">
  68 + <Label Text="物料名称" FontAttributes="Bold" />
  69 + <Label Grid.Column="1" Text="规格" FontAttributes="Bold" />
  70 + <Label Grid.Column="2" Text="待入库数量" FontAttributes="Bold" />
  71 + <Label Grid.Column="3" Text="预入库库位" FontAttributes="Bold" />
  72 + <Label Grid.Column="4" Text="已扫描数量" FontAttributes="Bold" />
  73 + </Grid>
  74 +
  75 + <!-- 待入库明细列表 -->
  76 + <CollectionView Grid.Row="2"
  77 + ItemsSource="{Binding PendingList}"
  78 + IsVisible="{Binding IsPendingVisible}"
  79 + SelectionMode="Single">
  80 + <CollectionView.ItemTemplate>
  81 + <DataTemplate>
  82 + <Grid ColumnDefinitions="*,*,*,*,*" Padding="8" BackgroundColor="White">
  83 + <VisualStateManager.VisualStateGroups>
  84 + <VisualStateGroup Name="CommonStates">
  85 + <VisualState Name="Normal">
  86 + <VisualState.Setters>
  87 + <Setter Property="BackgroundColor" Value="White"/>
  88 + </VisualState.Setters>
  89 + </VisualState>
  90 + <VisualState Name="Selected">
  91 + <VisualState.Setters>
  92 + <Setter Property="BackgroundColor" Value="#CCFFCC"/>
  93 + </VisualState.Setters>
  94 + </VisualState>
  95 + </VisualStateGroup>
  96 + </VisualStateManager.VisualStateGroups>
  97 + <Label Text="{Binding Name}" />
  98 + <Label Grid.Column="1" Text="{Binding Spec}" />
  99 + <Label Grid.Column="2" Text="{Binding PendingQty}" />
  100 + <Label Grid.Column="3" Text="{Binding Bin}" />
  101 + <Label Grid.Column="4" Text="{Binding ScannedQty}" />
  102 + </Grid>
  103 + </DataTemplate>
  104 + </CollectionView.ItemTemplate>
  105 + </CollectionView>
  106 +
  107 + <!-- 扫描明细表头 -->
  108 + <Grid Grid.Row="1" ColumnDefinitions="40,*,*,*,*,*" BackgroundColor="#F2F2F2" IsVisible="{Binding IsScannedVisible}" 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 + </Grid>
  116 +
  117 + <!-- 扫描明细列表 -->
  118 + <!-- 扫描明细列表 -->
  119 + <CollectionView Grid.Row="2"
  120 + ItemsSource="{Binding ScannedList}"
  121 + IsVisible="{Binding IsScannedVisible}"
  122 + SelectionMode="Single">
  123 + <CollectionView.ItemTemplate>
  124 + <DataTemplate>
  125 + <Grid ColumnDefinitions="40,*,*,*,*,*" 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 +
  141 + <CheckBox IsChecked="{Binding IsSelected}" />
  142 + <Label Grid.Column="1" Text="{Binding Barcode}" />
  143 + <Label Grid.Column="2" Text="{Binding Name}" />
  144 + <Label Grid.Column="3" Text="{Binding Spec}" />
  145 +
  146 + <!-- 改成 Picker -->
  147 + <Picker Grid.Column="4"
  148 + ItemsSource="{Binding Source={RelativeSource AncestorType={x:Type ContentPage}}, Path=BindingContext.AvailableBins}"
  149 + SelectedItem="{Binding Bin, Mode=TwoWay}"
  150 + FontSize="14"
  151 + Title="请选择"
  152 + HorizontalOptions="FillAndExpand" />
  153 +
  154 + <Label Grid.Column="5" Text="{Binding Qty}" />
  155 + </Grid>
  156 + </DataTemplate>
  157 + </CollectionView.ItemTemplate>
  158 + </CollectionView>
  159 +
  160 + </Grid>
  161 +
  162 + <!-- 底部按钮 -->
  163 + <!-- 底部按钮 -->
  164 + <Grid Grid.Row="4" ColumnDefinitions="*,*,*" Padding="16,8" ColumnSpacing="10">
  165 +
  166 + <!-- 扫描通过 -->
  167 + <Grid BackgroundColor="#4CAF50"
  168 + HorizontalOptions="Fill"
  169 + VerticalOptions="Fill">
  170 + <Grid.GestureRecognizers>
  171 + <TapGestureRecognizer Command="{Binding PassScanCommand}" />
  172 + </Grid.GestureRecognizers>
  173 + <StackLayout Orientation="Horizontal"
  174 + HorizontalOptions="Center"
  175 + VerticalOptions="Center">
  176 + <Image Source="pass.png" HeightRequest="20" WidthRequest="20" />
  177 + <Label Text="扫描通过"
  178 + Margin="5,0,0,0"
  179 + VerticalOptions="Center"
  180 + TextColor="White" />
  181 + </StackLayout>
  182 + </Grid>
  183 +
  184 + <!-- 取消扫描 -->
  185 + <Grid Grid.Column="1"
  186 + BackgroundColor="#F44336"
  187 + HorizontalOptions="Fill"
  188 + VerticalOptions="Fill">
  189 + <Grid.GestureRecognizers>
  190 + <TapGestureRecognizer Command="{Binding CancelScanCommand}" />
  191 + </Grid.GestureRecognizers>
  192 + <StackLayout Orientation="Horizontal"
  193 + HorizontalOptions="Center"
  194 + VerticalOptions="Center">
  195 + <Image Source="cancel.png" HeightRequest="20" WidthRequest="20" />
  196 + <Label Text="取消扫描"
  197 + Margin="5,0,0,0"
  198 + VerticalOptions="Center"
  199 + TextColor="White" />
  200 + </StackLayout>
  201 + </Grid>
  202 +
  203 + <!-- 确认入库 -->
  204 + <Grid Grid.Column="2"
  205 + BackgroundColor="#2196F3"
  206 + HorizontalOptions="Fill"
  207 + VerticalOptions="Fill">
  208 + <Grid.GestureRecognizers>
  209 + <TapGestureRecognizer Command="{Binding ConfirmCommand}" />
  210 + </Grid.GestureRecognizers>
  211 + <StackLayout Orientation="Horizontal"
  212 + HorizontalOptions="Center"
  213 + VerticalOptions="Center">
  214 + <Image Source="confirm.png" HeightRequest="20" WidthRequest="20" />
  215 + <Label Text="确认入库"
  216 + Margin="5,0,0,0"
  217 + VerticalOptions="Center"
  218 + TextColor="White" />
  219 + </StackLayout>
  220 + </Grid>
  221 +
  222 + </Grid>
  223 +
  224 + </Grid>
  225 +</ContentPage>
  1 +using IndustrialControl.Services;
  2 +using IndustrialControl.ViewModels;
  3 +
  4 +namespace IndustrialControl.Pages;
  5 +
  6 +public partial class InboundMaterialPage : ContentPage
  7 +{
  8 + private readonly ScanService _scanSvc;
  9 + private readonly InboundMaterialViewModel _vm;
  10 +
  11 + public InboundMaterialPage(InboundMaterialViewModel vm, ScanService scanSvc)
  12 + {
  13 + InitializeComponent();
  14 + BindingContext = vm;
  15 + _scanSvc = scanSvc;
  16 + _vm = vm;
  17 + }
  18 +
  19 + protected override void OnAppearing()
  20 + {
  21 + base.OnAppearing();
  22 + _scanSvc.Attach(ScanEntry);
  23 + ScanEntry.Focus();
  24 + }
  25 +
  26 + /// <summary>
  27 + /// 清空扫描记录
  28 + /// </summary>
  29 + void OnClearClicked(object sender, EventArgs e)
  30 + {
  31 + _vm.ClearScan();
  32 + ScanEntry.Text = string.Empty;
  33 + ScanEntry.Focus();
  34 + }
  35 +
  36 + /// <summary>
  37 + /// 预留摄像头扫码
  38 + /// </summary>
  39 + async void OnScanClicked(object sender, EventArgs e)
  40 + {
  41 + await DisplayAlert("提示", "此按钮预留摄像头扫码;硬件扫描直接扣扳机。", "确定");
  42 + }
  43 +
  44 + /// <summary>
  45 + /// 扫描通过按钮点击
  46 + /// </summary>
  47 + void OnPassScanClicked(object sender, EventArgs e)
  48 + {
  49 + _vm.PassSelectedScan();
  50 + }
  51 +
  52 + /// <summary>
  53 + /// 取消扫描按钮点击
  54 + /// </summary>
  55 + void OnCancelScanClicked(object sender, EventArgs e)
  56 + {
  57 + _vm.CancelSelectedScan();
  58 + }
  59 +
  60 + /// <summary>
  61 + /// 确认入库按钮点击
  62 + /// </summary>
  63 + async void OnConfirmClicked(object sender, EventArgs e)
  64 + {
  65 + var ok = await _vm.ConfirmInboundAsync();
  66 + if (ok)
  67 + {
  68 + await DisplayAlert("提示", "入库成功", "确定");
  69 + _vm.ClearAll();
  70 + }
  71 + else
  72 + {
  73 + await DisplayAlert("提示", "入库失败,请检查数据", "确定");
  74 + }
  75 + }
  76 +}
  1 +<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
  2 + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  3 + x:Class="IndustrialControl.Pages.InboundProductionPage"
  4 + BackgroundColor="White">
  5 +
  6 + <Grid RowDefinitions="Auto,Auto,Auto,*,Auto">
  7 +
  8 + <!-- 顶部蓝色标题栏 -->
  9 + <Grid BackgroundColor="#007BFF" HeightRequest="60" Padding="16,0">
  10 + <Label Text="仓储管理系统"
  11 + VerticalOptions="Center"
  12 + TextColor="White"
  13 + FontSize="18"
  14 + FontAttributes="Bold"/>
  15 + </Grid>
  16 +
  17 + <!-- 入库单/条码扫描 -->
  18 + <Grid Grid.Row="1" ColumnDefinitions="*,60" Padding="16,8">
  19 + <Entry x:Name="ScanEntry"
  20 + Placeholder="请扫描入库单/产品/包装条码"
  21 + FontSize="14"
  22 + VerticalOptions="Center"
  23 + BackgroundColor="White"
  24 + HeightRequest="40"
  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"/>
  32 + </Grid>
  33 +
  34 + <!-- 基础信息 -->
  35 + <Frame Grid.Row="2" Margin="16,0" Padding="8" BorderColor="#CCCCCC" BackgroundColor="White">
  36 + <Grid RowDefinitions="Auto,Auto" ColumnDefinitions="Auto,*,Auto,*" ColumnSpacing="8" RowSpacing="6">
  37 + <Label Grid.Row="0" Grid.Column="0" Text="入库单号:" FontAttributes="Bold" />
  38 + <Label Grid.Row="0" Grid.Column="1" Text="{Binding OrderNo}" LineBreakMode="TailTruncation" />
  39 + <Label Grid.Row="0" Grid.Column="2" Text="工单号:" FontAttributes="Bold" />
  40 + <Label Grid.Row="0" Grid.Column="3" Text="{Binding WorkOrderNo}" LineBreakMode="TailTruncation" />
  41 +
  42 + <Label Grid.Row="1" Grid.Column="0" Text="产品名称:" FontAttributes="Bold" />
  43 + <Label Grid.Row="1" Grid.Column="1" Text="{Binding ProductName}" LineBreakMode="TailTruncation" />
  44 + <Label Grid.Row="1" Grid.Column="2" Text="待入库数:" FontAttributes="Bold" />
  45 + <Label Grid.Row="1" Grid.Column="3" Text="{Binding PendingQty}" LineBreakMode="TailTruncation" />
  46 + </Grid>
  47 + </Frame>
  48 +
  49 + <!-- 扫描明细 -->
  50 + <Grid Grid.Row="3" RowDefinitions="Auto,*" Margin="0">
  51 + <!-- 表头 -->
  52 + <Grid ColumnDefinitions="40,*,*,*" BackgroundColor="#F2F2F2" Padding="8">
  53 + <Label Text="选择" FontAttributes="Bold" />
  54 + <Label Grid.Column="1" Text="条码" FontAttributes="Bold" />
  55 + <Label Grid.Column="2" Text="入库库位" FontAttributes="Bold" />
  56 + <Label Grid.Column="3" Text="产品数量" FontAttributes="Bold" />
  57 + </Grid>
  58 +
  59 + <!-- 数据列表 -->
  60 + <CollectionView Grid.Row="1"
  61 + ItemsSource="{Binding Lines}"
  62 + SelectionMode="Single">
  63 + <CollectionView.ItemTemplate>
  64 + <DataTemplate>
  65 + <Grid ColumnDefinitions="40,*,*,*" Padding="8" BackgroundColor="White">
  66 + <VisualStateManager.VisualStateGroups>
  67 + <VisualStateGroup Name="CommonStates">
  68 + <VisualState Name="Normal">
  69 + <VisualState.Setters>
  70 + <Setter Property="BackgroundColor" Value="White"/>
  71 + </VisualState.Setters>
  72 + </VisualState>
  73 + <VisualState Name="Selected">
  74 + <VisualState.Setters>
  75 + <Setter Property="BackgroundColor" Value="#CCFFCC"/>
  76 + </VisualState.Setters>
  77 + </VisualState>
  78 + </VisualStateGroup>
  79 + </VisualStateManager.VisualStateGroups>
  80 +
  81 + <CheckBox IsChecked="{Binding IsSelected}" />
  82 + <Label Grid.Column="1" Text="{Binding Barcode}" />
  83 + <!-- 库位改成 Picker -->
  84 + <Picker Grid.Column="2"
  85 + ItemsSource="{Binding Source={RelativeSource AncestorType={x:Type ContentPage}}, Path=BindingContext.AvailableBins}"
  86 + SelectedItem="{Binding Bin, Mode=TwoWay}"
  87 + FontSize="14"
  88 + Title="请选择"
  89 + HorizontalOptions="FillAndExpand" />
  90 + <Label Grid.Column="3" Text="{Binding Qty}" />
  91 + </Grid>
  92 + </DataTemplate>
  93 + </CollectionView.ItemTemplate>
  94 + </CollectionView>
  95 + </Grid>
  96 +
  97 + <!-- 底部按钮 -->
  98 + <Grid Grid.Row="4" ColumnDefinitions="*,*,*" Padding="16,8" ColumnSpacing="10">
  99 +
  100 + <!-- 扫描通过 -->
  101 + <Grid BackgroundColor="#4CAF50"
  102 + HorizontalOptions="Fill"
  103 + VerticalOptions="Fill">
  104 + <Grid.GestureRecognizers>
  105 + <TapGestureRecognizer Command="{Binding PassScanCommand}" />
  106 + </Grid.GestureRecognizers>
  107 + <StackLayout Orientation="Horizontal"
  108 + HorizontalOptions="Center"
  109 + VerticalOptions="Center">
  110 + <Image Source="pass.png" HeightRequest="20" WidthRequest="20" />
  111 + <Label Text="扫描通过"
  112 + Margin="5,0,0,0"
  113 + VerticalOptions="Center"
  114 + TextColor="White" />
  115 + </StackLayout>
  116 + </Grid>
  117 +
  118 + <!-- 取消扫描 -->
  119 + <Grid Grid.Column="1"
  120 + BackgroundColor="#F44336"
  121 + HorizontalOptions="Fill"
  122 + VerticalOptions="Fill">
  123 + <Grid.GestureRecognizers>
  124 + <TapGestureRecognizer Command="{Binding CancelScanCommand}" />
  125 + </Grid.GestureRecognizers>
  126 + <StackLayout Orientation="Horizontal"
  127 + HorizontalOptions="Center"
  128 + VerticalOptions="Center">
  129 + <Image Source="cancel.png" HeightRequest="20" WidthRequest="20" />
  130 + <Label Text="取消扫描"
  131 + Margin="5,0,0,0"
  132 + VerticalOptions="Center"
  133 + TextColor="White" />
  134 + </StackLayout>
  135 + </Grid>
  136 +
  137 + <!-- 确认入库 -->
  138 + <Grid Grid.Column="2"
  139 + BackgroundColor="#2196F3"
  140 + HorizontalOptions="Fill"
  141 + VerticalOptions="Fill">
  142 + <Grid.GestureRecognizers>
  143 + <TapGestureRecognizer Command="{Binding ConfirmCommand}" />
  144 + </Grid.GestureRecognizers>
  145 + <StackLayout Orientation="Horizontal"
  146 + HorizontalOptions="Center"
  147 + VerticalOptions="Center">
  148 + <Image Source="confirm.png" HeightRequest="20" WidthRequest="20" />
  149 + <Label Text="确认入库"
  150 + Margin="5,0,0,0"
  151 + VerticalOptions="Center"
  152 + TextColor="White" />
  153 + </StackLayout>
  154 + </Grid>
  155 +
  156 + </Grid>
  157 + </Grid>
  158 +</ContentPage>
  1 +using IndustrialControl.Services;
  2 +using IndustrialControl.ViewModels;
  3 +
  4 +namespace IndustrialControl.Pages
  5 +{
  6 + public partial class InboundProductionPage : ContentPage
  7 + {
  8 + private readonly ScanService _scanSvc;
  9 + private readonly InboundProductionViewModel _vm;
  10 +
  11 + public InboundProductionPage(InboundProductionViewModel vm, ScanService scanSvc)
  12 + {
  13 + InitializeComponent();
  14 + BindingContext = vm;
  15 + _scanSvc = scanSvc;
  16 + _vm = vm;
  17 + }
  18 +
  19 + protected override void OnAppearing()
  20 + {
  21 + base.OnAppearing();
  22 + _scanSvc.Attach(ScanEntry);
  23 + ScanEntry.Focus();
  24 + }
  25 +
  26 + /// <summary>
  27 + /// 清空扫描记录
  28 + /// </summary>
  29 + void OnClearClicked(object sender, EventArgs e)
  30 + {
  31 + _vm.ClearScan();
  32 + ScanEntry.Text = string.Empty;
  33 + ScanEntry.Focus();
  34 + }
  35 +
  36 + /// <summary>
  37 + /// 预留摄像头扫码
  38 + /// </summary>
  39 + async void OnScanClicked(object sender, EventArgs e)
  40 + {
  41 + await DisplayAlert("提示", "此按钮预留摄像头扫码;硬件扫描直接扣扳机。", "确定");
  42 + }
  43 +
  44 + /// <summary>
  45 + /// 扫描通过
  46 + /// </summary>
  47 + void OnPassScanClicked(object sender, EventArgs e)
  48 + {
  49 + _vm.PassSelectedScan();
  50 + }
  51 +
  52 + /// <summary>
  53 + /// 取消扫描
  54 + /// </summary>
  55 + void OnCancelScanClicked(object sender, EventArgs e)
  56 + {
  57 + _vm.CancelSelectedScan();
  58 + }
  59 +
  60 + /// <summary>
  61 + /// 确认入库
  62 + /// </summary>
  63 + async void OnConfirmClicked(object sender, EventArgs e)
  64 + {
  65 + var ok = await _vm.ConfirmInboundAsync();
  66 + if (ok)
  67 + {
  68 + await DisplayAlert("提示", "入库成功", "确定");
  69 + _vm.ClearAll();
  70 + }
  71 + else
  72 + {
  73 + await DisplayAlert("提示", "入库失败,请检查数据", "确定");
  74 + }
  75 + }
  76 + }
  77 +}
  1 +<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
  2 + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  3 + xmlns:pages="clr-namespace:IndustrialControl.Pages"
  4 + x:Class="IndustrialControl.Pages.LoginPage"
  5 + Title="登录">
  6 + <Grid Padding="24" RowDefinitions="Auto,*">
  7 + <VerticalStackLayout Spacing="12">
  8 + <Label Text="联创数智管家" FontAttributes="Bold" FontSize="28"/>
  9 + <Entry Placeholder="请输入用户名" Text="{Binding UserName}"/>
  10 + <Entry Placeholder="请输入登录密码" IsPassword="True" Text="{Binding Password}"/>
  11 + <Button Text="登录" Command="{Binding LoginCommand}"/>
  12 + </VerticalStackLayout>
  13 + </Grid>
  14 +</ContentPage>
  1 +namespace IndustrialControl.Pages;
  2 +public partial class LoginPage : ContentPage
  3 +{ public LoginPage(ViewModels.LoginViewModel vm)
  4 + { InitializeComponent(); BindingContext = vm; }
  5 +}
  1 +<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
  2 + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  3 + x:Class="IndustrialControl.Pages.LogsPage"
  4 + Title="实时日志">
  5 + <Grid RowDefinitions="Auto,*" Padding="12">
  6 + <Label Text="当日日志(自动刷新)" FontAttributes="Bold"/>
  7 + <ScrollView Grid.Row="1">
  8 + <Label Text="{Binding LogText}" FontFamily="monospace"/>
  9 + </ScrollView>
  10 + </Grid>
  11 +</ContentPage>
  1 +namespace IndustrialControl.Pages;
  2 +
  3 +public partial class LogsPage : ContentPage
  4 +{
  5 + public LogsPage(ViewModels.LogsViewModel vm){ InitializeComponent(); BindingContext = vm; }
  6 + protected override void OnAppearing(){ base.OnAppearing(); if(BindingContext is ViewModels.LogsViewModel vm) vm.OnAppearing(); }
  7 + protected override void OnDisappearing(){ base.OnDisappearing(); if(BindingContext is ViewModels.LogsViewModel vm) vm.OnDisappearing(); }
  8 +}
  1 +<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
  2 + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  3 + x:Class="IndustrialControl.Pages.OutboundFinishedPage"
  4 + BackgroundColor="White">
  5 +
  6 + <Grid RowDefinitions="Auto,Auto,Auto,*,Auto">
  7 +
  8 + <!-- 顶部蓝色标题栏 -->
  9 + <Grid BackgroundColor="#007BFF" HeightRequest="60" Padding="16,0">
  10 + <Label Text="仓储管理系统"
  11 + VerticalOptions="Center"
  12 + TextColor="White"
  13 + FontSize="18"
  14 + FontAttributes="Bold"/>
  15 + </Grid>
  16 +
  17 + <!-- 出库单/发货单扫描 -->
  18 + <Grid Grid.Row="1" ColumnDefinitions="*,60" Padding="16,8">
  19 + <Entry x:Name="ScanEntry"
  20 + Placeholder="请输入/扫描出库单号/产品条码"
  21 + FontSize="14"
  22 + VerticalOptions="Center"
  23 + BackgroundColor="White"
  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"/>
  32 + </Grid>
  33 +
  34 + <!-- 基础信息 -->
  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" />
  62 +
  63 + </Grid>
  64 + </Frame>
  65 +
  66 +
  67 +
  68 + <!-- Tab切换 + 表格 -->
  69 + <Grid Grid.Row="3" RowDefinitions="Auto,Auto,*" Margin="0">
  70 + <!-- Tab -->
  71 + <Grid ColumnDefinitions="*,*" BackgroundColor="White">
  72 + <Button Text="出库单明细"
  73 + BackgroundColor="{Binding PendingTabColor}"
  74 + TextColor="{Binding PendingTextColor}"
  75 + Clicked="OnPendingTabClicked"/>
  76 + <Button Text="扫描明细"
  77 + Grid.Column="1"
  78 + BackgroundColor="{Binding ScannedTabColor}"
  79 + TextColor="{Binding ScannedTextColor}"
  80 + Clicked="OnScannedTabClicked"/>
  81 + </Grid>
  82 +
  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"/>
  119 + </Grid>
  120 +
  121 + <!-- 出库单明细列表 -->
  122 + <CollectionView Grid.Row="2"
  123 + ItemsSource="{Binding PendingList}"
  124 + IsVisible="{Binding IsPendingVisible}">
  125 + <CollectionView.ItemTemplate>
  126 + <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"/>
  137 + </Grid>
  138 + </DataTemplate>
  139 + </CollectionView.ItemTemplate>
  140 + </CollectionView>
  141 +
  142 +
  143 + <!-- 扫描明细表头 -->
  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"/>
  154 + </Grid>
  155 +
  156 + <!-- 扫描明细列表 -->
  157 + <CollectionView Grid.Row="2"
  158 + ItemsSource="{Binding ScannedList}"
  159 + IsVisible="{Binding IsScannedVisible}">
  160 + <CollectionView.ItemTemplate>
  161 + <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" />
  167 + </Grid>
  168 + </DataTemplate>
  169 + </CollectionView.ItemTemplate>
  170 + </CollectionView>
  171 + </Grid>
  172 +
  173 +
  174 + <!-- 底部按钮 -->
  175 + <Grid Grid.Row="4" ColumnDefinitions="*,*,*" Padding="16,8" ColumnSpacing="10">
  176 +
  177 + <!-- 扫描通过 -->
  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>
  190 + <TapGestureRecognizer Command="{Binding PassScanCommand}" />
  191 + </HorizontalStackLayout.GestureRecognizers>
  192 + </HorizontalStackLayout>
  193 +
  194 + <!-- 取消扫描 -->
  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>
  207 + <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>
  227 +
  228 + </Grid>
  229 +
  230 + </Grid>
  231 +</ContentPage>
  1 +using IndustrialControl.Services;
  2 +using IndustrialControl.ViewModels;
  3 +
  4 +namespace IndustrialControl.Pages
  5 +{
  6 + public partial class OutboundFinishedPage : ContentPage
  7 + {
  8 + private readonly ScanService _scanSvc;
  9 + private readonly OutboundFinishedViewModel _vm;
  10 +
  11 + public OutboundFinishedPage(OutboundFinishedViewModel vm, ScanService scanSvc)
  12 + {
  13 + InitializeComponent();
  14 + BindingContext = vm;
  15 + _scanSvc = scanSvc;
  16 + _vm = vm;
  17 + }
  18 +
  19 + protected override void OnAppearing()
  20 + {
  21 + base.OnAppearing();
  22 + _scanSvc.Attach(ScanEntry);
  23 + ScanEntry.Focus();
  24 + }
  25 +
  26 + /// <summary>
  27 + /// 清空扫描记录
  28 + /// </summary>
  29 + void OnClearClicked(object sender, EventArgs e)
  30 + {
  31 + _vm.ClearScan();
  32 + ScanEntry.Text = string.Empty;
  33 + ScanEntry.Focus();
  34 + }
  35 +
  36 + /// <summary>
  37 + /// 预留摄像头扫码
  38 + /// </summary>
  39 + async void OnScanClicked(object sender, EventArgs e)
  40 + {
  41 + await DisplayAlert("提示", "此按钮预留摄像头扫码;硬件扫描直接扣扳机。", "确定");
  42 + }
  43 +
  44 + /// <summary>
  45 + /// 点击“出库单明细”Tab
  46 + /// </summary>
  47 + void OnPendingTabClicked(object sender, EventArgs e)
  48 + {
  49 + _vm.ShowPendingCommand.Execute(null);
  50 + }
  51 +
  52 + /// <summary>
  53 + /// 点击“扫描明细”Tab
  54 + /// </summary>
  55 + void OnScannedTabClicked(object sender, EventArgs e)
  56 + {
  57 + _vm.ShowScannedCommand.Execute(null);
  58 + }
  59 +
  60 + /// <summary>
  61 + /// 扫描通过
  62 + /// </summary>
  63 + void OnPassScanClicked(object sender, EventArgs e)
  64 + {
  65 + _vm.PassScanCommand.Execute(null);
  66 + }
  67 +
  68 + /// <summary>
  69 + /// 取消扫描
  70 + /// </summary>
  71 + void OnCancelScanClicked(object sender, EventArgs e)
  72 + {
  73 + _vm.CancelScanCommand.Execute(null);
  74 + }
  75 +
  76 + /// <summary>
  77 + /// 确认出库
  78 + /// </summary>
  79 + async void OnConfirmClicked(object sender, EventArgs e)
  80 + {
  81 + _vm.ConfirmCommand.Execute(null);
  82 + }
  83 + }
  84 +}
  1 +<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
  2 + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  3 + x:Class="IndustrialControl.Pages.OutboundMaterialPage"
  4 + BackgroundColor="White">
  5 +
  6 + <Grid RowDefinitions="Auto,Auto,Auto,*,Auto">
  7 +
  8 + <!-- 顶部蓝色标题栏 -->
  9 + <Grid BackgroundColor="#007BFF" HeightRequest="60" Padding="16,0">
  10 + <Label Text="仓储管理系统"
  11 + VerticalOptions="Center"
  12 + TextColor="White"
  13 + FontSize="18"
  14 + FontAttributes="Bold"/>
  15 + </Grid>
  16 +
  17 + <!-- 出库单/条码扫描 -->
  18 + <Grid Grid.Row="1" ColumnDefinitions="*,60" Padding="16,8">
  19 + <Entry x:Name="ScanEntry"
  20 + Placeholder="请扫描出库单/产品/包装条码"
  21 + FontSize="14"
  22 + VerticalOptions="Center"
  23 + BackgroundColor="White"
  24 + HeightRequest="40"
  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"/>
  32 + </Grid>
  33 +
  34 + <!-- 基础信息 -->
  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"/>
  57 + </Grid>
  58 + </Frame>
  59 +
  60 + <!-- Tab + 列表 -->
  61 + <Grid Grid.Row="3" RowDefinitions="Auto,Auto,*">
  62 +
  63 + <!-- Tab -->
  64 + <Grid ColumnDefinitions="*,*" BackgroundColor="White">
  65 + <Button Text="出库单明细"
  66 + BackgroundColor="{Binding PendingTabColor}"
  67 + TextColor="{Binding PendingTextColor}"
  68 + Clicked="OnPendingTabClicked"/>
  69 + <Button Text="扫描明细"
  70 + Grid.Column="1"
  71 + BackgroundColor="{Binding ScannedTabColor}"
  72 + TextColor="{Binding ScannedTextColor}"
  73 + Clicked="OnScannedTabClicked"/>
  74 + </Grid>
  75 +
  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"/>
  89 + </Grid>
  90 +
  91 + <!-- 出库单明细列表 -->
  92 + <CollectionView Grid.Row="2"
  93 + ItemsSource="{Binding PendingList}"
  94 + IsVisible="{Binding IsPendingVisible}">
  95 + <CollectionView.ItemTemplate>
  96 + <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"/>
  105 + </Grid>
  106 + </DataTemplate>
  107 + </CollectionView.ItemTemplate>
  108 + </CollectionView>
  109 +
  110 + <!-- 扫描明细表头 -->
  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"/>
  120 + </Grid>
  121 +
  122 + <!-- 扫描明细列表 -->
  123 + <CollectionView Grid.Row="2"
  124 + ItemsSource="{Binding ScannedList}"
  125 + IsVisible="{Binding IsScannedVisible}">
  126 + <CollectionView.ItemTemplate>
  127 + <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"/>
  133 + </Grid>
  134 + </DataTemplate>
  135 + </CollectionView.ItemTemplate>
  136 + </CollectionView>
  137 + </Grid>
  138 +
  139 + <!-- 底部按钮 -->
  140 + <Grid Grid.Row="4" ColumnDefinitions="*,*,*" Padding="16,8" ColumnSpacing="10">
  141 + <Button Text="扫描通过" BackgroundColor="#4CAF50" TextColor="White" Clicked="OnPassScanClicked"/>
  142 + <Button Grid.Column="1" Text="取消扫描" BackgroundColor="#F44336" TextColor="White" Clicked="OnCancelScanClicked"/>
  143 + <Button Grid.Column="2" Text="确认出库" BackgroundColor="#2196F3" TextColor="White" Clicked="OnConfirmClicked"/>
  144 + </Grid>
  145 +
  146 + </Grid>
  147 +</ContentPage>
  1 +using IndustrialControl.Services;
  2 +using IndustrialControl.ViewModels;
  3 +
  4 +namespace IndustrialControl.Pages
  5 +{
  6 + public partial class OutboundMaterialPage : ContentPage
  7 + {
  8 + private readonly ScanService _scanSvc;
  9 + private readonly OutboundMaterialViewModel _vm;
  10 +
  11 + public OutboundMaterialPage(OutboundMaterialViewModel vm, ScanService scanSvc)
  12 + {
  13 + InitializeComponent();
  14 + BindingContext = vm;
  15 + _scanSvc = scanSvc;
  16 + _vm = vm;
  17 + }
  18 +
  19 + protected override void OnAppearing()
  20 + {
  21 + base.OnAppearing();
  22 + _scanSvc.Attach(ScanEntry);
  23 + ScanEntry.Focus();
  24 + }
  25 +
  26 + // Tab切换
  27 + void OnPendingTabClicked(object sender, EventArgs e)
  28 + {
  29 + _vm.IsPendingVisible = true;
  30 + _vm.IsScannedVisible = false;
  31 + }
  32 +
  33 + void OnScannedTabClicked(object sender, EventArgs e)
  34 + {
  35 + _vm.IsPendingVisible = false;
  36 + _vm.IsScannedVisible = true;
  37 + }
  38 +
  39 + // 清空扫描记录
  40 + void OnClearClicked(object sender, EventArgs e)
  41 + {
  42 + _vm.ClearScan();
  43 + ScanEntry.Text = string.Empty;
  44 + ScanEntry.Focus();
  45 + }
  46 +
  47 + // 摄像头扫码按钮
  48 + async void OnScanClicked(object sender, EventArgs e)
  49 + {
  50 + await DisplayAlert("提示", "此按钮预留摄像头扫码;硬件扫描直接扣扳机。", "确定");
  51 + }
  52 +
  53 + // 扫描通过
  54 + void OnPassScanClicked(object sender, EventArgs e)
  55 + {
  56 + _vm.PassSelectedScan();
  57 + }
  58 +
  59 + // 取消扫描
  60 + void OnCancelScanClicked(object sender, EventArgs e)
  61 + {
  62 + _vm.CancelSelectedScan();
  63 + }
  64 +
  65 + // 确认出库
  66 + async void OnConfirmClicked(object sender, EventArgs e)
  67 + {
  68 + var ok = await _vm.ConfirmOutboundAsync();
  69 + if (ok)
  70 + {
  71 + await DisplayAlert("提示", "出库成功", "确定");
  72 + _vm.ClearAll();
  73 + }
  74 + else
  75 + {
  76 + await DisplayAlert("提示", "出库失败,请检查数据", "确定");
  77 + }
  78 + }
  79 + }
  80 +}
  1 +using System;
  2 +using Microsoft.Maui;
  3 +using Microsoft.Maui.Hosting;
  4 +
  5 +namespace IndustrialControl
  6 +{
  7 + internal class Program : MauiApplication
  8 + {
  9 + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
  10 +
  11 + static void Main(string[] args)
  12 + {
  13 + var app = new Program();
  14 + app.Run(args);
  15 + }
  16 + }
  17 +}
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<manifest package="maui-application-id-placeholder" version="0.0.0" api-version="8" xmlns="http://tizen.org/ns/packages">
  3 + <profile name="common" />
  4 + <ui-application appid="maui-application-id-placeholder" exec="IndustrialControl.dll" multiple="false" nodisplay="false" taskmanage="true" type="dotnet" launch_mode="single">
  5 + <label>maui-application-title-placeholder</label>
  6 + <icon>maui-appicon-placeholder</icon>
  7 + <metadata key="http://tizen.org/metadata/prefer_dotnet_aot" value="true" />
  8 + </ui-application>
  9 + <shortcut-list />
  10 + <privileges>
  11 + <privilege>http://tizen.org/privilege/internet</privilege>
  12 + </privileges>
  13 + <dependencies />
  14 + <provides-appdefined-privileges />
  15 +</manifest>
  1 +<maui:MauiWinUIApplication
  2 + x:Class="IndustrialControl.WinUI.App"
  3 + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  4 + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  5 + xmlns:maui="using:Microsoft.Maui"
  6 + xmlns:local="using:IndustrialControl.WinUI">
  7 +
  8 +</maui:MauiWinUIApplication>
  1 +using Microsoft.UI.Xaml;
  2 +
  3 +// To learn more about WinUI, the WinUI project structure,
  4 +// and more about our project templates, see: http://aka.ms/winui-project-info.
  5 +
  6 +namespace IndustrialControl.WinUI
  7 +{
  8 + /// <summary>
  9 + /// Provides application-specific behavior to supplement the default Application class.
  10 + /// </summary>
  11 + public partial class App : MauiWinUIApplication
  12 + {
  13 + /// <summary>
  14 + /// Initializes the singleton application object. This is the first line of authored code
  15 + /// executed, and as such is the logical equivalent of main() or WinMain().
  16 + /// </summary>
  17 + public App()
  18 + {
  19 + this.InitializeComponent();
  20 + }
  21 +
  22 + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
  23 + }
  24 +
  25 +}
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<Package
  3 + xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
  4 + xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
  5 + xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
  6 + xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
  7 + IgnorableNamespaces="uap rescap">
  8 +
  9 + <Identity Name="maui-package-name-placeholder" Publisher="CN=User Name" Version="0.0.0.0" />
  10 +
  11 + <mp:PhoneIdentity PhoneProductId="51796DCC-E64D-4816-833F-CF9CE3D4BEEA" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
  12 +
  13 + <Properties>
  14 + <DisplayName>$placeholder$</DisplayName>
  15 + <PublisherDisplayName>User Name</PublisherDisplayName>
  16 + <Logo>$placeholder$.png</Logo>
  17 + </Properties>
  18 +
  19 + <Dependencies>
  20 + <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
  21 + <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
  22 + </Dependencies>
  23 +
  24 + <Resources>
  25 + <Resource Language="x-generate" />
  26 + </Resources>
  27 +
  28 + <Applications>
  29 + <Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="$targetentrypoint$">
  30 + <uap:VisualElements
  31 + DisplayName="$placeholder$"
  32 + Description="$placeholder$"
  33 + Square150x150Logo="$placeholder$.png"
  34 + Square44x44Logo="$placeholder$.png"
  35 + BackgroundColor="transparent">
  36 + <uap:DefaultTile Square71x71Logo="$placeholder$.png" Wide310x150Logo="$placeholder$.png" Square310x310Logo="$placeholder$.png" />
  37 + <uap:SplashScreen Image="$placeholder$.png" />
  38 + </uap:VisualElements>
  39 + </Application>
  40 + </Applications>
  41 +
  42 + <Capabilities>
  43 + <rescap:Capability Name="runFullTrust" />
  44 + </Capabilities>
  45 +
  46 +</Package>
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  3 + <assemblyIdentity version="1.0.0.0" name="IndustrialControl.WinUI.app"/>
  4 +
  5 + <application xmlns="urn:schemas-microsoft-com:asm.v3">
  6 + <windowsSettings>
  7 + <!-- The combination of below two tags have the following effect:
  8 + 1) Per-Monitor for >= Windows 10 Anniversary Update
  9 + 2) System < Windows 10 Anniversary Update
  10 + -->
  11 + <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
  12 + <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
  13 + </windowsSettings>
  14 + </application>
  15 +</assembly>
  1 +{
  2 + "profiles": {
  3 + "Windows Machine": {
  4 + "commandName": "MsixPackage",
  5 + "nativeDebugging": false
  6 + }
  7 + }
  8 +}
  1 +<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  2 +<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg">
  3 + <rect x="0" y="0" width="456" height="456" fill="#512BD4" />
  4 +</svg>
  1 +<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  2 +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
  3 +<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
  4 + <path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
  5 + <path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
  6 + <path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
  7 + <path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
  8 +</svg>
不能预览此文件类型
不能预览此文件类型
  1 +Any raw assets you want to be deployed with your application can be placed in
  2 +this directory (and child directories). Deployment of the asset to your application
  3 +is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
  4 +
  5 + <MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
  6 +
  7 +These files will be deployed with your package and will be accessible using Essentials:
  8 +
  9 + async Task LoadMauiAsset()
  10 + {
  11 + using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
  12 + using var reader = new StreamReader(stream);
  13 +
  14 + var contents = reader.ReadToEnd();
  15 + }
  1 +{
  2 + "schemaVersion": 3,
  3 + "server": {
  4 + "ipAddress": "192.168.1.100",
  5 + "port": 8080
  6 + },
  7 + "apiEndpoints": {
  8 + "login": "/sso/login"
  9 + },
  10 + "logging": {
  11 + "level": "Information"
  12 + }
  13 +}
  1 +<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  2 +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
  3 +<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
  4 + <path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
  5 + <path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
  6 + <path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
  7 + <path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
  8 +</svg>
  1 +<?xml version="1.0" encoding="UTF-8" ?>
  2 +<?xaml-comp compile="true" ?>
  3 +<ResourceDictionary
  4 + xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
  5 + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
  6 +
  7 + <!-- Note: For Android please see also Platforms\Android\Resources\values\colors.xml -->
  8 +
  9 + <Color x:Key="Primary">#512BD4</Color>
  10 + <Color x:Key="PrimaryDark">#ac99ea</Color>
  11 + <Color x:Key="PrimaryDarkText">#242424</Color>
  12 + <Color x:Key="Secondary">#DFD8F7</Color>
  13 + <Color x:Key="SecondaryDarkText">#9880e5</Color>
  14 + <Color x:Key="Tertiary">#2B0B98</Color>
  15 +
  16 + <Color x:Key="White">White</Color>
  17 + <Color x:Key="Black">Black</Color>
  18 + <Color x:Key="Magenta">#D600AA</Color>
  19 + <Color x:Key="MidnightBlue">#190649</Color>
  20 + <Color x:Key="OffBlack">#1f1f1f</Color>
  21 +
  22 + <Color x:Key="Gray100">#E1E1E1</Color>
  23 + <Color x:Key="Gray200">#C8C8C8</Color>
  24 + <Color x:Key="Gray300">#ACACAC</Color>
  25 + <Color x:Key="Gray400">#919191</Color>
  26 + <Color x:Key="Gray500">#6E6E6E</Color>
  27 + <Color x:Key="Gray600">#404040</Color>
  28 + <Color x:Key="Gray900">#212121</Color>
  29 + <Color x:Key="Gray950">#141414</Color>
  30 +
  31 + <SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource Primary}"/>
  32 + <SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource Secondary}"/>
  33 + <SolidColorBrush x:Key="TertiaryBrush" Color="{StaticResource Tertiary}"/>
  34 + <SolidColorBrush x:Key="WhiteBrush" Color="{StaticResource White}"/>
  35 + <SolidColorBrush x:Key="BlackBrush" Color="{StaticResource Black}"/>
  36 +
  37 + <SolidColorBrush x:Key="Gray100Brush" Color="{StaticResource Gray100}"/>
  38 + <SolidColorBrush x:Key="Gray200Brush" Color="{StaticResource Gray200}"/>
  39 + <SolidColorBrush x:Key="Gray300Brush" Color="{StaticResource Gray300}"/>
  40 + <SolidColorBrush x:Key="Gray400Brush" Color="{StaticResource Gray400}"/>
  41 + <SolidColorBrush x:Key="Gray500Brush" Color="{StaticResource Gray500}"/>
  42 + <SolidColorBrush x:Key="Gray600Brush" Color="{StaticResource Gray600}"/>
  43 + <SolidColorBrush x:Key="Gray900Brush" Color="{StaticResource Gray900}"/>
  44 + <SolidColorBrush x:Key="Gray950Brush" Color="{StaticResource Gray950}"/>
  45 +</ResourceDictionary>
  1 +<?xml version="1.0" encoding="UTF-8" ?>
  2 +<?xaml-comp compile="true" ?>
  3 +<ResourceDictionary
  4 + xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
  5 + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
  6 +
  7 + <Style TargetType="ActivityIndicator">
  8 + <Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
  9 + </Style>
  10 +
  11 + <Style TargetType="IndicatorView">
  12 + <Setter Property="IndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}"/>
  13 + <Setter Property="SelectedIndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray100}}"/>
  14 + </Style>
  15 +
  16 + <Style TargetType="Border">
  17 + <Setter Property="Stroke" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
  18 + <Setter Property="StrokeShape" Value="Rectangle"/>
  19 + <Setter Property="StrokeThickness" Value="1"/>
  20 + </Style>
  21 +
  22 + <Style TargetType="BoxView">
  23 + <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
  24 + </Style>
  25 +
  26 + <Style TargetType="Button">
  27 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource PrimaryDarkText}}" />
  28 + <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" />
  29 + <Setter Property="FontFamily" Value="OpenSansRegular"/>
  30 + <Setter Property="FontSize" Value="14"/>
  31 + <Setter Property="BorderWidth" Value="0"/>
  32 + <Setter Property="CornerRadius" Value="8"/>
  33 + <Setter Property="Padding" Value="14,10"/>
  34 + <Setter Property="MinimumHeightRequest" Value="44"/>
  35 + <Setter Property="MinimumWidthRequest" Value="44"/>
  36 + <Setter Property="VisualStateManager.VisualStateGroups">
  37 + <VisualStateGroupList>
  38 + <VisualStateGroup x:Name="CommonStates">
  39 + <VisualState x:Name="Normal" />
  40 + <VisualState x:Name="Disabled">
  41 + <VisualState.Setters>
  42 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
  43 + <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
  44 + </VisualState.Setters>
  45 + </VisualState>
  46 + <VisualState x:Name="PointerOver" />
  47 + </VisualStateGroup>
  48 + </VisualStateGroupList>
  49 + </Setter>
  50 + </Style>
  51 +
  52 + <Style TargetType="CheckBox">
  53 + <Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
  54 + <Setter Property="MinimumHeightRequest" Value="44"/>
  55 + <Setter Property="MinimumWidthRequest" Value="44"/>
  56 + <Setter Property="VisualStateManager.VisualStateGroups">
  57 + <VisualStateGroupList>
  58 + <VisualStateGroup x:Name="CommonStates">
  59 + <VisualState x:Name="Normal" />
  60 + <VisualState x:Name="Disabled">
  61 + <VisualState.Setters>
  62 + <Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
  63 + </VisualState.Setters>
  64 + </VisualState>
  65 + </VisualStateGroup>
  66 + </VisualStateGroupList>
  67 + </Setter>
  68 + </Style>
  69 +
  70 + <Style TargetType="DatePicker">
  71 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
  72 + <Setter Property="BackgroundColor" Value="Transparent" />
  73 + <Setter Property="FontFamily" Value="OpenSansRegular"/>
  74 + <Setter Property="FontSize" Value="14"/>
  75 + <Setter Property="MinimumHeightRequest" Value="44"/>
  76 + <Setter Property="MinimumWidthRequest" Value="44"/>
  77 + <Setter Property="VisualStateManager.VisualStateGroups">
  78 + <VisualStateGroupList>
  79 + <VisualStateGroup x:Name="CommonStates">
  80 + <VisualState x:Name="Normal" />
  81 + <VisualState x:Name="Disabled">
  82 + <VisualState.Setters>
  83 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
  84 + </VisualState.Setters>
  85 + </VisualState>
  86 + </VisualStateGroup>
  87 + </VisualStateGroupList>
  88 + </Setter>
  89 + </Style>
  90 +
  91 + <Style TargetType="Editor">
  92 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
  93 + <Setter Property="BackgroundColor" Value="Transparent" />
  94 + <Setter Property="FontFamily" Value="OpenSansRegular"/>
  95 + <Setter Property="FontSize" Value="14" />
  96 + <Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
  97 + <Setter Property="MinimumHeightRequest" Value="44"/>
  98 + <Setter Property="MinimumWidthRequest" Value="44"/>
  99 + <Setter Property="VisualStateManager.VisualStateGroups">
  100 + <VisualStateGroupList>
  101 + <VisualStateGroup x:Name="CommonStates">
  102 + <VisualState x:Name="Normal" />
  103 + <VisualState x:Name="Disabled">
  104 + <VisualState.Setters>
  105 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
  106 + </VisualState.Setters>
  107 + </VisualState>
  108 + </VisualStateGroup>
  109 + </VisualStateGroupList>
  110 + </Setter>
  111 + </Style>
  112 +
  113 + <Style TargetType="Entry">
  114 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
  115 + <Setter Property="BackgroundColor" Value="Transparent" />
  116 + <Setter Property="FontFamily" Value="OpenSansRegular"/>
  117 + <Setter Property="FontSize" Value="14" />
  118 + <Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
  119 + <Setter Property="MinimumHeightRequest" Value="44"/>
  120 + <Setter Property="MinimumWidthRequest" Value="44"/>
  121 + <Setter Property="VisualStateManager.VisualStateGroups">
  122 + <VisualStateGroupList>
  123 + <VisualStateGroup x:Name="CommonStates">
  124 + <VisualState x:Name="Normal" />
  125 + <VisualState x:Name="Disabled">
  126 + <VisualState.Setters>
  127 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
  128 + </VisualState.Setters>
  129 + </VisualState>
  130 + </VisualStateGroup>
  131 + </VisualStateGroupList>
  132 + </Setter>
  133 + </Style>
  134 +
  135 + <Style TargetType="Frame">
  136 + <Setter Property="HasShadow" Value="False" />
  137 + <Setter Property="BorderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
  138 + <Setter Property="CornerRadius" Value="8" />
  139 + <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
  140 + </Style>
  141 +
  142 + <Style TargetType="ImageButton">
  143 + <Setter Property="Opacity" Value="1" />
  144 + <Setter Property="BorderColor" Value="Transparent"/>
  145 + <Setter Property="BorderWidth" Value="0"/>
  146 + <Setter Property="CornerRadius" Value="0"/>
  147 + <Setter Property="MinimumHeightRequest" Value="44"/>
  148 + <Setter Property="MinimumWidthRequest" Value="44"/>
  149 + <Setter Property="VisualStateManager.VisualStateGroups">
  150 + <VisualStateGroupList>
  151 + <VisualStateGroup x:Name="CommonStates">
  152 + <VisualState x:Name="Normal" />
  153 + <VisualState x:Name="Disabled">
  154 + <VisualState.Setters>
  155 + <Setter Property="Opacity" Value="0.5" />
  156 + </VisualState.Setters>
  157 + </VisualState>
  158 + <VisualState x:Name="PointerOver" />
  159 + </VisualStateGroup>
  160 + </VisualStateGroupList>
  161 + </Setter>
  162 + </Style>
  163 +
  164 + <Style TargetType="Label">
  165 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
  166 + <Setter Property="BackgroundColor" Value="Transparent" />
  167 + <Setter Property="FontFamily" Value="OpenSansRegular" />
  168 + <Setter Property="FontSize" Value="14" />
  169 + <Setter Property="VisualStateManager.VisualStateGroups">
  170 + <VisualStateGroupList>
  171 + <VisualStateGroup x:Name="CommonStates">
  172 + <VisualState x:Name="Normal" />
  173 + <VisualState x:Name="Disabled">
  174 + <VisualState.Setters>
  175 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
  176 + </VisualState.Setters>
  177 + </VisualState>
  178 + </VisualStateGroup>
  179 + </VisualStateGroupList>
  180 + </Setter>
  181 + </Style>
  182 +
  183 + <Style TargetType="Span">
  184 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
  185 + </Style>
  186 +
  187 + <Style TargetType="Label" x:Key="Headline">
  188 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource MidnightBlue}, Dark={StaticResource White}}" />
  189 + <Setter Property="FontSize" Value="32" />
  190 + <Setter Property="HorizontalOptions" Value="Center" />
  191 + <Setter Property="HorizontalTextAlignment" Value="Center" />
  192 + </Style>
  193 +
  194 + <Style TargetType="Label" x:Key="SubHeadline">
  195 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource MidnightBlue}, Dark={StaticResource White}}" />
  196 + <Setter Property="FontSize" Value="24" />
  197 + <Setter Property="HorizontalOptions" Value="Center" />
  198 + <Setter Property="HorizontalTextAlignment" Value="Center" />
  199 + </Style>
  200 +
  201 + <Style TargetType="ListView">
  202 + <Setter Property="SeparatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
  203 + <Setter Property="RefreshControlColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
  204 + </Style>
  205 +
  206 + <Style TargetType="Picker">
  207 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
  208 + <Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
  209 + <Setter Property="BackgroundColor" Value="Transparent" />
  210 + <Setter Property="FontFamily" Value="OpenSansRegular"/>
  211 + <Setter Property="FontSize" Value="14"/>
  212 + <Setter Property="MinimumHeightRequest" Value="44"/>
  213 + <Setter Property="MinimumWidthRequest" Value="44"/>
  214 + <Setter Property="VisualStateManager.VisualStateGroups">
  215 + <VisualStateGroupList>
  216 + <VisualStateGroup x:Name="CommonStates">
  217 + <VisualState x:Name="Normal" />
  218 + <VisualState x:Name="Disabled">
  219 + <VisualState.Setters>
  220 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
  221 + <Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
  222 + </VisualState.Setters>
  223 + </VisualState>
  224 + </VisualStateGroup>
  225 + </VisualStateGroupList>
  226 + </Setter>
  227 + </Style>
  228 +
  229 + <Style TargetType="ProgressBar">
  230 + <Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
  231 + <Setter Property="VisualStateManager.VisualStateGroups">
  232 + <VisualStateGroupList>
  233 + <VisualStateGroup x:Name="CommonStates">
  234 + <VisualState x:Name="Normal" />
  235 + <VisualState x:Name="Disabled">
  236 + <VisualState.Setters>
  237 + <Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
  238 + </VisualState.Setters>
  239 + </VisualState>
  240 + </VisualStateGroup>
  241 + </VisualStateGroupList>
  242 + </Setter>
  243 + </Style>
  244 +
  245 + <Style TargetType="RadioButton">
  246 + <Setter Property="BackgroundColor" Value="Transparent"/>
  247 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
  248 + <Setter Property="FontFamily" Value="OpenSansRegular"/>
  249 + <Setter Property="FontSize" Value="14"/>
  250 + <Setter Property="MinimumHeightRequest" Value="44"/>
  251 + <Setter Property="MinimumWidthRequest" Value="44"/>
  252 + <Setter Property="VisualStateManager.VisualStateGroups">
  253 + <VisualStateGroupList>
  254 + <VisualStateGroup x:Name="CommonStates">
  255 + <VisualState x:Name="Normal" />
  256 + <VisualState x:Name="Disabled">
  257 + <VisualState.Setters>
  258 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
  259 + </VisualState.Setters>
  260 + </VisualState>
  261 + </VisualStateGroup>
  262 + </VisualStateGroupList>
  263 + </Setter>
  264 + </Style>
  265 +
  266 + <Style TargetType="RefreshView">
  267 + <Setter Property="RefreshColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
  268 + </Style>
  269 +
  270 + <Style TargetType="SearchBar">
  271 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
  272 + <Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
  273 + <Setter Property="CancelButtonColor" Value="{StaticResource Gray500}" />
  274 + <Setter Property="BackgroundColor" Value="Transparent" />
  275 + <Setter Property="FontFamily" Value="OpenSansRegular" />
  276 + <Setter Property="FontSize" Value="14" />
  277 + <Setter Property="MinimumHeightRequest" Value="44"/>
  278 + <Setter Property="MinimumWidthRequest" Value="44"/>
  279 + <Setter Property="VisualStateManager.VisualStateGroups">
  280 + <VisualStateGroupList>
  281 + <VisualStateGroup x:Name="CommonStates">
  282 + <VisualState x:Name="Normal" />
  283 + <VisualState x:Name="Disabled">
  284 + <VisualState.Setters>
  285 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
  286 + <Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
  287 + </VisualState.Setters>
  288 + </VisualState>
  289 + </VisualStateGroup>
  290 + </VisualStateGroupList>
  291 + </Setter>
  292 + </Style>
  293 +
  294 + <Style TargetType="SearchHandler">
  295 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
  296 + <Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
  297 + <Setter Property="BackgroundColor" Value="Transparent" />
  298 + <Setter Property="FontFamily" Value="OpenSansRegular" />
  299 + <Setter Property="FontSize" Value="14" />
  300 + <Setter Property="VisualStateManager.VisualStateGroups">
  301 + <VisualStateGroupList>
  302 + <VisualStateGroup x:Name="CommonStates">
  303 + <VisualState x:Name="Normal" />
  304 + <VisualState x:Name="Disabled">
  305 + <VisualState.Setters>
  306 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
  307 + <Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
  308 + </VisualState.Setters>
  309 + </VisualState>
  310 + </VisualStateGroup>
  311 + </VisualStateGroupList>
  312 + </Setter>
  313 + </Style>
  314 +
  315 + <Style TargetType="Shadow">
  316 + <Setter Property="Radius" Value="15" />
  317 + <Setter Property="Opacity" Value="0.5" />
  318 + <Setter Property="Brush" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource White}}" />
  319 + <Setter Property="Offset" Value="10,10" />
  320 + </Style>
  321 +
  322 + <Style TargetType="Slider">
  323 + <Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
  324 + <Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
  325 + <Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
  326 + <Setter Property="VisualStateManager.VisualStateGroups">
  327 + <VisualStateGroupList>
  328 + <VisualStateGroup x:Name="CommonStates">
  329 + <VisualState x:Name="Normal" />
  330 + <VisualState x:Name="Disabled">
  331 + <VisualState.Setters>
  332 + <Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
  333 + <Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
  334 + <Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
  335 + </VisualState.Setters>
  336 + </VisualState>
  337 + </VisualStateGroup>
  338 + </VisualStateGroupList>
  339 + </Setter>
  340 + </Style>
  341 +
  342 + <Style TargetType="SwipeItem">
  343 + <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
  344 + </Style>
  345 +
  346 + <Style TargetType="Switch">
  347 + <Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
  348 + <Setter Property="ThumbColor" Value="{StaticResource White}" />
  349 + <Setter Property="VisualStateManager.VisualStateGroups">
  350 + <VisualStateGroupList>
  351 + <VisualStateGroup x:Name="CommonStates">
  352 + <VisualState x:Name="Normal" />
  353 + <VisualState x:Name="Disabled">
  354 + <VisualState.Setters>
  355 + <Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
  356 + <Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
  357 + </VisualState.Setters>
  358 + </VisualState>
  359 + <VisualState x:Name="On">
  360 + <VisualState.Setters>
  361 + <Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Secondary}, Dark={StaticResource Gray200}}" />
  362 + <Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
  363 + </VisualState.Setters>
  364 + </VisualState>
  365 + <VisualState x:Name="Off">
  366 + <VisualState.Setters>
  367 + <Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray400}, Dark={StaticResource Gray500}}" />
  368 + </VisualState.Setters>
  369 + </VisualState>
  370 + </VisualStateGroup>
  371 + </VisualStateGroupList>
  372 + </Setter>
  373 + </Style>
  374 +
  375 + <Style TargetType="TimePicker">
  376 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
  377 + <Setter Property="BackgroundColor" Value="Transparent"/>
  378 + <Setter Property="FontFamily" Value="OpenSansRegular"/>
  379 + <Setter Property="FontSize" Value="14"/>
  380 + <Setter Property="MinimumHeightRequest" Value="44"/>
  381 + <Setter Property="MinimumWidthRequest" Value="44"/>
  382 + <Setter Property="VisualStateManager.VisualStateGroups">
  383 + <VisualStateGroupList>
  384 + <VisualStateGroup x:Name="CommonStates">
  385 + <VisualState x:Name="Normal" />
  386 + <VisualState x:Name="Disabled">
  387 + <VisualState.Setters>
  388 + <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
  389 + </VisualState.Setters>
  390 + </VisualState>
  391 + </VisualStateGroup>
  392 + </VisualStateGroupList>
  393 + </Setter>
  394 + </Style>
  395 +
  396 + <Style TargetType="Page" ApplyToDerivedTypes="True">
  397 + <Setter Property="Padding" Value="0"/>
  398 + <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
  399 + </Style>
  400 +
  401 + <Style TargetType="Shell" ApplyToDerivedTypes="True">
  402 + <Setter Property="Shell.BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
  403 + <Setter Property="Shell.ForegroundColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource SecondaryDarkText}}" />
  404 + <Setter Property="Shell.TitleColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource SecondaryDarkText}}" />
  405 + <Setter Property="Shell.DisabledColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
  406 + <Setter Property="Shell.UnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray200}}" />
  407 + <Setter Property="Shell.NavBarHasShadow" Value="False" />
  408 + <Setter Property="Shell.TabBarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
  409 + <Setter Property="Shell.TabBarForegroundColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
  410 + <Setter Property="Shell.TabBarTitleColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
  411 + <Setter Property="Shell.TabBarUnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
  412 + </Style>
  413 +
  414 + <Style TargetType="NavigationPage">
  415 + <Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
  416 + <Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
  417 + <Setter Property="IconColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
  418 + </Style>
  419 +
  420 + <Style TargetType="TabbedPage">
  421 + <Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Gray950}}" />
  422 + <Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
  423 + <Setter Property="UnselectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
  424 + <Setter Property="SelectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
  425 + </Style>
  426 +
  427 +</ResourceDictionary>
  1 +using System.Text.Json;
  2 +using IndustrialControl.Models;
  3 +
  4 +namespace IndustrialControl.Services;
  5 +
  6 +public class ConfigLoader
  7 +{
  8 + public const string FileName = "appconfig.json";
  9 + private readonly string _configPath = Path.Combine(FileSystem.AppDataDirectory, FileName);
  10 +
  11 + private readonly JsonSerializerOptions _jsonOptions = new()
  12 + {
  13 + PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
  14 + WriteIndented = true
  15 + };
  16 +
  17 + public event Action? ConfigChanged;
  18 +
  19 + public AppConfig Current { get; private set; } = new AppConfig();
  20 +
  21 + public string BaseUrl => $"http://{Current.Server.IpAddress}:{Current.Server.Port}";
  22 +
  23 + public ConfigLoader()
  24 + {
  25 + Task.Run(EnsureConfigIsLatestAsync).Wait();
  26 + }
  27 +
  28 + public async Task EnsureConfigIsLatestAsync()
  29 + {
  30 + using var pkgStream = await FileSystem.OpenAppPackageFileAsync(FileName);
  31 + using var pkgReader = new StreamReader(pkgStream);
  32 + var pkgJson = await pkgReader.ReadToEndAsync();
  33 + var pkgCfg = JsonSerializer.Deserialize<AppConfig>(pkgJson, _jsonOptions) ?? new AppConfig();
  34 +
  35 + if (!File.Exists(_configPath))
  36 + {
  37 + Directory.CreateDirectory(Path.GetDirectoryName(_configPath)!);
  38 + await File.WriteAllTextAsync(_configPath, pkgJson);
  39 + Current = pkgCfg;
  40 + return;
  41 + }
  42 +
  43 + var currJson = await File.ReadAllTextAsync(_configPath);
  44 + var currCfg = JsonSerializer.Deserialize<AppConfig>(currJson, _jsonOptions) ?? new AppConfig();
  45 +
  46 + if (currCfg.SchemaVersion < pkgCfg.SchemaVersion)
  47 + {
  48 + pkgCfg.Server = currCfg.Server;
  49 + Current = pkgCfg;
  50 + await SaveAsync(Current, fireChanged: false);
  51 + }
  52 + else
  53 + {
  54 + currCfg.ApiEndpoints ??= pkgCfg.ApiEndpoints;
  55 + currCfg.Logging ??= pkgCfg.Logging;
  56 + currCfg.Server ??= currCfg.Server ?? new ServerSettings();
  57 + Current = currCfg;
  58 + }
  59 + }
  60 +
  61 + public async Task SaveAsync(AppConfig cfg, bool fireChanged = true)
  62 + {
  63 + var json = JsonSerializer.Serialize(cfg, _jsonOptions);
  64 + await File.WriteAllTextAsync(_configPath, json);
  65 + Current = cfg;
  66 + if (fireChanged) ConfigChanged?.Invoke();
  67 + }
  68 +
  69 + public async Task<AppConfig> ReloadAsync()
  70 + {
  71 + var json = await File.ReadAllTextAsync(_configPath);
  72 + Current = JsonSerializer.Deserialize<AppConfig>(json, _jsonOptions) ?? new AppConfig();
  73 + ConfigChanged?.Invoke();
  74 + return Current;
  75 + }
  76 +
  77 + public string GetConfigPath() => _configPath;
  78 +}
  1 +namespace IndustrialControl.Services;
  2 +
  3 +public class LogService : IDisposable
  4 +{
  5 + private readonly string _logsDir = Path.Combine(FileSystem.AppDataDirectory, "logs");
  6 + private readonly TimeSpan _interval = TimeSpan.FromMilliseconds(800);
  7 + private CancellationTokenSource? _cts;
  8 +
  9 + public event Action<string>? LogTextUpdated;
  10 +
  11 + public string TodayLogPath => Path.Combine(_logsDir, $"gr-{DateTime.Now:yyyy-MM-dd}.txt");
  12 +
  13 + public void Start()
  14 + {
  15 + Stop();
  16 + _cts = new CancellationTokenSource();
  17 + _ = Task.Run(() => LoopAsync(_cts.Token));
  18 + }
  19 +
  20 + public void Stop()
  21 + {
  22 + _cts?.Cancel();
  23 + _cts?.Dispose();
  24 + _cts = null;
  25 + }
  26 +
  27 + private async Task LoopAsync(CancellationToken token)
  28 + {
  29 + Directory.CreateDirectory(_logsDir);
  30 + string last = string.Empty;
  31 + while (!token.IsCancellationRequested)
  32 + {
  33 + try
  34 + {
  35 + if (File.Exists(TodayLogPath))
  36 + {
  37 + var text = await File.ReadAllTextAsync(TodayLogPath, token);
  38 + if (!string.Equals(text, last, StringComparison.Ordinal))
  39 + {
  40 + last = text;
  41 + LogTextUpdated?.Invoke(text);
  42 + }
  43 + }
  44 + }
  45 + catch { }
  46 + await Task.Delay(_interval, token);
  47 + }
  48 + }
  49 +
  50 + public void Dispose() => Stop();
  51 +}
  1 +namespace IndustrialControl.Services;
  2 +
  3 +public interface IWarehouseDataService
  4 +{
  5 + Task<InboundOrder> GetInboundOrderAsync(string orderNo);
  6 + Task<SimpleOk> ConfirmInboundAsync(string orderNo, IEnumerable<ScanItem> items);
  7 +
  8 + Task<SimpleOk> ConfirmInboundProductionAsync(string orderNo, IEnumerable<ScanItem> items);
  9 + Task<SimpleOk> ConfirmOutboundMaterialAsync(string orderNo, IEnumerable<ScanItem> items);
  10 + Task<SimpleOk> ConfirmOutboundFinishedAsync(string orderNo, IEnumerable<ScanItem> items);
  11 +}
  12 +
  13 +public record InboundOrder(string OrderNo, string Supplier, string LinkedNo, int ExpectedQty);
  14 +public record ScanItem(int Index, string Barcode, string? Bin, int Qty);
  15 +public record SimpleOk(bool Succeeded, string? Message = null);
  16 +
  17 +public class MockWarehouseDataService : IWarehouseDataService
  18 +{
  19 + private readonly Random _rand = new();
  20 +
  21 + public Task<InboundOrder> GetInboundOrderAsync(string orderNo)
  22 + => Task.FromResult(new InboundOrder(orderNo, "供应商/客户:XXXX", "关联单:CGD2736273", _rand.Next(10, 80)));
  23 +
  24 + public Task<SimpleOk> ConfirmInboundAsync(string orderNo, IEnumerable<ScanItem> items)
  25 + => Task.FromResult(new SimpleOk(true, $"入库成功:{items.Count()} 条"));
  26 +
  27 + public Task<SimpleOk> ConfirmInboundProductionAsync(string orderNo, IEnumerable<ScanItem> items)
  28 + => Task.FromResult(new SimpleOk(true, $"生产入库成功:{items.Count()} 条"));
  29 +
  30 + public Task<SimpleOk> ConfirmOutboundMaterialAsync(string orderNo, IEnumerable<ScanItem> items)
  31 + => Task.FromResult(new SimpleOk(true, $"物料出库成功:{items.Count()} 条"));
  32 +
  33 + public Task<SimpleOk> ConfirmOutboundFinishedAsync(string orderNo, IEnumerable<ScanItem> items)
  34 + => Task.FromResult(new SimpleOk(true, $"成品出库成功:{items.Count()} 条"));
  35 +}
  1 +using CommunityToolkit.Mvvm.Messaging;
  2 +
  3 +namespace IndustrialControl.Services;
  4 +
  5 +public record ScanMessage(string Data);
  6 +
  7 +public class ScanService
  8 +{
  9 + public void Publish(string data)
  10 + {
  11 + if (string.IsNullOrWhiteSpace(data)) return;
  12 + MainThread.BeginInvokeOnMainThread(() =>
  13 + WeakReferenceMessenger.Default.Send(new ScanMessage(data.Trim())));
  14 + }
  15 +
  16 + public void Attach(Entry entry)
  17 + {
  18 + entry.Completed += (s, e) => Publish(((Entry)s!).Text ?? string.Empty);
  19 + }
  20 +}
  1 +using CommunityToolkit.Mvvm.ComponentModel;
  2 +using CommunityToolkit.Mvvm.Input;
  3 +using IndustrialControl.Models;
  4 +using IndustrialControl.Services;
  5 +
  6 +namespace IndustrialControl.ViewModels;
  7 +
  8 +public partial class AdminViewModel : ObservableObject
  9 +{
  10 + private readonly ConfigLoader _cfg;
  11 +
  12 + [ObservableProperty] private int schemaVersion;
  13 + [ObservableProperty] private string ipAddress = "";
  14 + [ObservableProperty] private int port;
  15 + [ObservableProperty] private string baseUrl = "";
  16 +
  17 + public AdminViewModel(ConfigLoader cfg)
  18 + {
  19 + _cfg = cfg;
  20 + LoadFromConfig();
  21 + _cfg.ConfigChanged += () => LoadFromConfig();
  22 + }
  23 +
  24 + private void LoadFromConfig()
  25 + {
  26 + var c = _cfg.Current;
  27 + SchemaVersion = c.SchemaVersion;
  28 + IpAddress = c.Server.IpAddress;
  29 + Port = c.Server.Port;
  30 + BaseUrl = _cfg.BaseUrl;
  31 + }
  32 +
  33 + [RelayCommand]
  34 + public async Task SaveAsync()
  35 + {
  36 + var c = _cfg.Current;
  37 + c.Server.IpAddress = IpAddress.Trim();
  38 + c.Server.Port = Port;
  39 + await _cfg.SaveAsync(c);
  40 + BaseUrl = _cfg.BaseUrl;
  41 + await Shell.Current.DisplayAlert("已保存", "配置已保存,可立即生效。", "确定");
  42 + }
  43 +
  44 + [RelayCommand]
  45 + public async Task ResetToPackageAsync()
  46 + {
  47 + await _cfg.EnsureConfigIsLatestAsync();
  48 + await _cfg.ReloadAsync();
  49 + await Shell.Current.DisplayAlert("已重载", "已从包内默认配置重载/合并。", "确定");
  50 + }
  51 +}
  1 +namespace IndustrialControl.ViewModels; public partial class HomeViewModel { }
  1 +using CommunityToolkit.Mvvm.ComponentModel;
  2 +using CommunityToolkit.Mvvm.Input;
  3 +using System.Collections.ObjectModel;
  4 +using IndustrialControl.Services;
  5 +using System.Threading.Tasks;
  6 +
  7 +namespace IndustrialControl.ViewModels
  8 +{
  9 + public partial class InboundMaterialViewModel : ObservableObject
  10 + {
  11 + private readonly IWarehouseDataService _warehouseSvc;
  12 + public ObservableCollection<string> AvailableBins { get; set; } = new ObservableCollection<string>
  13 +{
  14 + "CK1_A201",
  15 + "CK1_A202",
  16 + "CK1_A203",
  17 + "CK1_A204"
  18 +};
  19 +
  20 +
  21 + public InboundMaterialViewModel(IWarehouseDataService warehouseSvc)
  22 + {
  23 + _warehouseSvc = warehouseSvc;
  24 +
  25 + // 初始化命令
  26 + ShowPendingCommand = new RelayCommand(() => SwitchTab(true));
  27 + ShowScannedCommand = new RelayCommand(() => SwitchTab(false));
  28 + ConfirmCommand = new AsyncRelayCommand(ConfirmInboundAsync);
  29 +
  30 + // 默认显示待入库明细
  31 + IsPendingVisible = true;
  32 + IsScannedVisible = false;
  33 +
  34 + // 测试数据(上线可去掉)
  35 + PendingList = new ObservableCollection<PendingItem>
  36 + {
  37 + new PendingItem { Name="物料1", Spec="XXX", PendingQty=100, Bin="CK1_A201", ScannedQty=80 },
  38 + new PendingItem { Name="物料2", Spec="YYY", PendingQty=50, Bin="CK1_A202", ScannedQty=0 }
  39 + };
  40 +
  41 + ScannedList = new ObservableCollection<OutScannedItem>
  42 + {
  43 + new OutScannedItem { IsSelected=false, Barcode="FSC2025060300001", Name="物料1", Spec="XXX", Bin="CK1_A201", Qty=1 },
  44 + new OutScannedItem { IsSelected=false, Barcode="FSC2025060300002", Name="物料1", Spec="XXX", Bin="请选择", Qty=1 }
  45 + };
  46 + }
  47 +
  48 + // 基础信息
  49 + [ObservableProperty] private string orderNo;
  50 + [ObservableProperty] private string linkedDeliveryNo;
  51 + [ObservableProperty] private string linkedPurchaseNo;
  52 + [ObservableProperty] private string supplier;
  53 +
  54 + // Tab 显示控制
  55 + [ObservableProperty] private bool isPendingVisible;
  56 + [ObservableProperty] private bool isScannedVisible;
  57 +
  58 + // Tab 按钮颜色
  59 + [ObservableProperty] private string pendingTabColor = "#E6F2FF";
  60 + [ObservableProperty] private string scannedTabColor = "White";
  61 + [ObservableProperty] private string pendingTextColor = "#007BFF";
  62 + [ObservableProperty] private string scannedTextColor = "#333333";
  63 +
  64 + // 列表数据
  65 + public ObservableCollection<PendingItem> PendingList { get; set; }
  66 + public ObservableCollection<OutScannedItem> ScannedList { get; set; }
  67 +
  68 + // 命令
  69 + public IRelayCommand ShowPendingCommand { get; }
  70 + public IRelayCommand ShowScannedCommand { get; }
  71 + public IAsyncRelayCommand ConfirmCommand { get; }
  72 +
  73 + /// <summary>
  74 + /// 切换 Tab
  75 + /// </summary>
  76 + private void SwitchTab(bool showPending)
  77 + {
  78 + IsPendingVisible = showPending;
  79 + IsScannedVisible = !showPending;
  80 +
  81 + if (showPending)
  82 + {
  83 + PendingTabColor = "#E6F2FF";
  84 + ScannedTabColor = "White";
  85 + PendingTextColor = "#007BFF";
  86 + ScannedTextColor = "#333333";
  87 + }
  88 + else
  89 + {
  90 + PendingTabColor = "White";
  91 + ScannedTabColor = "#E6F2FF";
  92 + PendingTextColor = "#333333";
  93 + ScannedTextColor = "#007BFF";
  94 + }
  95 + }
  96 +
  97 + /// <summary>
  98 + /// 清空扫描记录
  99 + /// </summary>
  100 + public void ClearScan()
  101 + {
  102 + ScannedList.Clear();
  103 + }
  104 +
  105 + /// <summary>
  106 + /// 清空所有数据
  107 + /// </summary>
  108 + public void ClearAll()
  109 + {
  110 + OrderNo = string.Empty;
  111 + LinkedDeliveryNo = string.Empty;
  112 + LinkedPurchaseNo = string.Empty;
  113 + Supplier = string.Empty;
  114 + PendingList.Clear();
  115 + ScannedList.Clear();
  116 + }
  117 +
  118 + /// <summary>
  119 + /// 扫描通过逻辑
  120 + /// </summary>
  121 + public void PassSelectedScan()
  122 + {
  123 + foreach (var item in ScannedList)
  124 + {
  125 + if (item.IsSelected)
  126 + {
  127 + // 这里可以做业务处理,比如更新状态
  128 + }
  129 + }
  130 + }
  131 +
  132 + /// <summary>
  133 + /// 取消扫描逻辑
  134 + /// </summary>
  135 + public void CancelSelectedScan()
  136 + {
  137 + for (int i = ScannedList.Count - 1; i >= 0; i--)
  138 + {
  139 + if (ScannedList[i].IsSelected)
  140 + {
  141 + ScannedList.RemoveAt(i);
  142 + }
  143 + }
  144 + }
  145 +
  146 + /// <summary>
  147 + /// 确认入库逻辑
  148 + /// </summary>
  149 + public async Task<bool> ConfirmInboundAsync()
  150 + {
  151 + if (string.IsNullOrWhiteSpace(OrderNo))
  152 + return false;
  153 +
  154 + var result = await _warehouseSvc.ConfirmInboundAsync(OrderNo,
  155 + ScannedList.Select(x => new ScanItem(0, x.Barcode, x.Bin, x.Qty)));
  156 +
  157 + return result.Succeeded;
  158 + }
  159 + }
  160 +
  161 + // 待入库明细模型
  162 + public class PendingItem
  163 + {
  164 + public string Name { get; set; }
  165 + public string Spec { get; set; }
  166 + public int PendingQty { get; set; }
  167 + public string Bin { get; set; }
  168 + public int ScannedQty { get; set; }
  169 + }
  170 +
  171 + // 扫描明细模型
  172 + public class OutScannedItem
  173 + {
  174 + public bool IsSelected { get; set; }
  175 + public string Barcode { get; set; }
  176 + public string Name { get; set; }
  177 + public string Spec { get; set; }
  178 + public string Bin { get; set; }
  179 + public int Qty { get; set; }
  180 + }
  181 +
  182 +}
  1 +using CommunityToolkit.Mvvm.ComponentModel;
  2 +using CommunityToolkit.Mvvm.Input;
  3 +using System.Collections.ObjectModel;
  4 +using IndustrialControl.Services;
  5 +using System.Threading.Tasks;
  6 +
  7 +namespace IndustrialControl.ViewModels
  8 +{
  9 + public partial class InboundProductionViewModel : ObservableObject
  10 + {
  11 + private readonly IWarehouseDataService _warehouseSvc;
  12 +
  13 + public InboundProductionViewModel(IWarehouseDataService warehouseSvc)
  14 + {
  15 + _warehouseSvc = warehouseSvc;
  16 +
  17 + ConfirmCommand = new AsyncRelayCommand(ConfirmInboundAsync);
  18 +
  19 + // 测试数据
  20 + Lines = new ObservableCollection<ProductionScanItem>
  21 + {
  22 + new ProductionScanItem { IsSelected = false, Barcode="FSC2025060300001", Bin="CK1_A201", Qty=1 },
  23 + new ProductionScanItem { IsSelected = false, Barcode="FSC2025060300002", Bin="CK1_A201", Qty=1 }
  24 + };
  25 +
  26 + AvailableBins = new ObservableCollection<string>
  27 + {
  28 + "CK1_A201", "CK1_A202", "CK1_A203"
  29 + };
  30 + }
  31 +
  32 + // 基础信息
  33 + [ObservableProperty] private string orderNo;
  34 + [ObservableProperty] private string workOrderNo;
  35 + [ObservableProperty] private string productName;
  36 + [ObservableProperty] private int pendingQty;
  37 +
  38 + public ObservableCollection<ProductionScanItem> Lines { get; set; }
  39 + public ObservableCollection<string> AvailableBins { get; set; }
  40 +
  41 + public IAsyncRelayCommand ConfirmCommand { get; }
  42 +
  43 + public void ClearScan()
  44 + {
  45 + Lines.Clear();
  46 + }
  47 +
  48 + public void ClearAll()
  49 + {
  50 + OrderNo = string.Empty;
  51 + WorkOrderNo = string.Empty;
  52 + ProductName = string.Empty;
  53 + PendingQty = 0;
  54 + Lines.Clear();
  55 + }
  56 +
  57 + public void PassSelectedScan()
  58 + {
  59 + foreach (var item in Lines)
  60 + {
  61 + if (item.IsSelected)
  62 + {
  63 + // 扫描通过处理
  64 + }
  65 + }
  66 + }
  67 +
  68 + public void CancelSelectedScan()
  69 + {
  70 + for (int i = Lines.Count - 1; i >= 0; i--)
  71 + {
  72 + if (Lines[i].IsSelected)
  73 + Lines.RemoveAt(i);
  74 + }
  75 + }
  76 +
  77 + public async Task<bool> ConfirmInboundAsync()
  78 + {
  79 + if (string.IsNullOrWhiteSpace(OrderNo))
  80 + return false;
  81 +
  82 + var result = await _warehouseSvc.ConfirmInboundProductionAsync(OrderNo,
  83 + Lines.Select(x => new ScanItem(0, x.Barcode, x.Bin, x.Qty)));
  84 +
  85 + return result.Succeeded;
  86 + }
  87 + }
  88 +
  89 + public class ProductionScanItem
  90 + {
  91 + public bool IsSelected { get; set; }
  92 + public string Barcode { get; set; }
  93 + public string Bin { get; set; }
  94 + public int Qty { get; set; }
  95 + }
  96 +}
  1 +using CommunityToolkit.Mvvm.ComponentModel;
  2 +using CommunityToolkit.Mvvm.Input;
  3 +
  4 +namespace IndustrialControl.ViewModels;
  5 +
  6 +public partial class LoginViewModel : ObservableObject
  7 +{
  8 + [ObservableProperty] private string userName = string.Empty;
  9 + [ObservableProperty] private string password = string.Empty;
  10 + [ObservableProperty] private bool isBusy;
  11 +
  12 + [RelayCommand]
  13 + public async Task LoginAsync()
  14 + {
  15 + if (IsBusy) return; IsBusy = true;
  16 + try
  17 + {
  18 + await Task.Delay(200); // mock
  19 + await Shell.Current.GoToAsync("//Home");
  20 + }
  21 + catch (Exception ex)
  22 + {
  23 + await Application.Current.MainPage.DisplayAlert("登录失败", ex.Message, "确定");
  24 + }
  25 + finally { IsBusy = false; }
  26 + }
  27 +}
  1 +using CommunityToolkit.Mvvm.ComponentModel;
  2 +using IndustrialControl.Services;
  3 +
  4 +namespace IndustrialControl.ViewModels;
  5 +
  6 +public partial class LogsViewModel : ObservableObject
  7 +{
  8 + private readonly LogService _logSvc;
  9 +
  10 + [ObservableProperty] private string logText = "日志初始化中...";
  11 +
  12 + public string TodayPath => _logSvc.TodayLogPath;
  13 +
  14 + public LogsViewModel(LogService logSvc)
  15 + {
  16 + _logSvc = logSvc;
  17 + _logSvc.LogTextUpdated += text => LogText = text;
  18 + }
  19 +
  20 + public void OnAppearing() => _logSvc.Start();
  21 + public void OnDisappearing() => _logSvc.Stop();
  22 +}
  1 +using CommunityToolkit.Mvvm.ComponentModel;
  2 +using CommunityToolkit.Mvvm.Input;
  3 +using System.Collections.ObjectModel;
  4 +using System.Linq;
  5 +using System.Threading.Tasks;
  6 +
  7 +namespace IndustrialControl.ViewModels
  8 +{
  9 + public partial class OutboundFinishedViewModel : ObservableObject
  10 + {
  11 + #region 基础信息
  12 + [ObservableProperty] private string orderNo;
  13 + [ObservableProperty] private string deliveryNo;
  14 + [ObservableProperty] private string deliveryTime;
  15 + [ObservableProperty] private string remark;
  16 + #endregion
  17 +
  18 + #region Tab 状态
  19 + [ObservableProperty] private bool isPendingVisible = true;
  20 + [ObservableProperty] private bool isScannedVisible = false;
  21 +
  22 + [ObservableProperty] private string pendingTabColor = "#2196F3";
  23 + [ObservableProperty] private string pendingTextColor = "White";
  24 + [ObservableProperty] private string scannedTabColor = "White";
  25 + [ObservableProperty] private string scannedTextColor = "Black";
  26 + #endregion
  27 +
  28 + #region 数据集合
  29 + public ObservableCollection<OutPendingItem> PendingList { get; } = new();
  30 + public ObservableCollection<OutboundScannedItem> ScannedList { get; } = new();
  31 + #endregion
  32 +
  33 + public OutboundFinishedViewModel()
  34 + {
  35 + // 测试数据
  36 + OrderNo = "CK20250001";
  37 + DeliveryNo = "FH20250088";
  38 + DeliveryTime = "2025-08-15";
  39 + Remark = "优先出库";
  40 +
  41 + PendingList.Add(new OutPendingItem { Name = "成品A", Code = "CP001", Spec = "规格1", Bin = "A01", BatchNo = "B20250815", OutQty = 10 });
  42 + PendingList.Add(new OutPendingItem { Name = "成品B", Code = "CP002", Spec = "规格2", Bin = "A02", BatchNo = "B20250815", OutQty = 5 });
  43 +
  44 + ScannedList.Add(new OutboundScannedItem { Barcode = "BC0001", Name = "成品A", Qty = 2 });
  45 + ScannedList.Add(new OutboundScannedItem { Barcode = "BC0002", Name = "成品B", Qty = 1 });
  46 + }
  47 +
  48 + #region Tab 切换命令
  49 + [RelayCommand]
  50 + private void ShowPending()
  51 + {
  52 + IsPendingVisible = true;
  53 + IsScannedVisible = false;
  54 + PendingTabColor = "#2196F3";
  55 + PendingTextColor = "White";
  56 + ScannedTabColor = "White";
  57 + ScannedTextColor = "Black";
  58 + }
  59 +
  60 + [RelayCommand]
  61 + private void ShowScanned()
  62 + {
  63 + IsPendingVisible = false;
  64 + IsScannedVisible = true;
  65 + PendingTabColor = "White";
  66 + PendingTextColor = "Black";
  67 + ScannedTabColor = "#2196F3";
  68 + ScannedTextColor = "White";
  69 + }
  70 + #endregion
  71 +
  72 + #region 底部按钮命令
  73 + [RelayCommand]
  74 + private void PassScan()
  75 + {
  76 + var selected = ScannedList.Where(s => s.IsSelected).ToList();
  77 + if (!selected.Any()) return;
  78 +
  79 + foreach (var item in selected)
  80 + {
  81 + // 模拟通过逻辑
  82 + item.IsSelected = false;
  83 + }
  84 + }
  85 +
  86 + [RelayCommand]
  87 + private void CancelScan()
  88 + {
  89 + var selected = ScannedList.Where(s => s.IsSelected).ToList();
  90 + if (!selected.Any()) return;
  91 +
  92 + foreach (var item in selected)
  93 + {
  94 + ScannedList.Remove(item);
  95 + }
  96 + }
  97 +
  98 + [RelayCommand]
  99 + private async Task Confirm()
  100 + {
  101 + // 模拟提交
  102 + await Task.Delay(300);
  103 + // 这里你可以加出库成功提示逻辑
  104 + ClearAll();
  105 + }
  106 + #endregion
  107 +
  108 + #region 其他方法
  109 + public void ClearAll()
  110 + {
  111 + PendingList.Clear();
  112 + ScannedList.Clear();
  113 + OrderNo = string.Empty;
  114 + DeliveryNo = string.Empty;
  115 + DeliveryTime = string.Empty;
  116 + Remark = string.Empty;
  117 + }
  118 + public void ClearScan()
  119 + {
  120 + ScannedList.Clear();
  121 + }
  122 +
  123 + #endregion
  124 + }
  125 +
  126 + public class OutPendingItem
  127 + {
  128 + public string Name { get; set; }
  129 + public string Code { get; set; }
  130 + public string Spec { get; set; }
  131 + public string Bin { get; set; }
  132 + public string BatchNo { get; set; }
  133 + public int OutQty { get; set; }
  134 + }
  135 +
  136 + public class OutboundScannedItem
  137 + {
  138 + public bool IsSelected { get; set; }
  139 + public string Barcode { get; set; }
  140 + public string Name { get; set; }
  141 + public int Qty { get; set; }
  142 + }
  143 +}
  1 +using CommunityToolkit.Mvvm.ComponentModel;
  2 +using CommunityToolkit.Mvvm.Input;
  3 +using System.Collections.ObjectModel;
  4 +using System.Threading.Tasks;
  5 +using System.Linq;
  6 +using System.Windows.Input;
  7 +using Microsoft.Maui.Graphics;
  8 +
  9 +namespace IndustrialControl.ViewModels
  10 +{
  11 + public partial class OutboundMaterialViewModel : ObservableObject
  12 + {
  13 + // ===== 基础信息 =====
  14 + [ObservableProperty] private string orderNo;
  15 + [ObservableProperty] private string deliveryNo;
  16 + [ObservableProperty] private string customerName;
  17 + [ObservableProperty] private string deliveryTime;
  18 + [ObservableProperty] private string linkedOrderNo;
  19 + [ObservableProperty] private string remark;
  20 +
  21 + // ===== Tab 切换状态 =====
  22 + [ObservableProperty] private bool isPendingVisible = true;
  23 + [ObservableProperty] private bool isScannedVisible = false;
  24 +
  25 + [ObservableProperty] private Color pendingTabColor = Colors.LightBlue;
  26 + [ObservableProperty] private Color pendingTextColor = Colors.White;
  27 + [ObservableProperty] private Color scannedTabColor = Colors.White;
  28 + [ObservableProperty] private Color scannedTextColor = Colors.Black;
  29 +
  30 + // ===== 列表数据 =====
  31 + public ObservableCollection<OutboundPendingItem> PendingList { get; set; } = new();
  32 + public ObservableCollection<ScannedItem> ScannedList { get; set; } = new();
  33 +
  34 + // ===== 扫码输入 =====
  35 + [ObservableProperty] private string scanCode;
  36 +
  37 + public OutboundMaterialViewModel()
  38 + {
  39 + LoadSampleData();
  40 + }
  41 +
  42 + private void LoadSampleData()
  43 + {
  44 + // 基础信息示例
  45 + OrderNo = "XXXXX";
  46 + DeliveryNo = "SCGD20250601002";
  47 + CustomerName = "CGD2736273";
  48 + DeliveryTime = "2025-08-15";
  49 + LinkedOrderNo = "CGD2736273";
  50 + Remark = "优先出库";
  51 +
  52 + // 出库单明细
  53 + PendingList.Add(new OutboundPendingItem { Name = "产品1", Code = "XXX", Spec = "A101", Bin = "A101", BatchNo = "B20250815", OutQty = 10, ScannedQty = 10 });
  54 + PendingList.Add(new OutboundPendingItem { Name = "产品2", Code = "YYY", Spec = "A102", Bin = "A102", BatchNo = "B20250815", OutQty = 5, ScannedQty = 2 });
  55 + PendingList.Add(new OutboundPendingItem { Name = "产品3", Code = "ZZZ", Spec = "A103", Bin = "A103", BatchNo = "B20250815", OutQty = 8, ScannedQty = 0 });
  56 +
  57 + // 扫描明细
  58 + ScannedList.Add(new ScannedItem { Barcode = "FSC2025060300001", Name = "XXXX", Qty = 1, IsSelected = true });
  59 + ScannedList.Add(new ScannedItem { Barcode = "FSC2025060300002", Name = "XXXX", Qty = 1, IsSelected = true });
  60 + ScannedList.Add(new ScannedItem { Barcode = "FSC2025060300003", Name = "XXXX", Qty = 1, IsSelected = false });
  61 + ScannedList.Add(new ScannedItem { Barcode = "FSC2025060300004", Name = "XXXX", Qty = 1, IsSelected = false });
  62 + }
  63 +
  64 + // ===== 操作方法 =====
  65 + public void ClearScan()
  66 + {
  67 + ScanCode = string.Empty;
  68 + }
  69 +
  70 + public void ClearAll()
  71 + {
  72 + PendingList.Clear();
  73 + ScannedList.Clear();
  74 + ScanCode = string.Empty;
  75 + }
  76 +
  77 + public void PassSelectedScan()
  78 + {
  79 + foreach (var item in ScannedList.Where(s => s.IsSelected))
  80 + {
  81 + // 逻辑示例:标记为已通过(这里可加业务处理)
  82 + }
  83 + }
  84 +
  85 + public void CancelSelectedScan()
  86 + {
  87 + var toRemove = ScannedList.Where(s => s.IsSelected).ToList();
  88 + foreach (var item in toRemove)
  89 + ScannedList.Remove(item);
  90 + }
  91 +
  92 + public async Task<bool> ConfirmOutboundAsync()
  93 + {
  94 + // 模拟出库确认逻辑
  95 + await Task.Delay(500); // 模拟网络延迟
  96 + return true;
  97 + }
  98 + }
  99 +
  100 + // 出库单明细数据模型
  101 + public class OutboundPendingItem
  102 + {
  103 + public string Name { get; set; }
  104 + public string Code { get; set; }
  105 + public string Spec { get; set; }
  106 + public string Bin { get; set; }
  107 + public string BatchNo { get; set; }
  108 + public int OutQty { get; set; }
  109 + public int ScannedQty { get; set; }
  110 + }
  111 +
  112 + // 扫描明细数据模型
  113 + public class ScannedItem
  114 + {
  115 + public bool IsSelected { get; set; }
  116 + public string Barcode { get; set; }
  117 + public string Name { get; set; }
  118 + public int Qty { get; set; }
  119 + }
  120 +}