增加 统计页面订单列表 搜索,复制表格。window层级

增加 toast提示 可以作用域任何window
This commit is contained in:
chfei committed 2026-10-07 16:16:55 +08:00
1 parent b1e03ddbb1
commit 03f71a5d64
10 files changed
+286 -27

No files matched your search

+2 -2
View File
@@ -19,10 +19,10 @@ namespace _29WxOrderWpf.Common
private readonly Style dialogWindowStyle;
private bool isConstructorSuccessful = false;
private UserControl _content;
public DialogWindow()
public DialogWindow(Window owner = null)
{
InitializeComponent();
Owner = Application.Current.MainWindow;
Owner = owner ?? Application.Current.MainWindow;
isConstructorSuccessful = true;
dialogWindowStyle = (Style)TryFindResource("DialogWindowStyle");
Loaded += DialogWindow_Loaded;
@@ -22,22 +22,18 @@ namespace _29WxOrderWpf.Ctrls.OrderCtrls;
public partial class OrderListCtrl
{
#region 统计
private DialogWindow _statisticsDialog;
private ShopRevenueStatisticsView _statisticsDialog;
public ICommand ShowShopRevenueStatisticsDialogViewCommand => new DelegateCommand(ShowShopRevenueStatisticsDialogView);
private void ShowShopRevenueStatisticsDialogView()
{
if (_statisticsDialog == null)
{
_statisticsDialog = new DialogWindow()
{
Title = "统计",
Content = new ShopRevenueStatisticsView(),
};
_statisticsDialog =new ShopRevenueStatisticsView();
_statisticsDialog.Closed += (s, e) => _statisticsDialog = null;
}
_statisticsDialog.Show();
_statisticsDialog.Activate();
}
#endregion
@@ -1,6 +1,7 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using WPFCommon.Models;
namespace _29WxOrderWpf.Ctrls.Statistics.Models
{
@@ -13,5 +13,6 @@ namespace _29WxOrderWpf.Ctrls.Statistics
{
InitializeComponent();
}
}
}
@@ -1,4 +1,4 @@
<UserControl
<Window
x:Class="_29WxOrderWpf.Ctrls.Statistics.ShopRevenueStatisticsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -13,10 +13,8 @@
IsDesignTimeCreatable=True}"
d:DesignHeight="900"
d:DesignWidth="1600"
wpf:DialogWindowProperty.ResizeMode="CanResizeWithGrip"
wpf:DialogWindowProperty.ShowMinButton="False"
mc:Ignorable="d">
<UserControl.Resources>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="boolToVisibilityConverter" />
<!-- 卡片样式(白底、圆角、阴影) -->
@@ -119,7 +117,7 @@
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
</Window.Resources>
<Grid Background="#F5F7FB">
<TabControl SelectionChanged="TabControl_SelectionChanged" Style="{StaticResource StatisticsTabControlStyle}">
@@ -348,4 +346,4 @@
</StackPanel>
</Border>
</Grid>
</UserControl>
</Window>
@@ -1,4 +1,5 @@
using System.Windows.Controls;
using System.Windows;
using System.Windows.Controls;
using _29WxOrderWpf.Ctrls.Statistics.ViewModels;
namespace _29WxOrderWpf.Ctrls.Statistics
@@ -9,7 +10,7 @@ namespace _29WxOrderWpf.Ctrls.Statistics
/// 如需注入自定义数据服务,请使用
/// new ShopRevenueStatisticsView(new ShopRevenueStatisticsViewModel(你的 IShopRevenueStatisticsService 实现))。
/// </summary>
public partial class ShopRevenueStatisticsView : UserControl
public partial class ShopRevenueStatisticsView : Window
{
public ShopRevenueStatisticsView()
: this(new ShopRevenueStatisticsViewModel())
@@ -19,7 +20,9 @@ namespace _29WxOrderWpf.Ctrls.Statistics
public ShopRevenueStatisticsView(ShopRevenueStatisticsViewModel viewModel)
{
InitializeComponent();
viewModel.GetWindow = () => this;
DataContext = viewModel;
}
private void help_mouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
@@ -5,9 +5,11 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:_29WxOrderWpf.Ctrls.Statistics"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wpf="WPFCommon"
d:Background="White"
d:DesignHeight="450"
d:DesignWidth="800"
Focusable="True"
mc:Ignorable="d">
<UserControl.Resources>
<!-- 单元格文字居中 -->
@@ -16,11 +18,34 @@
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border
Grid.Row="0"
Padding="10"
BorderBrush="Gray"
BorderThickness="0,0,0,1">
<wpf:SpacingStackPanel HorizontalSpacing="10" Orientation="Horizontal">
<wpf:TitleContent
Title="订单号:"
Width="180"
Placement="Left"
TitleVerticalAlignment="Center">
<TextBox Height="22" Text="{Binding OrderId, UpdateSourceTrigger=PropertyChanged}" />
</wpf:TitleContent>
<Button
Command="{Binding SelectAllCommand}"
Content="复制数据"
Style="{StaticResource BaseButtonLightStyle}" />
</wpf:SpacingStackPanel>
</Border>
<DataGrid
Grid.Row="1"
d:ItemsSource="{d:SampleData ItemCount=10}"
AlternatingRowBackground="#FAFAFA"
AutoGenerateColumns="False"
@@ -35,11 +60,16 @@
HeadersVisibility="Column"
HorizontalGridLinesBrush="#EEEEEE"
IsReadOnly="True"
ItemsSource="{Binding Data}"
ItemsSource="{Binding DataView}"
RowHeight="44"
SelectionMode="Single"
SelectionUnit="FullRow"
VerticalScrollBarVisibility="Auto">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<!-- 店名 -->
<DataGridTextColumn
@@ -1,7 +1,11 @@
using _29WxOrderWpf.BaseWindows;
using _29WxOrderWpf.Commands;
using _29WxOrderWpf.Ctrls.Statistics.Models;
using _29WxOrderWpf.ToolCtrls;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@@ -14,6 +18,7 @@ using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using WPFCommon.Models;
namespace _29WxOrderWpf.Ctrls.Statistics;
@@ -41,20 +46,83 @@ public partial class ShowOrderDialogView : UserControl
try
{
Clipboard.SetDataObject(row.OrderId);
BaseMsgWindow.ShowAnimateMessage("复制成功");
ToastHelper.Show(window, "复制成功");
}
catch (Exception ex)
{
BaseMsgWindow.ShowError("复制失败 " + ex.Message,window: window);
ToastHelper.Show(window, "复制失败 " + ex.Message, type: ToastType.Error);
}
}
}
}
class ShowOrderDialogViewModel
class ShowOrderDialogViewModel : BindableBaseEx
{
public List<ShopRevenueStatisticsRow> Data { get; set; }
public ObservableCollection<ShopRevenueStatisticsRow> Data { get; set; }
public ICollectionView DataView { get; private set; }
public ShowOrderDialogViewModel(List<ShopRevenueStatisticsRow> list)
{
Data = list ?? new List<ShopRevenueStatisticsRow>();
Data = new ObservableCollection<ShopRevenueStatisticsRow>(list ?? new List<ShopRevenueStatisticsRow>());
DataView = CollectionViewSource.GetDefaultView(Data);
DataView.Filter = FilterRow;
}
private bool FilterRow(object obj)
{
if (string.IsNullOrWhiteSpace(OrderId))
return true;
if (obj is ShopRevenueStatisticsRow row)
{
return row.OrderId.Contains(OrderId);
}
return false;
}
public string OrderId
{
get => GetValue<string>();
set => SetValue(value, () => DataView.Refresh());
}
public ICommand SelectAllCommand => new DelegateCommand(SelectAll);
private void SelectAll()
{
var sb = new StringBuilder();
// 表头
sb.AppendLine("店名\t订单\t金额\t退款金额\t外采金额");
// 数据行
foreach (var item in Data)
{
string shopName = CsvEscape(item.ShopName);
string orderId = item.OrderId;
string orderPrice = (item.OrderPrice / 100m).ToString("0.00");
string refundAmount = (item.RefundAmount / 100m).ToString("0.00");
string outAmount = (item.OutAmount / 100m).ToString("0.00");
sb.AppendLine($"{shopName}\t{orderId}\t{orderPrice}\t{refundAmount}\t{outAmount}");
}
Application.Current.Dispatcher.Invoke(() => Clipboard.SetText(sb.ToString()));
var window = Application.Current.Windows
.OfType<Window>()
.FirstOrDefault(w => w.IsActive) ?? Application.Current.MainWindow;
ToastHelper.Show(window, "复制成功");
}
private string CsvEscape(string value)
{
if (string.IsNullOrEmpty(value))
return "";
// 含逗号、引号、换行时,用双引号包起来,内部引号翻倍
if (value.Contains(',') || value.Contains('"') || value.Contains('\n') || value.Contains('\r'))
{
return "\"" + value.Replace("\"", "\"\"") + "\"";
}
return value;
}
}
@@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
namespace _29WxOrderWpf.Ctrls.Statistics.ViewModels
@@ -330,13 +331,13 @@ namespace _29WxOrderWpf.Ctrls.Statistics.ViewModels
default:
break;
}
new DialogWindow()
new DialogWindow(GetWindow())
{
Title="订单列表",
Content = new ShowOrderDialogView(showList),
DataContext = rows
}.ShowDialog();
}.Show();
}
public Func<Window> GetWindow;
}
}
+161
View File
@@ -0,0 +1,161 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
namespace _29WxOrderWpf.ToolCtrls;
public enum ToastType { Info, Success, Warning, Error }
/// <summary>
/// 在指定 Window 内显示自动消失的提示框(Toast)
/// 用法:ToastHelper.Show(window, "保存成功", ToastType.Success);
/// </summary>
public static class ToastHelper
{
// 每个 Window 对应一个提示容器,用于多个提示堆叠
private static readonly ConditionalWeakTable<Window, ToastAdorner> Hosts = new();
private static SolidColorBrush msgBrush;
static ToastHelper()
{
msgBrush = Application.Current.Resources["pageColorBrush"] as SolidColorBrush;
msgBrush = Application.Current.Resources["pageColorBrush"] as SolidColorBrush;
}
/// <param name="owner">要展示提示的窗口</param>
/// <param name="message">提示内容</param>
/// <param name="type">类型(决定背景色)</param>
/// <param name="durationMs">停留时长(毫秒,不含淡入淡出)</param>
public static void Show(Window owner, string message,
ToastType type = ToastType.Info, int durationMs = 2000)
{
if (owner == null || string.IsNullOrEmpty(message)) return;
// 允许在非 UI 线程调用
if (!owner.Dispatcher.CheckAccess())
{
owner.Dispatcher.BeginInvoke(new Action(() => Show(owner, message, type, durationMs)));
return;
}
// 窗口还没加载完,等加载后再显示
if (!owner.IsLoaded)
{
RoutedEventHandler handler = null;
handler = (s, e) =>
{
owner.Loaded -= handler;
Show(owner, message, type, durationMs);
};
owner.Loaded += handler;
return;
}
if (owner.Content is not UIElement content) return;
var layer = AdornerLayer.GetAdornerLayer(content);
if (layer == null) return;
if (!Hosts.TryGetValue(owner, out var host))
{
host = new ToastAdorner(content);
layer.Add(host);
Hosts.Add(owner, host);
}
var toast = CreateToast(message, type);
host.Panel.Children.Add(toast);
// 淡入 -> 停留 -> 淡出
var fadeIn = TimeSpan.FromMilliseconds(250);
var fadeOut = TimeSpan.FromMilliseconds(400);
var hold = TimeSpan.FromMilliseconds(Math.Max(durationMs, 0));
var anim = new DoubleAnimationUsingKeyFrames();
anim.KeyFrames.Add(new LinearDoubleKeyFrame(0, TimeSpan.Zero));
anim.KeyFrames.Add(new LinearDoubleKeyFrame(1, fadeIn));
anim.KeyFrames.Add(new LinearDoubleKeyFrame(1, fadeIn + hold));
anim.KeyFrames.Add(new LinearDoubleKeyFrame(0, fadeIn + hold + fadeOut));
anim.Completed += (s, e) =>
{
host.Panel.Children.Remove(toast);
if (host.Panel.Children.Count == 0)
{
layer.Remove(host);
Hosts.Remove(owner);
}
};
toast.BeginAnimation(UIElement.OpacityProperty, anim);
}
private static Border CreateToast(string message, ToastType type)
{
var color = type switch
{
ToastType.Success => Color.FromRgb(0x2E, 0x9E, 0x5B),
ToastType.Warning => Color.FromRgb(0xE6, 0x9A, 0x1A),
ToastType.Error => Color.FromRgb(0xD9, 0x4A, 0x4A),
_ => msgBrush.Color,//Color.FromRgb(0x40, 0x40, 0x40),
};
return new Border
{
Background = new SolidColorBrush(color),
CornerRadius = new CornerRadius(6),
Padding = new Thickness(18, 10, 18, 10),
Margin = new Thickness(0, 0, 0, 8),
Opacity = 0,
MaxWidth = 480,
Effect = new DropShadowEffect
{
BlurRadius = 12,
ShadowDepth = 2,
Opacity = 0.3,
Color = Colors.Black
},
Child = new TextBlock
{
Text = message,
Foreground = Brushes.White,
FontSize = 14,
TextWrapping = TextWrapping.Wrap
}
};
}
/// <summary>覆盖在窗口内容上方的提示容器,顶部居中</summary>
private sealed class ToastAdorner : Adorner
{
public StackPanel Panel { get; }
public ToastAdorner(UIElement adornedElement) : base(adornedElement)
{
IsHitTestVisible = false; // 不拦截鼠标事件
Panel = new StackPanel { Orientation = Orientation.Vertical };
AddVisualChild(Panel);
}
protected override int VisualChildrenCount => 1;
protected override Visual GetVisualChild(int index) => Panel;
protected override Size MeasureOverride(Size constraint)
{
Panel.Measure(constraint);
return AdornedElement.RenderSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
var size = Panel.DesiredSize;
double x = (finalSize.Width - size.Width) / 2;
Panel.Arrange(new Rect(new Point(Math.Max(x, 0), 30), size)); // 距顶部 30
return finalSize;
}
}
}