Browse Source

实现表单功能

refactor/namespace-and-layering
root 4 weeks ago
parent
commit
9793d879ba
  1. 7
      AuroraDesk.Infrastructure/Services/NavigationService.cs
  2. 55
      AuroraDesk.Presentation/Converters/StringConverters.cs
  3. 1
      AuroraDesk.Presentation/Extensions/ServiceCollectionExtensions.cs
  4. 1
      AuroraDesk.Presentation/Services/PageViewModelFactory.cs
  5. 458
      AuroraDesk.Presentation/ViewModels/Pages/FormPageViewModel.cs
  6. 389
      AuroraDesk.Presentation/Views/Pages/FormPageView.axaml
  7. 22
      AuroraDesk.Presentation/Views/Pages/FormPageView.axaml.cs
  8. 151
      modify.md

7
AuroraDesk.Infrastructure/Services/NavigationService.cs

@ -253,6 +253,13 @@ public class NavigationService : INavigationService
ViewModel = _pageViewModelFactory.CreatePageViewModel("livecharts2", screen)
}
}
},
new NavigationItem
{
Id = "form",
Title = "表单示例",
IconType = IconType.DocumentText,
ViewModel = _pageViewModelFactory.CreatePageViewModel("form", screen)
}
};

55
AuroraDesk.Presentation/Converters/StringConverters.cs

@ -45,6 +45,16 @@ public static class StringConverters
/// 整数到可见性的转换器(0 则不可见)
/// </summary>
public static readonly IValueConverter IntToVisibilityConverter = new IntToVisibilityConverter();
/// <summary>
/// 字符串非空检查转换器(非空则返回 true)
/// </summary>
public static readonly IValueConverter IsNotNullOrEmptyConverter = new IsNotNullOrEmptyConverter();
/// <summary>
/// 布尔值到可见性转换器
/// </summary>
public static readonly IValueConverter BoolToVisibilityConverter = new BoolToVisibilityConverter();
}
/// <summary>
@ -212,3 +222,48 @@ public class IntToVisibilityConverter : IValueConverter
throw new NotImplementedException();
}
}
/// <summary>
/// 字符串非空检查转换器(非空则返回 true)
/// </summary>
public class IsNotNullOrEmptyConverter : IValueConverter
{
public static IsNotNullOrEmptyConverter Instance { get; } = new();
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return !string.IsNullOrEmpty(value?.ToString());
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
/// <summary>
/// 布尔值到可见性转换器
/// </summary>
public class BoolToVisibilityConverter : IValueConverter
{
public static BoolToVisibilityConverter Instance { get; } = new();
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is bool boolValue)
{
// 如果 parameter 是 "Invert",则反转布尔值
if (parameter?.ToString() == "Invert")
{
return !boolValue;
}
return boolValue;
}
return false;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

1
AuroraDesk.Presentation/Extensions/ServiceCollectionExtensions.cs

@ -55,6 +55,7 @@ public static class ServiceCollectionExtensions
services.AddTransient<WirelessAdapterPageViewModel>();
services.AddTransient<ScottPlotPageViewModel>();
services.AddTransient<LiveCharts2PageViewModel>();
services.AddTransient<FormPageViewModel>();
// 注意:MainWindowViewModel 的注册移到主项目的 App.axaml.cs 中
// 因为它依赖 AppViewModel,而 AppViewModel 在 AddReactiveUI() 中注册

1
AuroraDesk.Presentation/Services/PageViewModelFactory.cs

@ -66,6 +66,7 @@ public class PageViewModelFactory : IPageViewModelFactory
"wireless-adapter" => CreateWirelessAdapterPageViewModel(screen),
"scottplot" => CreatePageViewModel<ScottPlotPageViewModel>(screen),
"livecharts2" => CreatePageViewModel<LiveCharts2PageViewModel>(screen),
"form" => CreatePageViewModel<FormPageViewModel>(screen),
_ => throw new ArgumentException($"Unknown page: {pageId}", nameof(pageId))
};
}

458
AuroraDesk.Presentation/ViewModels/Pages/FormPageViewModel.cs

