587 lines
16 KiB
C#
587 lines
16 KiB
C#
using _29WxOrderWpf.Commands;
|
||
using _29WxOrderWpf.Common;
|
||
using _29WxOrderWpf.Ctrls.Statistics.Models;
|
||
using _29WxOrderWpf.Ctrls.Statistics.Services;
|
||
using AllBase.Model.MvvmBaseClasses;
|
||
using System;
|
||
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
|
||
{
|
||
/// <summary>
|
||
/// 天/周/月周期统计 - 页面 ViewModel(MVVM)。
|
||
/// 通过 PeriodStatisticType 指定统计周期(Day/Week/Month),
|
||
/// 页面统一展示:筛选条件 + 周期×店铺统计表格 + 分页。
|
||
/// </summary>
|
||
public class PeriodRevenueStatisticsViewModel : PropertyNotifyObject
|
||
{
|
||
/// <summary>每页行数</summary>
|
||
private const int PageSize = 50;
|
||
|
||
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 ObservableCollection<ShopOption> _selectedShops;
|
||
private ShopRevenueSummary _summary;
|
||
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);
|
||
|
||
}
|
||
|
||
public void Init()
|
||
{
|
||
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>选中的店铺(多选,绑定 ShopFilterComboBox.SelectedItems)</summary>
|
||
public ObservableCollection<ShopOption> SelectedShops
|
||
{
|
||
get { return _selectedShops; }
|
||
set
|
||
{
|
||
_selectedShops = value;
|
||
OnPropertyChanged(nameof(SelectedShops));
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 统计结果
|
||
|
||
/// <summary>汇总卡片数据</summary>
|
||
public ShopRevenueSummary Summary
|
||
{
|
||
get { return _summary; }
|
||
private set
|
||
{
|
||
_summary = value;
|
||
OnPropertyChanged(nameof(Summary));
|
||
}
|
||
}
|
||
|
||
/// <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
|
||
{
|
||
// 店铺选项(全部店铺由多选控件“全部商铺”表示,不再额外加入“全部店铺”项)
|
||
var shops = await _service.GetShopOptionsAsync();
|
||
foreach (var shop in shops)
|
||
ShopOptions.Add(shop);
|
||
SelectedShops = new ObservableCollection<ShopOption>(ShopOptions);
|
||
|
||
// 默认时间范围
|
||
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>根据选中店铺生成查询店铺 key 列表:未选/全选/选中“全部店铺”时返回空列表(表示全部店铺)</summary>
|
||
private List<string> BuildSelectedShopKeys()
|
||
{
|
||
var selected = SelectedShops;
|
||
if (selected == null || selected.Count == 0)
|
||
return new List<string>();
|
||
|
||
var keys = selected.Select(s => s == null ? string.Empty : s.AppKey).ToList();
|
||
if (keys.Any(string.IsNullOrEmpty))
|
||
return new List<string>();
|
||
|
||
if (selected.Count >= ShopOptions.Count)
|
||
return new List<string>();
|
||
|
||
return keys;
|
||
}
|
||
|
||
/// <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,
|
||
ShopKeys = BuildSelectedShopKeys(),
|
||
};
|
||
|
||
var table = await _service.GetPeriodStatisticsAsync(query, _periodType);
|
||
Summary = await _service.GetSummaryAsync(query);
|
||
_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()
|
||
{
|
||
SelectedShops = new ObservableCollection<ShopOption>(ShopOptions);
|
||
SelectedTimeRange = TimeRangeOptions.FirstOrDefault(t => t.Value == GetDefaultTimeRangeValue(_periodType));
|
||
ApplyTimeRange();
|
||
await LoadAsync();
|
||
}
|
||
|
||
#endregion
|
||
|
||
|
||
|
||
public ICommand ShowOrderListCommand => new AsyncDelegateCommand<string>(ShowOrderList);
|
||
|
||
private async Task ShowOrderList(string type)
|
||
{
|
||
var query = new ShopRevenueStatisticsQuery
|
||
{
|
||
StartDate = StartDate,
|
||
EndDate = EndDate,
|
||
ShopKeys = BuildSelectedShopKeys(),
|
||
};
|
||
var rows = await _service.QueryRowsAsync(query);
|
||
List<ShopRevenueStatisticsRow> showList = new List<ShopRevenueStatisticsRow>();
|
||
switch (type)
|
||
{
|
||
case "ValidOrderCount":
|
||
showList = RevenueStatisticsAggregator.GetValidOrder(rows);
|
||
break; //有效订单
|
||
case "RefundOrderCount":
|
||
showList = RevenueStatisticsAggregator.GetRefundOrder(rows);
|
||
break;//退款订单
|
||
case "BuyerRefundRemainingAmount":
|
||
showList = RevenueStatisticsAggregator.GetBuyerRefundRemainingOrder(rows);
|
||
break;//待退回采购金额
|
||
default:
|
||
break;
|
||
}
|
||
|
||
new DialogWindow()
|
||
{
|
||
Title = "订单列表",
|
||
Content = new ShowOrderDialogView(showList),
|
||
DataContext = rows
|
||
}.ShowDialog();
|
||
}
|
||
}
|
||
} |