Merge branch 'main' of github.com:jtt-fan/29WxOrder
This commit is contained in:
commit
4b8f6f7fb6
10 files changed
+1222
-19
No files matched your search
@@ -8,7 +8,7 @@
|
||||
Title="{Binding Title}"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
Background="#FFFFFF"
|
||||
Background="#FFFFFF"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<Window.Resources>
|
||||
<Style x:Key="DialogWindowStyle" TargetType="Window">
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Remoting.Contexts;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
|
||||
|
||||
|
||||
@@ -15,25 +18,26 @@ namespace _29WxOrderWpf.Common
|
||||
{
|
||||
private readonly Style dialogWindowStyle;
|
||||
private bool isConstructorSuccessful = false;
|
||||
private UserControl _content;
|
||||
public DialogWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
Owner = Application.Current.MainWindow;
|
||||
isConstructorSuccessful = true;
|
||||
dialogWindowStyle = (Style)TryFindResource("DialogWindowStyle");
|
||||
|
||||
Loaded += DialogWindow_Loaded;
|
||||
}
|
||||
private void DialogWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Loaded -= DialogWindow_Loaded;
|
||||
|
||||
|
||||
}
|
||||
protected override void OnContentChanged(object oldContent, object newContent)
|
||||
{
|
||||
this.Style = dialogWindowStyle;
|
||||
if (newContent is System.Windows.Controls.UserControl control)
|
||||
{
|
||||
_content = control;
|
||||
|
||||
Setter widthSetter = this.Style.Setters.OfType<Setter>().FirstOrDefault(a => a.Property == WidthProperty);
|
||||
if (widthSetter == null)
|
||||
@@ -70,7 +74,11 @@ namespace _29WxOrderWpf.Common
|
||||
{
|
||||
this.ResizeMode = resizeExt;
|
||||
}
|
||||
|
||||
WindowStyle wstyleExt = DialogWindowProperty.GetWindowStyle(control);
|
||||
if (wstyleExt != WindowStyle.SingleBorderWindow)
|
||||
{
|
||||
this.WindowStyle = wstyleExt;
|
||||
}
|
||||
}
|
||||
base.OnContentChanged(oldContent, newContent);
|
||||
}
|
||||
@@ -87,5 +95,31 @@ namespace _29WxOrderWpf.Common
|
||||
}
|
||||
base.OnStyleChanged(oldStyle, newStyle);
|
||||
}
|
||||
|
||||
|
||||
#region ShowMiniButton
|
||||
private const int GWL_STYLE = -16;
|
||||
private const int WS_MINIMIZEBOX = 0x00020000;
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
|
||||
protected override void OnSourceInitialized(EventArgs e)
|
||||
{
|
||||
base.OnSourceInitialized(e);
|
||||
|
||||
var hwnd = new WindowInteropHelper(this).Handle;
|
||||
|
||||
int style = GetWindowLong(hwnd, GWL_STYLE);
|
||||
style &= ~WS_MINIMIZEBOX;
|
||||
if (DialogWindowProperty.GetShowMinButton(this._content) == false)
|
||||
{
|
||||
SetWindowLong(hwnd, GWL_STYLE, style);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,18 @@ namespace _29WxOrderWpf.Common
|
||||
public static ResizeMode GetResizeMode(DependencyObject element)
|
||||
=> (ResizeMode)element.GetValue(ResizeModeProperty);
|
||||
|
||||
public static readonly DependencyProperty WindowStyleProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"WindowStyle",
|
||||
typeof(WindowStyle),
|
||||
typeof(DialogWindowProperty),
|
||||
new PropertyMetadata(WindowStyle.SingleBorderWindow));
|
||||
|
||||
public static void SetWindowStyle(DependencyObject element, WindowStyle value)
|
||||
=> element.SetValue(WindowStyleProperty, value);
|
||||
|
||||
public static WindowStyle GetWindowStyle(DependencyObject element)
|
||||
=> (WindowStyle)element.GetValue(WindowStyleProperty);
|
||||
|
||||
|
||||
public static readonly DependencyProperty WidthProperty = DependencyProperty.RegisterAttached(
|
||||
@@ -46,5 +58,47 @@ namespace _29WxOrderWpf.Common
|
||||
=> (double)element.GetValue(HeightProperty);
|
||||
|
||||
|
||||
// 1. ShowMinButton 附加属性
|
||||
public static readonly DependencyProperty ShowMinButtonProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"ShowMinButton",
|
||||
typeof(bool),
|
||||
typeof(DialogWindowProperty),
|
||||
new PropertyMetadata(true));
|
||||
|
||||
public static void SetShowMinButton(DependencyObject element, bool value)
|
||||
=> element.SetValue(ShowMinButtonProperty, value);
|
||||
|
||||
public static bool GetShowMinButton(DependencyObject element)
|
||||
=> (bool)element.GetValue(ShowMinButtonProperty);
|
||||
|
||||
// 2. ShowMaxButton 附加属性
|
||||
public static readonly DependencyProperty ShowMaxButtonProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"ShowMaxButton",
|
||||
typeof(bool),
|
||||
typeof(DialogWindowProperty),
|
||||
new PropertyMetadata(true));
|
||||
|
||||
public static void SetShowMaxButton(DependencyObject element, bool value)
|
||||
=> element.SetValue(ShowMaxButtonProperty, value);
|
||||
|
||||
public static bool GetShowMaxButton(DependencyObject element)
|
||||
=> (bool)element.GetValue(ShowMaxButtonProperty);
|
||||
|
||||
// 3. ShowCloseButton 附加属性
|
||||
public static readonly DependencyProperty ShowCloseButtonProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"ShowCloseButton",
|
||||
typeof(bool),
|
||||
typeof(DialogWindowProperty),
|
||||
new PropertyMetadata(true));
|
||||
|
||||
public static void SetShowCloseButton(DependencyObject element, bool value)
|
||||
=> element.SetValue(ShowCloseButtonProperty, value);
|
||||
|
||||
public static bool GetShowCloseButton(DependencyObject element)
|
||||
=> (bool)element.GetValue(ShowCloseButtonProperty);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -233,10 +233,10 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Charts
|
||||
{
|
||||
double abs = Math.Abs(value);
|
||||
if (abs >= 100000000)
|
||||
return (value / 100000000d).ToString("0.#") + "亿";
|
||||
return (value / 100000000d).ToString("0.00") + "亿";
|
||||
if (abs >= 10000)
|
||||
return (value / 10000d).ToString("0.#") + "万";
|
||||
return value.ToString("#,##0");
|
||||
return (value / 10000d).ToString("0.00") + "万";
|
||||
return value.ToString("#,##0.00");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,6 +181,52 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Models
|
||||
public decimal AvgOrderAmount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统计周期类型:天 / 周 / 月
|
||||
/// </summary>
|
||||
public enum PeriodStatisticType
|
||||
{
|
||||
/// <summary>按天统计</summary>
|
||||
Day = 0,
|
||||
|
||||
/// <summary>按周统计(周一起始)</summary>
|
||||
Week = 1,
|
||||
|
||||
/// <summary>按月统计</summary>
|
||||
Month = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 天/周/月周期统计表格行(金额单位:元)。
|
||||
/// 按所选店铺(或全部店铺)统计:天统计每天一行;周统计每周一行;月统计每月一行。
|
||||
/// </summary>
|
||||
public class ShopPeriodStatisticsItem
|
||||
{
|
||||
/// <summary>周期开始日期(用于排序)</summary>
|
||||
public DateTime PeriodStart { get; set; }
|
||||
|
||||
/// <summary>周期显示文本(天:yyyy-MM-dd;周:yyyy-MM-dd ~ yyyy-MM-dd;月:yyyy-MM)</summary>
|
||||
public string Period { get; set; }
|
||||
|
||||
/// <summary>订单数</summary>
|
||||
public int OrderCount { get; set; }
|
||||
|
||||
/// <summary>销售额(元)</summary>
|
||||
public decimal SalesAmount { get; set; }
|
||||
|
||||
/// <summary>利润(元)</summary>
|
||||
public decimal Profit { get; set; }
|
||||
|
||||
/// <summary>退款金额(元)</summary>
|
||||
public decimal RefundAmount { get; set; }
|
||||
|
||||
/// <summary>客户数</summary>
|
||||
public int CustomerCount { get; set; }
|
||||
|
||||
/// <summary>客单价(元)</summary>
|
||||
public decimal AvgOrderAmount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 店铺下拉选项
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
<UserControl
|
||||
x:Class="_29WxOrderWpf.Ctrls.Statistics.PeriodRevenueStatisticsView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
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="clr-namespace:_29WxOrderWpf.Ctrls.Statistics.ViewModels"
|
||||
d:Background="#F5F7FB"
|
||||
d:DataContext="{d:DesignInstance Type=vm:PeriodRevenueStatisticsViewModel}"
|
||||
d:DesignHeight="900"
|
||||
d:DesignWidth="1600"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="boolToVisibilityConverter" />
|
||||
|
||||
<!-- 日期只允许从日历选择,禁止手工输入 -->
|
||||
<Style TargetType="DatePickerTextBox">
|
||||
<Setter Property="IsReadOnly" Value="True" />
|
||||
</Style>
|
||||
|
||||
<!-- 卡片样式(白底、圆角、阴影) -->
|
||||
<Style x:Key="CardBorderStyle" TargetType="Border">
|
||||
<Setter Property="Background" Value="White" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
<Setter Property="Padding" Value="18" />
|
||||
<Setter Property="Margin" Value="4" />
|
||||
<Setter Property="Effect">
|
||||
<Setter.Value>
|
||||
<DropShadowEffect
|
||||
BlurRadius="10"
|
||||
Opacity="0.08"
|
||||
ShadowDepth="2"
|
||||
Color="#000000" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- 表格单元格文字居中 -->
|
||||
<Style x:Key="CenterCellTextStyle" TargetType="TextBlock">
|
||||
<Setter Property="TextAlignment" Value="Center" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<!-- 面板标题 -->
|
||||
<Style x:Key="PanelTitleStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="16" />
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
<Setter Property="Foreground" Value="#333333" />
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Background="#F5F7FB">
|
||||
<Grid Margin="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- ============ 查询条件工具栏 ============ -->
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
Margin="6,6,6,0"
|
||||
Padding="14,10"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<WrapPanel>
|
||||
<TextBlock
|
||||
Margin="6,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Foreground="#666666"
|
||||
Text="时间" />
|
||||
<ComboBox IsEditable="False"
|
||||
Width="110"
|
||||
Height="32"
|
||||
VerticalContentAlignment="Center"
|
||||
DisplayMemberPath="Text"
|
||||
ItemsSource="{Binding TimeRangeOptions}"
|
||||
SelectedItem="{Binding SelectedTimeRange, Mode=TwoWay}"
|
||||
Style="{StaticResource BaseComboBoxStyle}" />
|
||||
<TextBlock
|
||||
Margin="14,0,6,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Foreground="#666666"
|
||||
Text="日期" />
|
||||
<DatePicker
|
||||
Height="32"
|
||||
VerticalContentAlignment="Center"
|
||||
SelectedDate="{Binding StartDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock
|
||||
Margin="6,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Foreground="#666666"
|
||||
Text="~" />
|
||||
<DatePicker
|
||||
Height="32"
|
||||
VerticalContentAlignment="Center"
|
||||
SelectedDate="{Binding EndDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock
|
||||
Margin="14,0,6,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Foreground="#666666"
|
||||
Text="店铺" />
|
||||
<ComboBox IsEditable="False"
|
||||
Width="130"
|
||||
Height="32"
|
||||
VerticalContentAlignment="Center"
|
||||
DisplayMemberPath="ShopName"
|
||||
ItemsSource="{Binding ShopOptions}"
|
||||
SelectedItem="{Binding SelectedShop, Mode=TwoWay}"
|
||||
Style="{StaticResource BaseComboBoxStyle}" />
|
||||
<Button
|
||||
Height="32"
|
||||
Margin="18,0,0,0"
|
||||
Padding="16,0"
|
||||
Command="{Binding QueryCommand}"
|
||||
Content=" 查询 "
|
||||
Style="{StaticResource BasePageButtonStyle}" />
|
||||
<Button
|
||||
Height="32"
|
||||
Margin="8,0,0,0"
|
||||
Padding="16,0"
|
||||
Command="{Binding ResetCommand}"
|
||||
Content=" 重置 "
|
||||
Style="{StaticResource BasePageButtonLightStyle}" />
|
||||
</WrapPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ============ 周期×店铺统计表格 ============ -->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Margin="6,8,6,12"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Margin="0,0,0,10"
|
||||
Style="{StaticResource PanelTitleStyle}"
|
||||
Text="{Binding PageTitle}" />
|
||||
|
||||
<DataGrid
|
||||
Grid.Row="1"
|
||||
AlternatingRowBackground="#FAFAFA"
|
||||
AutoGenerateColumns="False"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
CanUserAddRows="False"
|
||||
CanUserDeleteRows="False"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeRows="False"
|
||||
ColumnHeaderStyle="{StaticResource BaseDataGridColumnHeaderStyle}"
|
||||
FontSize="13"
|
||||
Foreground="#333333"
|
||||
GridLinesVisibility="Horizontal"
|
||||
HeadersVisibility="Column"
|
||||
HorizontalGridLinesBrush="#EEEEEE"
|
||||
IsReadOnly="True"
|
||||
ItemsSource="{Binding Statistics}"
|
||||
RowHeight="46"
|
||||
ScrollViewer.CanContentScroll="True"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
SelectionMode="Single"
|
||||
SelectionUnit="FullRow">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn
|
||||
Width="1.4*"
|
||||
Binding="{Binding Period}"
|
||||
ElementStyle="{StaticResource CenterCellTextStyle}">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock
|
||||
FontSize="14"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource mainForegroundColorBrush}"
|
||||
Text="{Binding DataContext.PeriodColumnHeader, RelativeSource={RelativeSource AncestorType=DataGrid}}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding OrderCount, StringFormat={}{0:N0}}"
|
||||
ElementStyle="{StaticResource CenterCellTextStyle}"
|
||||
Header="订单数" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding SalesAmount, StringFormat={}¥{0:N2}}"
|
||||
ElementStyle="{StaticResource CenterCellTextStyle}"
|
||||
Header="销售额" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding Profit, StringFormat={}¥{0:N2}}"
|
||||
ElementStyle="{StaticResource CenterCellTextStyle}"
|
||||
Header="利润" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding RefundAmount, StringFormat={}¥{0:N2}}"
|
||||
ElementStyle="{StaticResource CenterCellTextStyle}"
|
||||
Header="退款金额" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding CustomerCount, StringFormat={}{0:N0}}"
|
||||
ElementStyle="{StaticResource CenterCellTextStyle}"
|
||||
Header="客户数" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding AvgOrderAmount, StringFormat={}¥{0:N2}}"
|
||||
ElementStyle="{StaticResource CenterCellTextStyle}"
|
||||
Header="客单价" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<!-- ============ 底部翻页 ============ -->
|
||||
<Border
|
||||
Grid.Row="2"
|
||||
Margin="0,10,0,0"
|
||||
Padding="0,4"
|
||||
Background="#F8F9FB"
|
||||
CornerRadius="6">
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Margin="0,0,12,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="13"
|
||||
Foreground="#666666"
|
||||
Text="{Binding PagerText}" />
|
||||
<Button
|
||||
Height="28"
|
||||
Margin="4,0,0,0"
|
||||
Padding="12,0"
|
||||
Command="{Binding FirstPageCommand}"
|
||||
Content="首页"
|
||||
Style="{StaticResource BasePageButtonLightStyle}" />
|
||||
<Button
|
||||
Height="28"
|
||||
Margin="4,0,0,0"
|
||||
Padding="12,0"
|
||||
Command="{Binding PrevPageCommand}"
|
||||
Content="上一页"
|
||||
Style="{StaticResource BasePageButtonLightStyle}" />
|
||||
<Button
|
||||
Height="28"
|
||||
Margin="4,0,0,0"
|
||||
Padding="12,0"
|
||||
Command="{Binding NextPageCommand}"
|
||||
Content="下一页"
|
||||
Style="{StaticResource BasePageButtonLightStyle}" />
|
||||
<Button
|
||||
Height="28"
|
||||
Margin="4,0,0,0"
|
||||
Padding="12,0"
|
||||
Command="{Binding LastPageCommand}"
|
||||
Content="末页"
|
||||
Style="{StaticResource BasePageButtonLightStyle}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- ============ 加载遮罩 ============ -->
|
||||
<Border Background="#66FFFFFF" Visibility="{Binding IsLoading, Converter={StaticResource boolToVisibilityConverter}}" d:Visibility="Collapsed">
|
||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<ProgressBar
|
||||
Width="240"
|
||||
Height="8"
|
||||
IsIndeterminate="True" />
|
||||
<TextBlock
|
||||
Margin="0,12,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="13"
|
||||
Foreground="#888888"
|
||||
Text="数据加载中..." />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using _29WxOrderWpf.Ctrls.Statistics.Models;
|
||||
using _29WxOrderWpf.Ctrls.Statistics.ViewModels;
|
||||
|
||||
namespace _29WxOrderWpf.Ctrls.Statistics
|
||||
{
|
||||
/// <summary>
|
||||
/// 天/周/月周期统计页面(MVVM)。
|
||||
/// 通过 PeriodType 指定统计周期:Day(天统计)/ Week(周统计)/ Month(月统计)。
|
||||
/// </summary>
|
||||
public partial class PeriodRevenueStatisticsView : UserControl
|
||||
{
|
||||
public PeriodRevenueStatisticsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
// 默认天统计;当 XAML 指定 Week/Month 时通过 PeriodType 回调切换
|
||||
DataContext = new PeriodRevenueStatisticsViewModel(PeriodStatisticType.Day);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PeriodTypeProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(PeriodType),
|
||||
typeof(PeriodStatisticType),
|
||||
typeof(PeriodRevenueStatisticsView),
|
||||
new PropertyMetadata(PeriodStatisticType.Day, OnPeriodTypeChanged));
|
||||
|
||||
/// <summary>统计周期(Day / Week / Month)</summary>
|
||||
public PeriodStatisticType PeriodType
|
||||
{
|
||||
get { return (PeriodStatisticType)GetValue(PeriodTypeProperty); }
|
||||
set { SetValue(PeriodTypeProperty, value); }
|
||||
}
|
||||
|
||||
private static void OnPeriodTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
((PeriodRevenueStatisticsView)d).ApplyPeriodType((PeriodStatisticType)e.NewValue);
|
||||
}
|
||||
|
||||
private void ApplyPeriodType(PeriodStatisticType periodType)
|
||||
{
|
||||
DataContext = new PeriodRevenueStatisticsViewModel(periodType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,9 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Services
|
||||
/// <summary>获取店铺统计表格</summary>
|
||||
Task<List<ShopStatisticsItem>> GetShopStatisticsAsync(ShopRevenueStatisticsQuery query);
|
||||
|
||||
/// <summary>获取天/周/月周期统计表格(每天/每周/每月各一行,按所选店铺或全部店铺统计)</summary>
|
||||
Task<List<ShopPeriodStatisticsItem>> GetPeriodStatisticsAsync(ShopRevenueStatisticsQuery query, PeriodStatisticType periodType);
|
||||
|
||||
/// <summary>获取店铺下拉列表(用于查询条件)</summary>
|
||||
Task<List<ShopOption>> GetShopOptionsAsync();
|
||||
}
|
||||
@@ -197,6 +200,113 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Services
|
||||
.OrderByDescending(i => i.SalesAmount)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>天/周/月周期统计表格(每天/每周/每月各一行,无数据的周期也补一行,金额单位:元)</summary>
|
||||
public static List<ShopPeriodStatisticsItem> GetPeriodStatistics(IEnumerable<ShopRevenueStatisticsRow> rows, DateTime startDate, DateTime endDate, PeriodStatisticType periodType)
|
||||
{
|
||||
var list = (rows ?? new List<ShopRevenueStatisticsRow>()).ToList();
|
||||
|
||||
// 有数据的周期
|
||||
var map = list
|
||||
.GroupBy(r => GetPeriodStart(r.CreateTime, periodType))
|
||||
.ToDictionary(
|
||||
g => g.Key,
|
||||
g =>
|
||||
{
|
||||
var valid = g.Where(IsValidOrder).ToList();
|
||||
var refunded = g.Where(IsRefunded).ToList();
|
||||
decimal salesAmount = valid.Sum(r => (decimal)r.OrderPrice) / 100m;
|
||||
int orderCount = valid.Select(r => r.OrderId).Distinct().Count();
|
||||
return new ShopPeriodStatisticsItem
|
||||
{
|
||||
PeriodStart = g.Key,
|
||||
Period = GetPeriodText(g.Key, periodType),
|
||||
OrderCount = orderCount,
|
||||
SalesAmount = salesAmount,
|
||||
Profit = valid.Sum(r => (decimal)r.Profit) / 100m,
|
||||
RefundAmount = refunded.Sum(r => (decimal)r.OrderPrice) / 100m,
|
||||
CustomerCount = orderCount,
|
||||
AvgOrderAmount = orderCount == 0 ? 0m : salesAmount / orderCount
|
||||
};
|
||||
});
|
||||
|
||||
// 按周期补齐整段时间:无数据的周期也生成一行(全 0)
|
||||
var result = new List<ShopPeriodStatisticsItem>();
|
||||
foreach (var periodStart in EnumeratePeriods(startDate, endDate, periodType))
|
||||
{
|
||||
if (map.TryGetValue(periodStart, out var item))
|
||||
result.Add(item);
|
||||
else
|
||||
result.Add(new ShopPeriodStatisticsItem
|
||||
{
|
||||
PeriodStart = periodStart,
|
||||
Period = GetPeriodText(periodStart, periodType),
|
||||
OrderCount = 0,
|
||||
SalesAmount = 0m,
|
||||
Profit = 0m,
|
||||
RefundAmount = 0m,
|
||||
CustomerCount = 0,
|
||||
AvgOrderAmount = 0m
|
||||
});
|
||||
}
|
||||
|
||||
// 倒序排列:最新的周期在最上面
|
||||
result.Reverse();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>枚举 startDate~endDate 覆盖的所有周期开始日期(天/周/月)</summary>
|
||||
private static IEnumerable<DateTime> EnumeratePeriods(DateTime startDate, DateTime endDate, PeriodStatisticType periodType)
|
||||
{
|
||||
var start = GetPeriodStart(startDate, periodType);
|
||||
var end = GetPeriodStart(endDate, periodType);
|
||||
switch (periodType)
|
||||
{
|
||||
case PeriodStatisticType.Week:
|
||||
for (var d = start; d <= end; d = d.AddDays(7))
|
||||
yield return d;
|
||||
break;
|
||||
case PeriodStatisticType.Month:
|
||||
for (var d = start; d <= end; d = d.AddMonths(1))
|
||||
yield return d;
|
||||
break;
|
||||
default:
|
||||
for (var d = start; d <= end; d = d.AddDays(1))
|
||||
yield return d;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>周期开始日期:天=当天;周=周一起始;月=当月1号</summary>
|
||||
private static DateTime GetPeriodStart(DateTime time, PeriodStatisticType periodType)
|
||||
{
|
||||
switch (periodType)
|
||||
{
|
||||
case PeriodStatisticType.Week:
|
||||
var d = time.Date;
|
||||
int diff = d.DayOfWeek == DayOfWeek.Sunday ? 6 : (int)d.DayOfWeek - 1;
|
||||
return d.AddDays(-diff);
|
||||
case PeriodStatisticType.Month:
|
||||
return new DateTime(time.Year, time.Month, 1);
|
||||
default:
|
||||
return time.Date;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>周期显示文本:天=yyyy-MM-dd;周=yyyy-MM-dd ~ yyyy-MM-dd;月=yyyy-MM</summary>
|
||||
private static string GetPeriodText(DateTime periodStart, PeriodStatisticType periodType)
|
||||
{
|
||||
switch (periodType)
|
||||
{
|
||||
case PeriodStatisticType.Week:
|
||||
return periodStart.ToString("yyyy-MM-dd") + " ~ " + periodStart.AddDays(6).ToString("yyyy-MM-dd");
|
||||
case PeriodStatisticType.Month:
|
||||
return periodStart.ToString("yyyy-MM");
|
||||
default:
|
||||
return periodStart.ToString("yyyy-MM-dd");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -317,15 +427,24 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Services
|
||||
sb.Append("&end_time=").Append(BaseClass.ConvertDateTimeLong(endDate.Date.AddDays(1).AddTicks(-1)));
|
||||
var html = await GetHtml.PostUtf8Async(url, sb.ToString());
|
||||
|
||||
var ret = JsonConvert.DeserializeObject<Result<List<ShopRevenueStatisticsRow>>>(html, new JsonSerializerSettings
|
||||
try
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
});
|
||||
if (ret.Code != 200)
|
||||
{
|
||||
throw new Exception(ret.Msg);
|
||||
var ret = JsonConvert.DeserializeObject<Result<List<ShopRevenueStatisticsRow>>>(html, new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
});
|
||||
if (ret.Code != 200)
|
||||
{
|
||||
throw new Exception(ret.Msg);
|
||||
}
|
||||
return ret.Data ?? new List<ShopRevenueStatisticsRow>();
|
||||
|
||||
}
|
||||
return ret.Data ?? new List<ShopRevenueStatisticsRow>();
|
||||
catch
|
||||
{
|
||||
throw new Exception("查询失败");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -427,6 +546,13 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Services
|
||||
return RevenueStatisticsAggregator.GetShopStatistics(rows);
|
||||
}
|
||||
|
||||
/// <summary>获取天/周/月周期统计表格</summary>
|
||||
public async Task<List<ShopPeriodStatisticsItem>> GetPeriodStatisticsAsync(ShopRevenueStatisticsQuery query, PeriodStatisticType periodType)
|
||||
{
|
||||
var rows = await QueryRowsAsync(query);
|
||||
return RevenueStatisticsAggregator.GetPeriodStatistics(rows, query.StartDate, query.EndDate, periodType);
|
||||
}
|
||||
|
||||
public Task<List<ShopOption>> GetShopOptionsAsync()
|
||||
{
|
||||
var list = GlobalConfig.ShopLoginList.Select(a =>
|
||||
|
||||
@@ -6,12 +6,14 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:_29WxOrderWpf.Ctrls.Statistics.ViewModels"
|
||||
xmlns:stat="clr-namespace:_29WxOrderWpf.Ctrls.Statistics"
|
||||
xmlns:wpf="clr-namespace:_29WxOrderWpf.Common"
|
||||
d:Background="#F5F7FB"
|
||||
d:DataContext="{d:DesignInstance Type=vm:ShopRevenueStatisticsViewModel,IsDesignTimeCreatable=True}"
|
||||
d:DesignHeight="900"
|
||||
d:DesignWidth="1600"
|
||||
wpf:DialogWindowProperty.ResizeMode="CanResizeWithGrip"
|
||||
wpf:DialogWindowProperty.ShowMinButton="False"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="boolToVisibilityConverter" />
|
||||
@@ -45,10 +47,87 @@
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
<Setter Property="Foreground" Value="#333333" />
|
||||
</Style>
|
||||
|
||||
<!-- TabControl 样式(页签 + 内容区) -->
|
||||
<Style x:Key="StatisticsTabControlStyle" TargetType="TabControl">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="TabControl">
|
||||
<Grid Margin="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
Margin="6,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
Background="Transparent">
|
||||
<TabPanel
|
||||
x:Name="HeaderPanel"
|
||||
IsItemsHost="True"
|
||||
Background="Transparent" />
|
||||
</Border>
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Background="Transparent"
|
||||
CornerRadius="0,8,8,8">
|
||||
<ContentPresenter
|
||||
x:Name="PART_SelectedContentHost"
|
||||
ContentSource="SelectedContent" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- TabItem 样式 -->
|
||||
<Style x:Key="StatisticsTabItemStyle" TargetType="TabItem">
|
||||
<Setter Property="Foreground" Value="#666666" />
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="TabItem">
|
||||
<Border
|
||||
x:Name="Bd"
|
||||
Margin="0,0,4,0"
|
||||
Padding="20,9"
|
||||
Background="Transparent"
|
||||
BorderBrush="#E0E4EA"
|
||||
BorderThickness="1,1,1,0"
|
||||
CornerRadius="8,8,0,0">
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
ContentSource="Header" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#F0F4FB" />
|
||||
<Setter Property="Foreground" Value="#98638F" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="White" />
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="#E0E4EA" />
|
||||
<Setter Property="Foreground" Value="#98638F" />
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Background="#F5F7FB">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
<TabControl Style="{StaticResource StatisticsTabControlStyle}">
|
||||
<TabItem
|
||||
Header="综合统计"
|
||||
Style="{StaticResource StatisticsTabItemStyle}">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="12">
|
||||
|
||||
<!-- ============ 查询条件工具栏 ============ -->
|
||||
@@ -406,17 +485,17 @@
|
||||
Header="订单数" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding SalesAmount, StringFormat={}¥{0:N0}}"
|
||||
Binding="{Binding SalesAmount, StringFormat={}¥{0:N2}}"
|
||||
ElementStyle="{StaticResource CenterCellTextStyle}"
|
||||
Header="销售额" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding Profit, StringFormat={}¥{0:N0}}"
|
||||
Binding="{Binding Profit, StringFormat={}¥{0:N2}}"
|
||||
ElementStyle="{StaticResource CenterCellTextStyle}"
|
||||
Header="利润" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding RefundAmount, StringFormat={}¥{0:N0}}"
|
||||
Binding="{Binding RefundAmount, StringFormat={}¥{0:N2}}"
|
||||
ElementStyle="{StaticResource CenterCellTextStyle}"
|
||||
Header="退款金额" />
|
||||
<DataGridTextColumn
|
||||
@@ -434,7 +513,27 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
<TabItem
|
||||
Header="天统计"
|
||||
Style="{StaticResource StatisticsTabItemStyle}">
|
||||
<stat:PeriodRevenueStatisticsView PeriodType="Day" />
|
||||
</TabItem>
|
||||
|
||||
<TabItem
|
||||
Header="周统计"
|
||||
Style="{StaticResource StatisticsTabItemStyle}">
|
||||
<stat:PeriodRevenueStatisticsView PeriodType="Week" />
|
||||
</TabItem>
|
||||
|
||||
<TabItem
|
||||
Header="月统计"
|
||||
Style="{StaticResource StatisticsTabItemStyle}">
|
||||
<stat:PeriodRevenueStatisticsView PeriodType="Month" />
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
|
||||
<!-- ============ 加载遮罩 ============ -->
|
||||
<Border Background="#66FFFFFF" Visibility="{Binding IsLoading, Converter={StaticResource boolToVisibilityConverter}}" d:Visibility="Collapsed" >
|
||||
|
||||
@@ -0,0 +1,516 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using System.Windows;
|
||||
using AllBase.Model.MvvmBaseClasses;
|
||||
using _29WxOrderWpf.Ctrls.Statistics.Models;
|
||||
using _29WxOrderWpf.Ctrls.Statistics.Services;
|
||||
using _29WxOrderWpf.Commands;
|
||||
|
||||
namespace _29WxOrderWpf.Ctrls.Statistics.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 天/周/月周期统计 - 页面 ViewModel(MVVM)。
|
||||
/// 通过 PeriodStatisticType 指定统计周期(Day/Week/Month),
|
||||
/// 页面统一展示:筛选条件 + 周期×店铺统计表格 + 分页。
|
||||
/// </summary>
|
||||
public class PeriodRevenueStatisticsViewModel : PropertyNotifyObject
|
||||
{
|
||||
/// <summary>每页行数</summary>
|
||||
private const int PageSize = 20;
|
||||
|
||||
private readonly IShopRevenueStatisticsService _service;
|
||||
private readonly PeriodStatisticType _periodType;
|
||||
private readonly DelegateCommand _firstPageCommand;
|
||||
private readonly DelegateCommand _prevPageCommand;
|
||||
private readonly DelegateCommand _nextPageCommand;
|
||||
private readonly DelegateCommand _lastPageCommand;
|
||||
|
||||
private ShopOption _selectedShop;
|
||||
private ObservableCollection<ShopPeriodStatisticsItem> _statistics;
|
||||
private List<ShopPeriodStatisticsItem> _allItems = new List<ShopPeriodStatisticsItem>();
|
||||
private int _pageIndex = 1;
|
||||
private int _totalPages = 1;
|
||||
private int _totalCount;
|
||||
private string _pagerText = string.Empty;
|
||||
|
||||
public PeriodRevenueStatisticsViewModel(PeriodStatisticType periodType)
|
||||
: this(periodType, new ShopRevenueStatisticsService())
|
||||
{
|
||||
}
|
||||
|
||||
public PeriodRevenueStatisticsViewModel(PeriodStatisticType periodType, IShopRevenueStatisticsService service)
|
||||
{
|
||||
_periodType = periodType;
|
||||
_service = service ?? new ShopRevenueStatisticsService();
|
||||
|
||||
PageTitle = GetPageTitle(periodType);
|
||||
PeriodColumnHeader = GetPeriodColumnHeader(periodType);
|
||||
|
||||
TimeRangeOptions = new ObservableCollection<TimeRangeOption>(GetTimeRangeOptions(periodType));
|
||||
|
||||
ShopOptions = new ObservableCollection<ShopOption>();
|
||||
|
||||
QueryCommand = new DelegateCommand(async () => await LoadAsync());
|
||||
ResetCommand = new DelegateCommand(Reset);
|
||||
_firstPageCommand = new DelegateCommand(() => GoToPage(1), () => HasPrevPage);
|
||||
_prevPageCommand = new DelegateCommand(() => GoToPage(PageIndex - 1), () => HasPrevPage);
|
||||
_nextPageCommand = new DelegateCommand(() => GoToPage(PageIndex + 1), () => HasNextPage);
|
||||
_lastPageCommand = new DelegateCommand(() => GoToPage(TotalPages), () => HasNextPage);
|
||||
|
||||
InitializeAsync();
|
||||
}
|
||||
|
||||
#region 页面标题
|
||||
|
||||
/// <summary>页面标题(天统计/周统计/月统计)</summary>
|
||||
public string PageTitle { get; }
|
||||
|
||||
/// <summary>周期列表头(日期/周/月份)</summary>
|
||||
public string PeriodColumnHeader { get; }
|
||||
|
||||
private static string GetPageTitle(PeriodStatisticType periodType)
|
||||
{
|
||||
switch (periodType)
|
||||
{
|
||||
case PeriodStatisticType.Week:
|
||||
return "周统计";
|
||||
case PeriodStatisticType.Month:
|
||||
return "月统计";
|
||||
default:
|
||||
return "天统计";
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetPeriodColumnHeader(PeriodStatisticType periodType)
|
||||
{
|
||||
switch (periodType)
|
||||
{
|
||||
case PeriodStatisticType.Week:
|
||||
return "周";
|
||||
case PeriodStatisticType.Month:
|
||||
return "月份";
|
||||
default:
|
||||
return "日期";
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 时间范围选项
|
||||
|
||||
/// <summary>按统计周期返回对应的时间范围选项</summary>
|
||||
private static List<TimeRangeOption> GetTimeRangeOptions(PeriodStatisticType periodType)
|
||||
{
|
||||
switch (periodType)
|
||||
{
|
||||
case PeriodStatisticType.Week:
|
||||
return new List<TimeRangeOption>
|
||||
{
|
||||
new TimeRangeOption { Text = "本周", Value = "week" },
|
||||
new TimeRangeOption { Text = "上周", Value = "lastweek" },
|
||||
new TimeRangeOption { Text = "最近2周", Value = "last2weeks" },
|
||||
new TimeRangeOption { Text = "最近4周", Value = "last4weeks" },
|
||||
new TimeRangeOption { Text = "自定义", Value = "custom" }
|
||||
};
|
||||
case PeriodStatisticType.Month:
|
||||
return new List<TimeRangeOption>
|
||||
{
|
||||
new TimeRangeOption { Text = "本月", Value = "month" },
|
||||
new TimeRangeOption { Text = "上月", Value = "lastmonth" },
|
||||
new TimeRangeOption { Text = "最近3个月", Value = "last3months" },
|
||||
new TimeRangeOption { Text = "最近6个月", Value = "last6months" },
|
||||
new TimeRangeOption { Text = "最近12个月", Value = "last12months" },
|
||||
new TimeRangeOption { Text = "自定义", Value = "custom" }
|
||||
};
|
||||
default:
|
||||
return new List<TimeRangeOption>
|
||||
{
|
||||
new TimeRangeOption { Text = "最近5天", Value = "last5" },
|
||||
new TimeRangeOption { Text = "最近10天", Value = "last10" },
|
||||
new TimeRangeOption { Text = "最近20天", Value = "last20" },
|
||||
new TimeRangeOption { Text = "最近30天", Value = "last30" },
|
||||
new TimeRangeOption { Text = "自定义", Value = "custom" }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>按统计周期返回默认选中的时间范围值</summary>
|
||||
private static string GetDefaultTimeRangeValue(PeriodStatisticType periodType)
|
||||
{
|
||||
switch (periodType)
|
||||
{
|
||||
case PeriodStatisticType.Week:
|
||||
return "week";
|
||||
case PeriodStatisticType.Month:
|
||||
return "month";
|
||||
default:
|
||||
return "last10";
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 查询条件
|
||||
|
||||
/// <summary>时间范围选项</summary>
|
||||
public ObservableCollection<TimeRangeOption> TimeRangeOptions { get; }
|
||||
|
||||
/// <summary>选中的时间范围</summary>
|
||||
public TimeRangeOption SelectedTimeRange
|
||||
{
|
||||
get { return this.GetValue(x => x.SelectedTimeRange); }
|
||||
set
|
||||
{
|
||||
this.SetValue(x => x.SelectedTimeRange, value);
|
||||
ApplyTimeRange();
|
||||
OnPropertyChanged(nameof(IsCustomDateRange));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>是否为自定义时间范围</summary>
|
||||
public bool IsCustomDateRange
|
||||
{
|
||||
get { return SelectedTimeRange != null && SelectedTimeRange.Value == "custom"; }
|
||||
}
|
||||
|
||||
/// <summary>开始日期</summary>
|
||||
public DateTime StartDate
|
||||
{
|
||||
get { return this.GetValue(x => x.StartDate); }
|
||||
set { this.SetValue(x => x.StartDate, value); }
|
||||
}
|
||||
|
||||
/// <summary>结束日期</summary>
|
||||
public DateTime EndDate
|
||||
{
|
||||
get { return this.GetValue(x => x.EndDate); }
|
||||
set { this.SetValue(x => x.EndDate, value); }
|
||||
}
|
||||
|
||||
/// <summary>店铺选项(含“全部店铺”)</summary>
|
||||
public ObservableCollection<ShopOption> ShopOptions { get; }
|
||||
|
||||
/// <summary>选中的店铺</summary>
|
||||
public ShopOption SelectedShop
|
||||
{
|
||||
get { return _selectedShop; }
|
||||
set
|
||||
{
|
||||
_selectedShop = value;
|
||||
OnPropertyChanged(nameof(SelectedShop));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 统计结果
|
||||
|
||||
/// <summary>当前页的周期×店铺统计表格数据</summary>
|
||||
public ObservableCollection<ShopPeriodStatisticsItem> Statistics
|
||||
{
|
||||
get { return _statistics; }
|
||||
private set
|
||||
{
|
||||
_statistics = value;
|
||||
OnPropertyChanged(nameof(Statistics));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>是否正在加载</summary>
|
||||
public bool IsLoading
|
||||
{
|
||||
get { return this.GetValue(x => x.IsLoading); }
|
||||
set { this.SetValue(x => x.IsLoading, value); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 分页
|
||||
|
||||
/// <summary>总行数</summary>
|
||||
public int TotalCount
|
||||
{
|
||||
get { return _totalCount; }
|
||||
private set
|
||||
{
|
||||
_totalCount = value;
|
||||
OnPropertyChanged(nameof(TotalCount));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>当前页码(从 1 开始)</summary>
|
||||
public int PageIndex
|
||||
{
|
||||
get { return _pageIndex; }
|
||||
private set
|
||||
{
|
||||
_pageIndex = value;
|
||||
OnPropertyChanged(nameof(PageIndex));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>总页数</summary>
|
||||
public int TotalPages
|
||||
{
|
||||
get { return _totalPages; }
|
||||
private set
|
||||
{
|
||||
_totalPages = value;
|
||||
OnPropertyChanged(nameof(TotalPages));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>分页提示文本(共 X 条 第 Y / Z 页)</summary>
|
||||
public string PagerText
|
||||
{
|
||||
get { return _pagerText; }
|
||||
private set
|
||||
{
|
||||
_pagerText = value;
|
||||
OnPropertyChanged(nameof(PagerText));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>是否有上一页</summary>
|
||||
public bool HasPrevPage
|
||||
{
|
||||
get { return PageIndex > 1; }
|
||||
}
|
||||
|
||||
/// <summary>是否有下一页</summary>
|
||||
public bool HasNextPage
|
||||
{
|
||||
get { return PageIndex < TotalPages; }
|
||||
}
|
||||
|
||||
/// <summary>是否多页</summary>
|
||||
public bool HasMultiplePages
|
||||
{
|
||||
get { return TotalPages > 1; }
|
||||
}
|
||||
|
||||
/// <summary>首页命令</summary>
|
||||
public ICommand FirstPageCommand
|
||||
{
|
||||
get { return _firstPageCommand; }
|
||||
}
|
||||
|
||||
/// <summary>上一页命令</summary>
|
||||
public ICommand PrevPageCommand
|
||||
{
|
||||
get { return _prevPageCommand; }
|
||||
}
|
||||
|
||||
/// <summary>下一页命令</summary>
|
||||
public ICommand NextPageCommand
|
||||
{
|
||||
get { return _nextPageCommand; }
|
||||
}
|
||||
|
||||
/// <summary>末页命令</summary>
|
||||
public ICommand LastPageCommand
|
||||
{
|
||||
get { return _lastPageCommand; }
|
||||
}
|
||||
|
||||
/// <summary>跳转到指定页并刷新当前页数据</summary>
|
||||
private void GoToPage(int page)
|
||||
{
|
||||
if (page < 1)
|
||||
page = 1;
|
||||
if (page > TotalPages)
|
||||
page = TotalPages;
|
||||
PageIndex = page;
|
||||
ApplyPaging();
|
||||
}
|
||||
|
||||
/// <summary>按当前页码截取当前页数据并刷新分页状态</summary>
|
||||
private void ApplyPaging()
|
||||
{
|
||||
var pageItems = _allItems
|
||||
.Skip((PageIndex - 1) * PageSize)
|
||||
.Take(PageSize)
|
||||
.ToList();
|
||||
|
||||
Statistics = new ObservableCollection<ShopPeriodStatisticsItem>(pageItems);
|
||||
PagerText = string.Format("共 {0} 条 第 {1} / {2} 页", TotalCount, PageIndex, TotalPages);
|
||||
|
||||
OnPropertyChanged(nameof(HasPrevPage));
|
||||
OnPropertyChanged(nameof(HasNextPage));
|
||||
OnPropertyChanged(nameof(HasMultiplePages));
|
||||
_firstPageCommand.RaiseCanExecuteChanged();
|
||||
_prevPageCommand.RaiseCanExecuteChanged();
|
||||
_nextPageCommand.RaiseCanExecuteChanged();
|
||||
_lastPageCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
|
||||
/// <summary>查询命令</summary>
|
||||
public ICommand QueryCommand { get; }
|
||||
|
||||
/// <summary>重置命令</summary>
|
||||
public ICommand ResetCommand { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 初始化
|
||||
|
||||
private async void InitializeAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 店铺选项
|
||||
ShopOptions.Add(new ShopOption { AppKey = string.Empty, ShopName = "全部店铺" });
|
||||
var shops = await _service.GetShopOptionsAsync();
|
||||
foreach (var shop in shops)
|
||||
ShopOptions.Add(shop);
|
||||
SelectedShop = ShopOptions.FirstOrDefault();
|
||||
|
||||
// 默认时间范围
|
||||
SelectedTimeRange = TimeRangeOptions.FirstOrDefault(t => t.Value == GetDefaultTimeRangeValue(_periodType));
|
||||
ApplyTimeRange();
|
||||
|
||||
await LoadAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show("初始化数据失败:" + ex.Message, "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 业务逻辑
|
||||
|
||||
/// <summary>根据时间范围选项计算起止日期</summary>
|
||||
private void ApplyTimeRange()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
var today = now.Date;
|
||||
var selected = SelectedTimeRange == null ? string.Empty : SelectedTimeRange.Value;
|
||||
switch (selected)
|
||||
{
|
||||
case "last5":
|
||||
StartDate = today.AddDays(-4);
|
||||
EndDate = now;
|
||||
break;
|
||||
case "last10":
|
||||
StartDate = today.AddDays(-9);
|
||||
EndDate = now;
|
||||
break;
|
||||
case "last20":
|
||||
StartDate = today.AddDays(-19);
|
||||
EndDate = now;
|
||||
break;
|
||||
case "last30":
|
||||
StartDate = today.AddDays(-29);
|
||||
EndDate = now;
|
||||
break;
|
||||
case "week":
|
||||
{
|
||||
int diff = today.DayOfWeek == DayOfWeek.Sunday ? 6 : (int)today.DayOfWeek - 1;
|
||||
StartDate = today.AddDays(-diff);
|
||||
EndDate = now;
|
||||
}
|
||||
break;
|
||||
case "lastweek":
|
||||
{
|
||||
int diff = today.DayOfWeek == DayOfWeek.Sunday ? 6 : (int)today.DayOfWeek - 1;
|
||||
var thisWeekStart = today.AddDays(-diff);
|
||||
StartDate = thisWeekStart.AddDays(-7);
|
||||
EndDate = thisWeekStart.AddDays(-1).AddHours(23).AddMinutes(59).AddSeconds(59);
|
||||
}
|
||||
break;
|
||||
case "last2weeks":
|
||||
StartDate = today.AddDays(-13);
|
||||
EndDate = now;
|
||||
break;
|
||||
case "last4weeks":
|
||||
StartDate = today.AddDays(-27);
|
||||
EndDate = now;
|
||||
break;
|
||||
case "month":
|
||||
StartDate = new DateTime(today.Year, today.Month, 1);
|
||||
EndDate = now;
|
||||
break;
|
||||
case "lastmonth":
|
||||
{
|
||||
var firstOfThisMonth = new DateTime(today.Year, today.Month, 1);
|
||||
StartDate = firstOfThisMonth.AddMonths(-1);
|
||||
EndDate = firstOfThisMonth.AddDays(-1).AddHours(23).AddMinutes(59).AddSeconds(59);
|
||||
}
|
||||
break;
|
||||
case "last3months":
|
||||
StartDate = new DateTime(today.Year, today.Month, 1).AddMonths(-2);
|
||||
EndDate = now;
|
||||
break;
|
||||
case "last6months":
|
||||
StartDate = new DateTime(today.Year, today.Month, 1).AddMonths(-5);
|
||||
EndDate = now;
|
||||
break;
|
||||
case "last12months":
|
||||
StartDate = new DateTime(today.Year, today.Month, 1).AddMonths(-11);
|
||||
EndDate = now;
|
||||
break;
|
||||
default:
|
||||
// 自定义:保持用户选择的日期不变
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>执行查询,刷新周期统计表格并回到第一页</summary>
|
||||
private async Task LoadAsync()
|
||||
{
|
||||
if (StartDate > EndDate)
|
||||
{
|
||||
var temp = StartDate;
|
||||
StartDate = EndDate;
|
||||
EndDate = temp;
|
||||
}
|
||||
|
||||
IsLoading = true;
|
||||
try
|
||||
{
|
||||
var query = new ShopRevenueStatisticsQuery
|
||||
{
|
||||
StartDate = StartDate,
|
||||
EndDate = EndDate,
|
||||
ShopKey = SelectedShop == null ? string.Empty : SelectedShop.AppKey,
|
||||
};
|
||||
|
||||
var table = await _service.GetPeriodStatisticsAsync(query, _periodType);
|
||||
_allItems = table ?? new List<ShopPeriodStatisticsItem>();
|
||||
TotalCount = _allItems.Count;
|
||||
TotalPages = TotalCount == 0 ? 1 : (int)Math.Ceiling(TotalCount / (double)PageSize);
|
||||
GoToPage(1);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 查询异常:保留上一次数据,并弹框提示
|
||||
MessageBox.Show("查询失败:" + ex.Message, "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>重置查询条件并重新加载</summary>
|
||||
private async void Reset()
|
||||
{
|
||||
SelectedShop = ShopOptions.FirstOrDefault();
|
||||
SelectedTimeRange = TimeRangeOptions.FirstOrDefault(t => t.Value == GetDefaultTimeRangeValue(_periodType));
|
||||
ApplyTimeRange();
|
||||
await LoadAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user