@ -0,0 +1,458 @@
using AuroraDesk.Presentation.ViewModels.Base;
using ReactiveUI;
using System.Collections.ObjectModel;
using System.Reactive;
namespace AuroraDesk.Presentation.ViewModels.Pages;
/// <summary>
/// 表单类型枚举
/// </summary>
public enum FormType
{
Basic, // 基础表单(垂直布局,全字段)
Simple, // 简洁表单(水平布局,标签和输入框一行)
Contact, // 联系表单(包含下拉框选择)
Feedback, // 反馈表单(包含复选框)
Register // 注册表单(两列布局)
}
/// <summary>
/// 表单页面 ViewModel
/// </summary>
public class FormPageViewModel : RoutableViewModel
{
private bool _isFormDialogOpen;
private FormType _currentFormType = FormType.Basic;
private string _formTitle = "填写表单";
private string _formDescription = "请填写以下信息";
private string _name = string.Empty;
private string _email = string.Empty;
private string _phone = string.Empty;
private string _address = string.Empty;
private string _remarks = string.Empty;
private string _submitResult = string.Empty;
private string _selectedCountry = string.Empty;
private string _selectedCity = string.Empty;
private bool _agreeTerms = false;
private bool _subscribeNewsletter = false;
private bool _receiveNotifications = false;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="hostScreen">宿主 Screen</param>
public FormPageViewModel(IScreen hostScreen) : base(hostScreen, "Form")
{
// 初始化下拉框选项
Countries = new ObservableCollection<string> { "中国", "美国", "日本", "韩国", "英国", "德国", "法国", "其他" };
Cities = new ObservableCollection<string> { "北京", "上海", "广州", "深圳", "杭州", "成都", "其他" };
ShowBasicFormCommand = ReactiveCommand.Create(() => ShowForm(FormType.Basic));
ShowSimpleFormCommand = ReactiveCommand.Create(() => ShowForm(FormType.Simple));
ShowContactFormCommand = ReactiveCommand.Create(() => ShowForm(FormType.Contact));
ShowFeedbackFormCommand = ReactiveCommand.Create(() => ShowForm(FormType.Feedback));
ShowRegisterFormCommand = ReactiveCommand.Create(() => ShowForm(FormType.Register));
SubmitFormCommand = ReactiveCommand.Create(SubmitForm);
CancelFormCommand = ReactiveCommand.Create(CancelForm);
}
/// <summary>
/// 是否显示表单对话框
/// </summary>
public bool IsFormDialogOpen
{
get => _isFormDialogOpen;
set => this.RaiseAndSetIfChanged(ref _isFormDialogOpen, value);
}
/// <summary>
/// 当前表单类型
/// </summary>
public FormType CurrentFormType
{
get => _currentFormType;
set => this.RaiseAndSetIfChanged(ref _currentFormType, value);
}
/// <summary>
/// 表单标题
/// </summary>
public string FormTitle
{
get => _formTitle;
set => this.RaiseAndSetIfChanged(ref _formTitle, value);
}
/// <summary>
/// 表单描述
/// </summary>
public string FormDescription
{
get => _formDescription;
set => this.RaiseAndSetIfChanged(ref _formDescription, value);
}
/// <summary>
/// 是否显示电话字段
/// </summary>
public bool ShowPhoneField => CurrentFormType == FormType.Basic ||
CurrentFormType == FormType.Contact ||
CurrentFormType == FormType.Register;
/// <summary>
/// 是否显示地址字段
/// </summary>
public bool ShowAddressField => CurrentFormType == FormType.Basic ||
CurrentFormType == FormType.Register;
/// <summary>
/// 是否显示备注字段
/// </summary>
public bool ShowRemarksField => CurrentFormType == FormType.Basic ||
CurrentFormType == FormType.Feedback;
/// <summary>
/// 是否使用水平布局(标签和输入框在一行)
/// </summary>
public bool UseHorizontalLayout => CurrentFormType == FormType.Simple;
/// <summary>
/// 是否使用两列布局
/// </summary>
public bool UseTwoColumnLayout => CurrentFormType == FormType.Register;
/// <summary>
/// 是否显示下拉框字段
/// </summary>
public bool ShowComboBoxFields => CurrentFormType == FormType.Contact;
/// <summary>
/// 是否显示复选框字段
/// </summary>
public bool ShowCheckBoxFields => CurrentFormType == FormType.Feedback;
/// <summary>
/// 国家列表
/// </summary>
public ObservableCollection<string> Countries { get; }
/// <summary>
/// 城市列表
/// </summary>
public ObservableCollection<string> Cities { get; }
/// <summary>
/// 选中的国家
/// </summary>
public string SelectedCountry
{
get => _selectedCountry;
set => this.RaiseAndSetIfChanged(ref _selectedCountry, value);
}
/// <summary>
/// 选中的城市
/// </summary>
public string SelectedCity
{
get => _selectedCity;
set => this.RaiseAndSetIfChanged(ref _selectedCity, value);
}
/// <summary>
/// 同意条款
/// </summary>
public bool AgreeTerms
{
get => _agreeTerms;
set => this.RaiseAndSetIfChanged(ref _agreeTerms, value);
}
/// <summary>
/// 订阅 newsletter
/// </summary>
public bool SubscribeNewsletter
{
get => _subscribeNewsletter;
set => this.RaiseAndSetIfChanged(ref _subscribeNewsletter, value);
}
/// <summary>
/// 接收通知
/// </summary>
public bool ReceiveNotifications
{
get => _receiveNotifications;
set => this.RaiseAndSetIfChanged(ref _receiveNotifications, value);
}
/// <summary>
/// 姓名
/// </summary>
public string Name
{
get => _name;
set => this.RaiseAndSetIfChanged(ref _name, value);
}
/// <summary>
/// 邮箱
/// </summary>
public string Email
{
get => _email;
set => this.RaiseAndSetIfChanged(ref _email, value);
}
/// <summary>
/// 电话
/// </summary>
public string Phone
{
get => _phone;
set => this.RaiseAndSetIfChanged(ref _phone, value);
}
/// <summary>
/// 地址
/// </summary>
public string Address
{
get => _address;
set => this.RaiseAndSetIfChanged(ref _address, value);
}
/// <summary>
/// 备注
/// </summary>
public string Remarks
{
get => _remarks;
set => this.RaiseAndSetIfChanged(ref _remarks, value);
}
/// <summary>
/// 提交结果
/// </summary>
public string SubmitResult
{
get => _submitResult;
set => this.RaiseAndSetIfChanged(ref _submitResult, value);
}
/// <summary>
/// 显示基础表单命令
/// </summary>
public ReactiveCommand<Unit, Unit> ShowBasicFormCommand { get; }
/// <summary>
/// 显示简洁表单命令
/// </summary>
public ReactiveCommand<Unit, Unit> ShowSimpleFormCommand { get; }
/// <summary>
/// 显示联系表单命令
/// </summary>
public ReactiveCommand<Unit, Unit> ShowContactFormCommand { get; }
/// <summary>
/// 显示反馈表单命令
/// </summary>
public ReactiveCommand<Unit, Unit> ShowFeedbackFormCommand { get; }
/// <summary>
/// 显示注册表单命令
/// </summary>
public ReactiveCommand<Unit, Unit> ShowRegisterFormCommand { get; }
/// <summary>
/// 提交表单命令
/// </summary>
public ReactiveCommand<Unit, Unit> SubmitFormCommand { get; }
/// <summary>
/// 取消表单命令
/// </summary>
public ReactiveCommand<Unit, Unit> CancelFormCommand { get; }
private void ShowForm(FormType formType)
{
// 重置表单数据
Name = string.Empty;
Email = string.Empty;
Phone = string.Empty;
Address = string.Empty;
Remarks = string.Empty;
SelectedCountry = string.Empty;
SelectedCity = string.Empty;
AgreeTerms = false;
SubscribeNewsletter = false;
ReceiveNotifications = false;
SubmitResult = string.Empty;
// 设置表单类型
CurrentFormType = formType;
// 根据表单类型设置标题和描述
switch (formType)
{
case FormType.Basic:
FormTitle = "基础表单";
FormDescription = "垂直布局,标签在上输入框在下";
break;
case FormType.Simple:
FormTitle = "简洁表单";
FormDescription = "水平布局,标签和输入框在同一行";
break;
case FormType.Contact:
FormTitle = "联系表单";
FormDescription = "包含下拉框选择国家/城市";
break;
case FormType.Feedback:
FormTitle = "反馈表单";
FormDescription = "包含复选框选项";
break;
case FormType.Register:
FormTitle = "注册表单";
FormDescription = "两列布局,充分利用空间";
break;
}
// 触发所有属性更新
this.RaisePropertyChanged(nameof(ShowPhoneField));
this.RaisePropertyChanged(nameof(ShowAddressField));
this.RaisePropertyChanged(nameof(ShowRemarksField));
this.RaisePropertyChanged(nameof(UseHorizontalLayout));
this.RaisePropertyChanged(nameof(UseTwoColumnLayout));
this.RaisePropertyChanged(nameof(ShowComboBoxFields));
this.RaisePropertyChanged(nameof(ShowCheckBoxFields));
// 显示对话框
IsFormDialogOpen = true;
}
private void SubmitForm()
{
// 验证必填字段
if (string.IsNullOrWhiteSpace(Name))
{
SubmitResult = "错误:姓名不能为空";
return;
}
if (string.IsNullOrWhiteSpace(Email))
{
SubmitResult = "错误:邮箱不能为空";
return;
}
// 简单的邮箱格式验证
if (!Email.Contains("@") || !Email.Contains("."))
{
SubmitResult = "错误:邮箱格式不正确";
return;
}
// 根据表单类型验证特定字段
if (CurrentFormType == FormType.Contact || CurrentFormType == FormType.Register)
{
if (string.IsNullOrWhiteSpace(Phone))
{
SubmitResult = "错误:电话不能为空";
return;
}
}
if (CurrentFormType == FormType.Register)
{
if (string.IsNullOrWhiteSpace(Address))
{
SubmitResult = "错误:地址不能为空";
return;
}
}
if (CurrentFormType == FormType.Feedback)
{
if (string.IsNullOrWhiteSpace(Remarks))
{
SubmitResult = "错误:反馈内容不能为空";
return;
}
if (!AgreeTerms)
{
SubmitResult = "错误:必须同意服务条款";
return;
}
}
if (CurrentFormType == FormType.Contact)
{
if (string.IsNullOrWhiteSpace(SelectedCountry))
{
SubmitResult = "错误:请选择国家";
return;
}
if (string.IsNullOrWhiteSpace(SelectedCity))
{
SubmitResult = "错误:请选择城市";
return;
}
}
// 关闭对话框
IsFormDialogOpen = false;
// 显示提交结果
var formTypeName = CurrentFormType switch
{
FormType.Basic => "基础表单",
FormType.Simple => "简洁表单",
FormType.Contact => "联系表单",
FormType.Feedback => "反馈表单",
FormType.Register => "注册表单",
_ => "表单"
};
var result = $"[{formTypeName}] 提交成功!\n姓名:{Name}\n邮箱:{Email}";
if (ShowPhoneField && !string.IsNullOrWhiteSpace(Phone))
{
result += $"\n电话:{Phone}";
}
if (ShowAddressField && !string.IsNullOrWhiteSpace(Address))
{
result += $"\n地址:{Address}";
}
if (ShowComboBoxFields)
{
if (!string.IsNullOrWhiteSpace(SelectedCountry))
{
result += $"\n国家:{SelectedCountry}";
}
if (!string.IsNullOrWhiteSpace(SelectedCity))
{
result += $"\n城市:{SelectedCity}";
}
}
if (ShowRemarksField && !string.IsNullOrWhiteSpace(Remarks))
{
result += $"\n备注:{Remarks}";
}
if (ShowCheckBoxFields)
{
result += $"\n同意条款:{(AgreeTerms ? "" : "")}";
result += $"\n订阅邮件:{(SubscribeNewsletter ? "" : "")}";
result += $"\n接收通知:{(ReceiveNotifications ? "" : "")}";
}
SubmitResult = result;
}
private void CancelForm()
{
IsFormDialogOpen = false;
SubmitResult = "已取消提交";
}
}

