增强原材料卡片管理功能,新增免密接口和数据处理逻辑,支持原材料卡片的增删改查操作。更新前端视图以支持多行拆码明细拼接,优化用户体验和系统实时数据同步能力。
This commit is contained in:
187
yy-admin-master/YY.Admin/Controls/DateTimeListPicker.xaml
Normal file
187
yy-admin-master/YY.Admin/Controls/DateTimeListPicker.xaml
Normal file
@@ -0,0 +1,187 @@
|
||||
<UserControl x:Class="YY.Admin.Controls.DateTimeListPicker"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:hc="https://handyorg.github.io/handycontrol"
|
||||
x:Name="Root"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="32" d:DesignWidth="220">
|
||||
|
||||
<UserControl.Resources>
|
||||
<!-- 时/分/秒 列表项样式:紧凑高亮,鼠标悬浮浅蓝 -->
|
||||
<Style x:Key="DTLP_TimeListBoxItemStyle" TargetType="ListBoxItem">
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListBoxItem">
|
||||
<Border x:Name="Bd" Background="{TemplateBinding Background}" Padding="0">
|
||||
<ContentPresenter HorizontalAlignment="Stretch"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#EAF3FF"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#1E88E5"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- 时/分/秒 列表通用样式 -->
|
||||
<Style x:Key="DTLP_TimeListBoxStyle" TargetType="ListBox">
|
||||
<Setter Property="BorderThickness" Value="1,0,0,0"/>
|
||||
<Setter Property="BorderBrush" Value="#EEEEEE"/>
|
||||
<Setter Property="Width" Value="56"/>
|
||||
<Setter Property="Height" Value="200"/>
|
||||
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Hidden"/>
|
||||
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
|
||||
<Setter Property="ItemContainerStyle" Value="{StaticResource DTLP_TimeListBoxItemStyle}"/>
|
||||
</Style>
|
||||
|
||||
<!-- 文本框右侧日历图标按钮 -->
|
||||
<Style x:Key="DTLP_IconToggleStyle" TargetType="ToggleButton">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Focusable" Value="False"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<Border Background="{TemplateBinding Background}" Padding="2">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#F2F6FA"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<!-- 顶部:TextBox + 日历图标按钮(合一外观) -->
|
||||
<Grid>
|
||||
<hc:TextBox x:Name="PART_TextBox"
|
||||
Text="{Binding DisplayText, ElementName=Root, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
hc:InfoElement.Placeholder="{Binding Placeholder, ElementName=Root}"
|
||||
Padding="6,4,28,4"
|
||||
VerticalContentAlignment="Center"
|
||||
KeyDown="OnTextBoxKeyDown"
|
||||
LostFocus="OnTextBoxLostFocus"/>
|
||||
|
||||
<ToggleButton x:Name="PART_Toggle"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Width="24" Height="24" Margin="0,0,4,0"
|
||||
Style="{StaticResource DTLP_IconToggleStyle}"
|
||||
IsChecked="{Binding IsDropDownOpen, ElementName=Root, Mode=TwoWay}">
|
||||
<Path Width="14" Height="14" Stretch="Uniform" Fill="#888888"
|
||||
Data="M19,4H17V3a1,1,0,0,0-2,0V4H9V3A1,1,0,0,0,7,3V4H5A2,2,0,0,0,3,6V20a2,2,0,0,0,2,2H19a2,2,0,0,0,2-2V6A2,2,0,0,0,19,4Zm0,16H5V10H19ZM5,8V6H7V7A1,1,0,0,0,9,7V6h6V7a1,1,0,0,0,2,0V6h2V8Z"/>
|
||||
</ToggleButton>
|
||||
</Grid>
|
||||
|
||||
<!-- 弹出层 -->
|
||||
<Popup x:Name="PART_Popup"
|
||||
IsOpen="{Binding IsDropDownOpen, ElementName=Root, Mode=TwoWay}"
|
||||
PlacementTarget="{Binding ElementName=PART_TextBox}"
|
||||
Placement="Bottom" VerticalOffset="2"
|
||||
StaysOpen="False"
|
||||
AllowsTransparency="True"
|
||||
PopupAnimation="Fade">
|
||||
<Border Background="White"
|
||||
BorderBrush="#DCDCDC" BorderThickness="1"
|
||||
Padding="8"
|
||||
SnapsToDevicePixels="True">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Color="#80000000" BlurRadius="10" ShadowDepth="2" Opacity="0.25"/>
|
||||
</Border.Effect>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 上:日历 + 时/分/秒三列 -->
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Calendar x:Name="PART_Calendar" Grid.Column="0"
|
||||
SelectedDate="{Binding PendingDate, ElementName=Root, Mode=TwoWay}"
|
||||
DisplayDate="{Binding PendingDate, ElementName=Root, Mode=OneWay}"
|
||||
Margin="0,0,4,0"/>
|
||||
|
||||
<ListBox x:Name="PART_HourList" Grid.Column="1"
|
||||
Style="{StaticResource DTLP_TimeListBoxStyle}"
|
||||
ItemsSource="{Binding Hours, ElementName=Root}"
|
||||
SelectedItem="{Binding PendingHour, ElementName=Root, Mode=TwoWay}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding StringFormat={}{0:00}}"
|
||||
TextAlignment="Center" Padding="0,4"
|
||||
FontSize="13"/>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<ListBox x:Name="PART_MinuteList" Grid.Column="2"
|
||||
Style="{StaticResource DTLP_TimeListBoxStyle}"
|
||||
ItemsSource="{Binding Minutes, ElementName=Root}"
|
||||
SelectedItem="{Binding PendingMinute, ElementName=Root, Mode=TwoWay}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding StringFormat={}{0:00}}"
|
||||
TextAlignment="Center" Padding="0,4"
|
||||
FontSize="13"/>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<ListBox x:Name="PART_SecondList" Grid.Column="3"
|
||||
Style="{StaticResource DTLP_TimeListBoxStyle}"
|
||||
ItemsSource="{Binding Seconds, ElementName=Root}"
|
||||
SelectedItem="{Binding PendingSecond, ElementName=Root, Mode=TwoWay}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding StringFormat={}{0:00}}"
|
||||
TextAlignment="Center" Padding="0,4"
|
||||
FontSize="13"/>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
|
||||
<!-- 下:此刻 / 确定 -->
|
||||
<Border Grid.Row="1" BorderThickness="0,1,0,0" BorderBrush="#EEEEEE" Margin="0,6,0,0" Padding="0,6,0,0">
|
||||
<DockPanel LastChildFill="False">
|
||||
<Button DockPanel.Dock="Left" Content="此刻"
|
||||
Click="OnNowClick"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
Foreground="#1E88E5" Cursor="Hand"
|
||||
Padding="4,2" FontSize="12"
|
||||
ToolTip="一键回填当前时间"/>
|
||||
<Button DockPanel.Dock="Right" Content="确定"
|
||||
Click="OnConfirmClick"
|
||||
Background="#1E88E5" Foreground="White"
|
||||
BorderThickness="0"
|
||||
Padding="16,4" Cursor="Hand" FontSize="12"/>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
272
yy-admin-master/YY.Admin/Controls/DateTimeListPicker.xaml.cs
Normal file
272
yy-admin-master/YY.Admin/Controls/DateTimeListPicker.xaml.cs
Normal file
@@ -0,0 +1,272 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace YY.Admin.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// 自定义日期时间选择器。
|
||||
/// 弹出层布局:左侧 <see cref="Calendar"/> 日历 + 右侧时/分/秒 三列 <see cref="ListBox"/>,
|
||||
/// 底部「此刻 / 确定」按钮,用于替代 HandyControl 默认的「圆盘表盘 + 上午下午」风格 DateTimePicker。
|
||||
/// </summary>
|
||||
public partial class DateTimeListPicker : UserControl
|
||||
{
|
||||
public DateTimeListPicker()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += (_, _) => SyncDisplay();
|
||||
}
|
||||
|
||||
#region 公共依赖属性
|
||||
|
||||
/// <summary>选择的日期时间,双向绑定。</summary>
|
||||
public static readonly DependencyProperty SelectedDateTimeProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(SelectedDateTime), typeof(DateTime?), typeof(DateTimeListPicker),
|
||||
new FrameworkPropertyMetadata(
|
||||
null,
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
OnSelectedDateTimeChanged));
|
||||
|
||||
public DateTime? SelectedDateTime
|
||||
{
|
||||
get => (DateTime?)GetValue(SelectedDateTimeProperty);
|
||||
set => SetValue(SelectedDateTimeProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>占位符(空值时显示在 TextBox 上的提示文字)。</summary>
|
||||
public static readonly DependencyProperty PlaceholderProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(Placeholder), typeof(string), typeof(DateTimeListPicker),
|
||||
new PropertyMetadata("请选择日期时间"));
|
||||
|
||||
public string Placeholder
|
||||
{
|
||||
get => (string)GetValue(PlaceholderProperty);
|
||||
set => SetValue(PlaceholderProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>显示格式,默认 yyyy-MM-dd HH:mm:ss。</summary>
|
||||
public static readonly DependencyProperty DateTimeFormatProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(DateTimeFormat), typeof(string), typeof(DateTimeListPicker),
|
||||
new PropertyMetadata("yyyy-MM-dd HH:mm:ss", OnDateTimeFormatChanged));
|
||||
|
||||
public string DateTimeFormat
|
||||
{
|
||||
get => (string)GetValue(DateTimeFormatProperty);
|
||||
set => SetValue(DateTimeFormatProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 内部依赖属性
|
||||
|
||||
/// <summary>弹出层是否打开。</summary>
|
||||
public static readonly DependencyProperty IsDropDownOpenProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(IsDropDownOpen), typeof(bool), typeof(DateTimeListPicker),
|
||||
new PropertyMetadata(false, OnIsDropDownOpenChanged));
|
||||
|
||||
public bool IsDropDownOpen
|
||||
{
|
||||
get => (bool)GetValue(IsDropDownOpenProperty);
|
||||
set => SetValue(IsDropDownOpenProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>TextBox 当前显示的文字。用户可手工编辑,回车/失焦时尝试解析。</summary>
|
||||
public static readonly DependencyProperty DisplayTextProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(DisplayText), typeof(string), typeof(DateTimeListPicker),
|
||||
new PropertyMetadata(string.Empty));
|
||||
|
||||
public string DisplayText
|
||||
{
|
||||
get => (string)GetValue(DisplayTextProperty);
|
||||
set => SetValue(DisplayTextProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>弹出层内未确认的日期值。</summary>
|
||||
public static readonly DependencyProperty PendingDateProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(PendingDate), typeof(DateTime?), typeof(DateTimeListPicker),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
public DateTime? PendingDate
|
||||
{
|
||||
get => (DateTime?)GetValue(PendingDateProperty);
|
||||
set => SetValue(PendingDateProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PendingHourProperty =
|
||||
DependencyProperty.Register(nameof(PendingHour), typeof(int), typeof(DateTimeListPicker), new PropertyMetadata(0));
|
||||
|
||||
public int PendingHour
|
||||
{
|
||||
get => (int)GetValue(PendingHourProperty);
|
||||
set => SetValue(PendingHourProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PendingMinuteProperty =
|
||||
DependencyProperty.Register(nameof(PendingMinute), typeof(int), typeof(DateTimeListPicker), new PropertyMetadata(0));
|
||||
|
||||
public int PendingMinute
|
||||
{
|
||||
get => (int)GetValue(PendingMinuteProperty);
|
||||
set => SetValue(PendingMinuteProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PendingSecondProperty =
|
||||
DependencyProperty.Register(nameof(PendingSecond), typeof(int), typeof(DateTimeListPicker), new PropertyMetadata(0));
|
||||
|
||||
public int PendingSecond
|
||||
{
|
||||
get => (int)GetValue(PendingSecondProperty);
|
||||
set => SetValue(PendingSecondProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// 注意:这三个集合必须用字段初始化器赋值,而不是放到构造函数体内。
|
||||
// 原因:XAML 中通过 {Binding Hours, ElementName=Root} 绑定到普通 CLR 只读属性,
|
||||
// 绑定在 InitializeComponent() 执行时就建立。若赋值滞后到构造函数体内,
|
||||
// 绑定第一次求值时 Hours 仍为 null,且不会再有变更通知,导致弹出层右侧三列为空。
|
||||
|
||||
/// <summary>0-23 小时列表。</summary>
|
||||
public List<int> Hours { get; } = Enumerable.Range(0, 24).ToList();
|
||||
|
||||
/// <summary>0-59 分钟列表。</summary>
|
||||
public List<int> Minutes { get; } = Enumerable.Range(0, 60).ToList();
|
||||
|
||||
/// <summary>0-59 秒列表。</summary>
|
||||
public List<int> Seconds { get; } = Enumerable.Range(0, 60).ToList();
|
||||
|
||||
private bool _suppressTextSync;
|
||||
|
||||
private static void OnSelectedDateTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
((DateTimeListPicker)d).SyncDisplay();
|
||||
}
|
||||
|
||||
private static void OnDateTimeFormatChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
((DateTimeListPicker)d).SyncDisplay();
|
||||
}
|
||||
|
||||
private static void OnIsDropDownOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var picker = (DateTimeListPicker)d;
|
||||
if ((bool)e.NewValue)
|
||||
{
|
||||
picker.SyncPendingFromSelected();
|
||||
picker.Dispatcher.BeginInvoke(new Action(picker.ScrollListBoxesToSelection),
|
||||
System.Windows.Threading.DispatcherPriority.Loaded);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>将 <see cref="SelectedDateTime"/> 同步到 TextBox 显示文本。</summary>
|
||||
private void SyncDisplay()
|
||||
{
|
||||
_suppressTextSync = true;
|
||||
try
|
||||
{
|
||||
DisplayText = SelectedDateTime.HasValue
|
||||
? SelectedDateTime.Value.ToString(DateTimeFormat, CultureInfo.InvariantCulture)
|
||||
: string.Empty;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_suppressTextSync = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>弹出层打开时,把当前选中值(或当前时间)拷贝到 Pending 字段。</summary>
|
||||
private void SyncPendingFromSelected()
|
||||
{
|
||||
var dt = SelectedDateTime ?? DateTime.Now;
|
||||
PendingDate = dt.Date;
|
||||
PendingHour = dt.Hour;
|
||||
PendingMinute = dt.Minute;
|
||||
PendingSecond = dt.Second;
|
||||
}
|
||||
|
||||
/// <summary>将三列时间 ListBox 滚动到当前选中项。</summary>
|
||||
private void ScrollListBoxesToSelection()
|
||||
{
|
||||
PART_HourList?.ScrollIntoView(PendingHour);
|
||||
PART_MinuteList?.ScrollIntoView(PendingMinute);
|
||||
PART_SecondList?.ScrollIntoView(PendingSecond);
|
||||
}
|
||||
|
||||
private void OnNowClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
PendingDate = now.Date;
|
||||
PendingHour = now.Hour;
|
||||
PendingMinute = now.Minute;
|
||||
PendingSecond = now.Second;
|
||||
ScrollListBoxesToSelection();
|
||||
}
|
||||
|
||||
private void OnConfirmClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var date = PendingDate ?? DateTime.Today;
|
||||
SelectedDateTime = new DateTime(
|
||||
date.Year, date.Month, date.Day,
|
||||
ClampTime(PendingHour, 23),
|
||||
ClampTime(PendingMinute, 59),
|
||||
ClampTime(PendingSecond, 59));
|
||||
IsDropDownOpen = false;
|
||||
}
|
||||
|
||||
private static int ClampTime(int value, int max)
|
||||
{
|
||||
if (value < 0) return 0;
|
||||
if (value > max) return max;
|
||||
return value;
|
||||
}
|
||||
|
||||
private void OnTextBoxKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter)
|
||||
{
|
||||
TryParseDisplayText();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTextBoxLostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
TryParseDisplayText();
|
||||
}
|
||||
|
||||
/// <summary>解析当前 TextBox 文本到 <see cref="SelectedDateTime"/>,失败则回滚显示。</summary>
|
||||
private void TryParseDisplayText()
|
||||
{
|
||||
if (_suppressTextSync) return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(DisplayText))
|
||||
{
|
||||
SelectedDateTime = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (DateTime.TryParseExact(DisplayText, DateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var exact))
|
||||
{
|
||||
SelectedDateTime = exact;
|
||||
}
|
||||
else if (DateTime.TryParse(DisplayText, CultureInfo.CurrentCulture, DateTimeStyles.None, out var fallback))
|
||||
{
|
||||
SelectedDateTime = fallback;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 输入非法,恢复为原值
|
||||
SyncDisplay();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user