389
AuroraDesk.Presentation/Views/Pages/FormPageView.axaml

@ -0,0 +1,389 @@
<reactive:ReactiveUserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:AuroraDesk.Presentation.ViewModels.Pages"
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
xmlns:heroicons="clr-namespace:HeroIconsAvalonia.Controls;assembly=HeroIconsAvalonia"
xmlns:reactive="using:ReactiveUI.Avalonia"
xmlns:converters="clr-namespace:AuroraDesk.Presentation.Converters;assembly=AuroraDesk.Presentation"
mc:Ignorable="d"
x:Class="AuroraDesk.Presentation.Views.Pages.FormPageView"
x:DataType="vm:FormPageViewModel">
<Design.DataContext>
<vm:FormPageViewModel />
</Design.DataContext>
<dialogHost:DialogHost IsOpen="{Binding IsFormDialogOpen}">
<dialogHost:DialogHost.DialogContent>
<Border Background="{StaticResource BackgroundWhite}"
CornerRadius="16"
Padding="24"
Width="480"
MaxHeight="600">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="18">
<!-- 标题 -->
<Grid ColumnDefinitions="Auto,*" ColumnSpacing="12">
<Border Grid.Column="0"
Width="40"
Height="40"
CornerRadius="20"
Background="#3B82F6"
VerticalAlignment="Center">
<heroicons:HeroIcon Type="DocumentText"
Width="22"
Height="22"
Foreground="White"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<StackPanel Grid.Column="1" Spacing="4">
<TextBlock Text="{Binding FormTitle}"
FontSize="20"
FontWeight="SemiBold"
Foreground="{StaticResource TextPrimary}"/>
<TextBlock Text="{Binding FormDescription}"
FontSize="14"
Foreground="{StaticResource SecondaryGrayDark}"/>
</StackPanel>
</Grid>
<!-- 表单字段 -->
<StackPanel Spacing="16">
<!-- 基础表单:垂直布局 -->
<StackPanel Spacing="16" IsVisible="{Binding UseHorizontalLayout, Converter={x:Static converters:StringConverters.BoolToVisibilityConverter}, ConverterParameter='Invert'}">
<!-- 姓名 -->
<StackPanel Spacing="6">
<TextBlock Text="姓名 *"
FontSize="14"
FontWeight="Medium"
Foreground="{StaticResource TextPrimary}"/>
<TextBox Text="{Binding Name}"
Watermark="请输入姓名"
Padding="12,10"
FontSize="14"/>
</StackPanel>
<!-- 邮箱 -->
<StackPanel Spacing="6">
<TextBlock Text="邮箱 *"
FontSize="14"
FontWeight="Medium"
Foreground="{StaticResource TextPrimary}"/>
<TextBox Text="{Binding Email}"
Watermark="请输入邮箱地址"
Padding="12,10"
FontSize="14"/>
</StackPanel>
<!-- 电话 -->
<StackPanel Spacing="6" IsVisible="{Binding ShowPhoneField}">
<TextBlock Text="电话 *"
FontSize="14"
FontWeight="Medium"
Foreground="{StaticResource TextPrimary}"/>
<TextBox Text="{Binding Phone}"
Watermark="请输入电话号码"
Padding="12,10"
FontSize="14"/>
</StackPanel>
<!-- 地址 -->
<StackPanel Spacing="6" IsVisible="{Binding ShowAddressField}">
<TextBlock Text="地址 *"
FontSize="14"
FontWeight="Medium"
Foreground="{StaticResource TextPrimary}"/>
<TextBox Text="{Binding Address}"
Watermark="请输入地址"
Padding="12,10"
FontSize="14"/>
</StackPanel>
<!-- 备注 -->
<StackPanel Spacing="6" IsVisible="{Binding ShowRemarksField}">
<TextBlock Text="备注 *"
FontSize="14"
FontWeight="Medium"
Foreground="{StaticResource TextPrimary}"/>
<TextBox Text="{Binding Remarks}"
Watermark="请输入备注信息"
Padding="12,10"
AcceptsReturn="True"
TextWrapping="Wrap"
MinHeight="80"
FontSize="14"/>
</StackPanel>
<!-- 下拉框字段(联系表单) -->
<StackPanel Spacing="16" IsVisible="{Binding ShowComboBoxFields}">
<StackPanel Spacing="6">
<TextBlock Text="国家 *"
FontSize="14"
FontWeight="Medium"
Foreground="{StaticResource TextPrimary}"/>
<ComboBox ItemsSource="{Binding Countries}"
SelectedItem="{Binding SelectedCountry}"
Padding="12,10"
FontSize="14"
PlaceholderText="请选择国家"/>
</StackPanel>
<StackPanel Spacing="6">
<TextBlock Text="城市 *"
FontSize="14"
FontWeight="Medium"
Foreground="{StaticResource TextPrimary}"/>
<ComboBox ItemsSource="{Binding Cities}"
SelectedItem="{Binding SelectedCity}"
Padding="12,10"
FontSize="14"
PlaceholderText="请选择城市"/>
</StackPanel>
</StackPanel>
<!-- 复选框字段(反馈表单) -->
<StackPanel Spacing="12" IsVisible="{Binding ShowCheckBoxFields}">
<CheckBox Content="我已阅读并同意服务条款 *"
IsChecked="{Binding AgreeTerms}"
FontSize="14"/>
<CheckBox Content="订阅邮件通知"
IsChecked="{Binding SubscribeNewsletter}"
FontSize="14"/>
<CheckBox Content="接收系统通知"
IsChecked="{Binding ReceiveNotifications}"
FontSize="14"/>
</StackPanel>
</StackPanel>
<!-- 简洁表单:水平布局(标签和输入框在一行) -->
<StackPanel Spacing="16" IsVisible="{Binding UseHorizontalLayout}">
<!-- 姓名 -->
<Grid ColumnDefinitions="100,*" ColumnSpacing="12">
<TextBlock Grid.Column="0"
Text="姓名 *"
FontSize="14"
FontWeight="Medium"
Foreground="{StaticResource TextPrimary}"
VerticalAlignment="Center"/>
<TextBox Grid.Column="1"
Text="{Binding Name}"
Watermark="请输入姓名"
Padding="12,10"
FontSize="14"/>
</Grid>
<!-- 邮箱 -->
<Grid ColumnDefinitions="100,*" ColumnSpacing="12">
<TextBlock Grid.Column="0"
Text="邮箱 *"
FontSize="14"
FontWeight="Medium"
Foreground="{StaticResource TextPrimary}"
VerticalAlignment="Center"/>
<TextBox Grid.Column="1"
Text="{Binding Email}"
Watermark="请输入邮箱地址"
Padding="12,10"
FontSize="14"/>
</Grid>
</StackPanel>
<!-- 注册表单:两列布局 -->
<Grid ColumnDefinitions="*,*" ColumnSpacing="16" RowDefinitions="Auto,Auto,Auto,Auto"
IsVisible="{Binding UseTwoColumnLayout}">
<!-- 第一行:姓名 -->
<StackPanel Grid.Row="0" Grid.Column="0" Spacing="6">
<TextBlock Text="姓名 *"
FontSize="14"
FontWeight="Medium"
Foreground="{StaticResource TextPrimary}"/>
<TextBox Text="{Binding Name}"
Watermark="请输入姓名"
Padding="12,10"
FontSize="14"/>
</StackPanel>
<!-- 第一行:邮箱 -->
<StackPanel Grid.Row="0" Grid.Column="1" Spacing="6">
<TextBlock Text="邮箱 *"
FontSize="14"
FontWeight="Medium"
Foreground="{StaticResource TextPrimary}"/>
<TextBox Text="{Binding Email}"
Watermark="请输入邮箱地址"
Padding="12,10"
FontSize="14"/>
</StackPanel>
<!-- 第二行:电话 -->
<StackPanel Grid.Row="1" Grid.Column="0" Spacing="6">
<TextBlock Text="电话 *"
FontSize="14"
FontWeight="Medium"
Foreground="{StaticResource TextPrimary}"/>
<TextBox Text="{Binding Phone}"
Watermark="请输入电话号码"
Padding="12,10"
FontSize="14"/>
</StackPanel>
<!-- 第二行:地址 -->
<StackPanel Grid.Row="1" Grid.Column="1" Spacing="6">
<TextBlock Text="地址 *"
FontSize="14"
FontWeight="Medium"
Foreground="{StaticResource TextPrimary}"/>
<TextBox Text="{Binding Address}"
Watermark="请输入地址"
Padding="12,10"
FontSize="14"/>
</StackPanel>
</Grid>
</StackPanel>
<!-- 错误提示 -->
<Border Background="#FEE2E2"
BorderBrush="#FCA5A5"
BorderThickness="1"
CornerRadius="8"
Padding="12"
IsVisible="{Binding SubmitResult, Converter={x:Static converters:StringConverters.IsNotNullOrEmptyConverter}}">
<TextBlock Text="{Binding SubmitResult}"
Foreground="#DC2626"
TextWrapping="Wrap"
FontSize="13"/>
</Border>
<!-- 按钮 -->
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Right"
Spacing="10">
<Button Content="取消"
MinWidth="96"
Command="{Binding CancelFormCommand}"/>
<Button Content="提交"
MinWidth="96"
Command="{Binding SubmitFormCommand}"
Background="#3B82F6"
Foreground="White"
BorderThickness="0"
CornerRadius="8"/>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Border>
</dialogHost:DialogHost.DialogContent>
<Grid RowDefinitions="Auto,Auto,*" ColumnDefinitions="*" Margin="20" Background="Transparent">
<!-- 标题区域 -->
<StackPanel Spacing="8">
<TextBlock Text="表单示例"
FontSize="20"
FontWeight="SemiBold"
Foreground="{StaticResource TextPrimary}"/>
<TextBlock Text="点击下方不同按钮打开不同类型的表单对话框,每种表单包含不同的字段组合。"
FontSize="14"
Foreground="{StaticResource SecondaryGrayDark}"/>
</StackPanel>
<!-- 按钮区域 -->
<WrapPanel Grid.Row="1"
Margin="0,18,0,0"
Orientation="Horizontal">
<Button Content="基础表单"
Padding="18,10"
Margin="0,0,12,12"
Command="{Binding ShowBasicFormCommand}"
Background="#3B82F6"
BorderBrush="Transparent"
Foreground="White"
CornerRadius="10">
<Button.Styles>
<Style Selector="Button:pointerover">
<Setter Property="Background" Value="#2563EB"/>
</Style>
</Button.Styles>
</Button>
<Button Content="简洁表单"
Padding="18,10"
Margin="0,0,12,12"
Command="{Binding ShowSimpleFormCommand}"
Background="#10B981"
BorderBrush="Transparent"
Foreground="White"
CornerRadius="10">
<Button.Styles>
<Style Selector="Button:pointerover">
<Setter Property="Background" Value="#059669"/>
</Style>
</Button.Styles>
</Button>
<Button Content="联系表单"
Padding="18,10"
Margin="0,0,12,12"
Command="{Binding ShowContactFormCommand}"
Background="#8B5CF6"
BorderBrush="Transparent"
Foreground="White"
CornerRadius="10">
<Button.Styles>
<Style Selector="Button:pointerover">
<Setter Property="Background" Value="#7C3AED"/>
</Style>
</Button.Styles>
</Button>
<Button Content="反馈表单"
Padding="18,10"
Margin="0,0,12,12"
Command="{Binding ShowFeedbackFormCommand}"
Background="#F59E0B"
BorderBrush="Transparent"
Foreground="White"
CornerRadius="10">
<Button.Styles>
<Style Selector="Button:pointerover">
<Setter Property="Background" Value="#D97706"/>
</Style>
</Button.Styles>
</Button>
<Button Content="注册表单"
Padding="18,10"
Margin="0,0,12,12"
Command="{Binding ShowRegisterFormCommand}"
Background="#EF4444"
BorderBrush="Transparent"
Foreground="White"
CornerRadius="10">
<Button.Styles>
<Style Selector="Button:pointerover">
<Setter Property="Background" Value="#DC2626"/>
</Style>
</Button.Styles>
</Button>
</WrapPanel>
<!-- 结果显示区域 -->
<Border Grid.Row="2"
Margin="0,24,0,0"
Padding="16"
Background="{StaticResource BackgroundLight}"
BorderBrush="{StaticResource BorderLight}"
BorderThickness="1"
CornerRadius="8">
<StackPanel Spacing="6">
<TextBlock Text="提交结果"
FontSize="16"
FontWeight="SemiBold"
Foreground="{StaticResource TextPrimary}"/>
<TextBlock Text="{Binding SubmitResult, FallbackValue=尚未提交任何数据}"
TextWrapping="Wrap"
Foreground="{StaticResource TextPrimary}"
FontSize="14"/>
</StackPanel>
</Border>
</Grid>
</dialogHost:DialogHost>
</reactive:ReactiveUserControl>

22
AuroraDesk.Presentation/Views/Pages/FormPageView.axaml.cs

@ -0,0 +1,22 @@
using Avalonia.Markup.Xaml;
using AuroraDesk.Presentation.ViewModels.Pages;
using ReactiveUI.Avalonia;
namespace AuroraDesk.Presentation.Views.Pages;
/// <summary>
/// 表单页面视图
/// </summary>
public partial class FormPageView : ReactiveUserControl<FormPageViewModel>
{
public FormPageView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

151
modify.md

@ -2213,8 +2213,149 @@
- 直接更新 `Values` 集合(`ObservableCollection<ObservablePoint>`),实现平滑的叠加效果
- 使用 `CreateLineSeries()` 方法创建系列,`UpdateLineChartData()` 只更新数据
- 避免整个图表重新渲染,实现真正的叠加动画效果
- **阈值颜色区分**:
- 使用 `LineSeries<ObservablePoint>` 显示折线,保持点的位置关系
- 使用 `ScatterSeries<ObservablePoint>` 显示点,支持不同颜色
- 超过阈值的点显示为绿色,正常点显示为蓝色/红色
- 阈值改变时自动更新图表颜色
- **阈值颜色区分**:
- 使用 `LineSeries<ObservablePoint>` 显示折线,保持点的位置关系
- 使用 `ScatterSeries<ObservablePoint>` 显示点,支持不同颜色
- 超过阈值的点显示为绿色,正常点显示为蓝色/红色
- 阈值改变时自动更新图表颜色
### 新增表单示例页面
- **日期**: 2025年1月
- **功能描述**: 添加了表单示例页面,包含按钮点击弹出表单对话框功能
- **实现内容**:
1. **ViewModel 层**:
- 创建 `FormPageViewModel`,继承自 `RoutableViewModel`
- 实现表单字段属性:姓名、邮箱、电话、地址、备注
- 实现表单对话框显示/隐藏控制:`IsFormDialogOpen`
- 提供表单提交和取消命令:`ShowFormCommand`、`SubmitFormCommand`、`CancelFormCommand`
- 实现表单验证逻辑:必填字段检查、邮箱格式验证
- 显示提交结果信息
2. **View 层**:
- 创建 `FormPageView.axaml`,使用 `DialogHost` 实现弹出表单
- 主界面包含"打开表单"按钮和结果显示区域
- 表单对话框包含:
- 标题区域(带图标)
- 表单字段:姓名(必填)、邮箱(必填)、电话(可选)、地址(可选)、备注(可选)
- 错误提示区域(动态显示验证错误)
- 提交和取消按钮
- 表单支持滚动,适配长内容
3. **转换器扩展**:
- 在 `StringConverters` 中新增 `IsNotNullOrEmptyConverter` 转换器
- 用于检查字符串是否非空,控制错误提示区域的显示
4. **服务注册**:
- 在 `PageViewModelFactory` 中注册 `form` 页面 ID
- 在 `ServiceCollectionExtensions` 中注册 `FormPageViewModel`
- 在 `NavigationService` 中添加"表单示例"导航项,使用 `IconType.DocumentText` 图标
- **涉及文件**:
- `AuroraDesk.Presentation/ViewModels/Pages/FormPageViewModel.cs` - ViewModel
- `AuroraDesk.Presentation/Views/Pages/FormPageView.axaml` - 视图 XAML
- `AuroraDesk.Presentation/Views/Pages/FormPageView.axaml.cs` - 视图代码后台
- `AuroraDesk.Presentation/Converters/StringConverters.cs` - 新增转换器
- `AuroraDesk.Presentation/Services/PageViewModelFactory.cs` - 页面工厂注册
- `AuroraDesk.Presentation/Extensions/ServiceCollectionExtensions.cs` - ViewModel 注册
- `AuroraDesk.Infrastructure/Services/NavigationService.cs` - 导航服务
- **技术特点**:
- ✅ 使用 ReactiveUI + MVVM 模式
- ✅ 使用 DialogHost.Avalonia 实现模态对话框
- ✅ 支持表单验证和错误提示
- ✅ 现代化的界面设计,表单布局清晰
- ✅ 支持必填字段和可选字段
- ✅ 提交结果显示在主界面
- **效果**:
- ✅ 可以在导航栏中访问"表单示例"页面
- ✅ 点击"打开表单"按钮弹出表单对话框
- ✅ 填写表单信息并提交,支持验证
- ✅ 提交结果显示在主界面
- ✅ 表单界面美观,交互流畅
### 扩展表单示例页面 - 支持多种表单形态
- **日期**: 2025年1月
- **功能描述**: 扩展表单页面,添加多个按钮,每个按钮对应不同的表单形态
- **实现内容**:
1. **表单类型枚举**:
- 新增 `FormType` 枚举,定义5种表单类型:
- `Basic`: 基础表单(全字段:姓名、邮箱、电话、地址、备注)
- `Simple`: 简洁表单(仅姓名、邮箱)
- `Contact`: 联系表单(姓名、电话、邮箱)
- `Feedback`: 反馈表单(姓名、邮箱、备注)
- `Register`: 注册表单(姓名、邮箱、电话、地址)
2. **ViewModel 扩展**:
- 添加 `CurrentFormType` 属性,标识当前表单类型
- 添加 `FormTitle``FormDescription` 属性,动态显示表单标题和描述
- 添加字段可见性属性:`ShowPhoneField`、`ShowAddressField`、`ShowRemarksField`
- 添加5个不同的命令:`ShowBasicFormCommand`、`ShowSimpleFormCommand`、`ShowContactFormCommand`、`ShowFeedbackFormCommand`、`ShowRegisterFormCommand`
- 根据表单类型动态设置标题、描述和字段可见性
- 根据表单类型进行不同的验证规则
3. **View 扩展**:
- 添加5个不同颜色的按钮,每个按钮对应一种表单类型
- 按钮颜色区分:基础表单(蓝色)、简洁表单(绿色)、联系表单(紫色)、反馈表单(橙色)、注册表单(红色)
- 表单字段根据 `IsVisible` 绑定动态显示/隐藏
- 表单标题和描述绑定到 ViewModel 属性,动态更新
4. **验证逻辑优化**:
- 基础表单:验证姓名和邮箱
- 简洁表单:验证姓名和邮箱
- 联系表单:验证姓名、邮箱和电话
- 反馈表单:验证姓名、邮箱和备注
- 注册表单:验证姓名、邮箱、电话和地址
- **涉及文件**:
- `AuroraDesk.Presentation/ViewModels/Pages/FormPageViewModel.cs` - 扩展 ViewModel,添加表单类型和多个命令
- `AuroraDesk.Presentation/Views/Pages/FormPageView.axaml` - 添加多个按钮,动态显示字段
- **技术特点**:
- ✅ 使用枚举定义表单类型,类型安全
- ✅ 根据表单类型动态显示/隐藏字段
- ✅ 不同表单类型使用不同的验证规则
- ✅ 按钮使用不同颜色区分,视觉清晰
- ✅ 表单标题和描述动态更新
- **效果**:
- ✅ 提供5种不同的表单形态,满足不同场景需求
- ✅ 点击不同按钮打开对应类型的表单
- ✅ 表单字段根据类型动态显示,界面简洁
- ✅ 每种表单类型有独立的验证规则
- ✅ 提交结果显示表单类型,便于区分
### 重构表单示例页面 - 实现不同布局形态和控件类型
- **日期**: 2025年1月
- **功能描述**: 重构表单页面,为不同表单类型设计不同的布局形态和控件组合,使每种表单在视觉和功能上都有明显区别
- **实现内容**:
1. **布局形态设计**:
- **基础表单**:垂直布局(标签在上,输入框在下),包含所有字段
- **简洁表单**:水平布局(标签和输入框在同一行,使用 Grid 两列),仅包含姓名和邮箱
- **联系表单**:垂直布局,包含下拉框(ComboBox)选择国家和城市
- **反馈表单**:垂直布局,包含复选框(CheckBox)选项
- **注册表单**:两列布局(使用 Grid 两列两行),充分利用空间
2. **新增控件类型**:
- **下拉框(ComboBox)**:用于联系表单,选择国家和城市
- **复选框(CheckBox)**:用于反馈表单,包含"同意条款"、"订阅邮件"、"接收通知"等选项
3. **ViewModel 扩展**:
- 添加布局控制属性:`UseHorizontalLayout`、`UseTwoColumnLayout`
- 添加控件显示属性:`ShowComboBoxFields`、`ShowCheckBoxFields`
- 添加下拉框数据:`Countries`、`Cities`、`SelectedCountry`、`SelectedCity`
- 添加复选框数据:`AgreeTerms`、`SubscribeNewsletter`、`ReceiveNotifications`
- 根据表单类型进行不同的验证规则(包括下拉框和复选框验证)
4. **转换器扩展**:
- 新增 `BoolToVisibilityConverter` 转换器,支持布尔值到可见性转换
- 支持反转参数(`ConverterParameter='Invert'`),用于条件显示
5. **XAML 布局重构**:
- 使用条件显示(`IsVisible` 绑定)实现不同布局
- 基础表单:垂直 StackPanel 布局
- 简洁表单:Grid 水平布局(标签列宽 100,输入框自适应)
- 注册表单:Grid 两列两行布局
- 联系表单:在垂直布局中添加 ComboBox
- 反馈表单:在垂直布局中添加 CheckBox
- **涉及文件**:
- `AuroraDesk.Presentation/ViewModels/Pages/FormPageViewModel.cs` - 扩展 ViewModel,添加布局控制和控件数据
- `AuroraDesk.Presentation/Views/Pages/FormPageView.axaml` - 重构布局,实现不同表单形态
- `AuroraDesk.Presentation/Converters/StringConverters.cs` - 新增 BoolToVisibilityConverter
- **技术特点**:
- ✅ 5种不同的布局形态,视觉差异明显
- ✅ 使用 Grid 实现水平布局和两列布局
- ✅ 使用 ComboBox 和 CheckBox 丰富表单控件类型
- ✅ 条件显示实现布局切换,代码清晰
- ✅ 每种表单类型有独特的布局和控件组合
- **效果**:
- ✅ 基础表单:传统垂直布局,标签在上输入框在下
- ✅ 简洁表单:水平布局,标签和输入框在同一行,节省空间
- ✅ 联系表单:包含下拉框选择,交互更友好
- ✅ 反馈表单:包含复选框选项,支持多选
- ✅ 注册表单:两列布局,充分利用横向空间
- ✅ 每种表单形态在视觉和功能上都有明显区别

Loading…
Cancel
Save