Files
29WxOrder/29WxOrderWpf/Ctrls/Statistics/ViewModels/PeriodRevenueStatisticsViewModel.cs
T

635 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using WxOrder.Framework.Commons;
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;
using WxOrder.Framework.Commons;
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;
/// <summary>是否已完成首次初始化(用于避免页签来回切换时重复初始化、重复查询)</summary>
private bool _initialized;
/// <summary>上一次真正生效(并据此计算过日期)的时间范围选项,避免同一个选项被重复应用</summary>
private TimeRangeOption _appliedTimeRange;
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);
}
/// <summary>
/// 页面首次显示时初始化查询条件并加载数据(由外层 TabControl 的选中事件调用)。
/// 重复调用(在“综合统计 / 天统计 / 周统计 / 月统计”页签之间来回切换)会被忽略:
/// 否则每次切回来都会把时间范围和店铺重置成默认值,并重新发起一次查询。
/// 需要刷新数据时点“查询”按钮即可。
/// </summary>
public void Init()
{
if (_initialized)
return;
_initialized = true;
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);
// 只有用户真正切换到“另一个”时间范围选项时才重新计算起止日期。
// 页签切换时下拉框会把同一个选项(甚至先 null 再回写)重放一遍,
// 若照样重算,就会把用户手工调过的日期覆盖掉(例如自定义区间被改回“本周”)。
if (value != null && !ReferenceEquals(_appliedTimeRange, value))
{
_appliedTimeRange = 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>
/// 根据选中店铺生成查询用 ERP 店铺ID 列表(接口 ShopIds 使用 ShopLoginInfo.ErpShopId):
/// 未选/全选/选中“全部店铺”时返回空列表(表示全部店铺)。
/// </summary>
private List<string> BuildSelectedShopIds()
{
var selected = SelectedShops;
if (selected == null || selected.Count == 0)
return new List<string>();
var ids = selected.Select(s => s == null ? string.Empty : s.ErpShopId).ToList();
if (ids.Any(string.IsNullOrEmpty))
return new List<string>();
if (selected.Count >= ShopOptions.Count)
return new List<string>();
return ids;
}
/// <summary>组装查询参数(起止日期已保证先后顺序)</summary>
private ShopRevenueStatisticsQuery BuildQuery()
{
if (StartDate > EndDate)
{
var temp = StartDate;
StartDate = EndDate;
EndDate = temp;
}
return new ShopRevenueStatisticsQuery
{
StartDate = StartDate,
EndDate = EndDate,
ShopIds = BuildSelectedShopIds(),
};
}
/// <summary>执行查询,刷新周期统计表格并回到第一页</summary>
private async Task LoadAsync()
{
IsLoading = true;
try
{
var result = await _service.QueryPeriodStatisticsAsync(BuildQuery(), _periodType);
Summary = result.Summary;
_allItems = result.Items ?? 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 detailType = ParseDetailType(type);
List<ShopOrderDetailResponse> showList;
// 明细接口数据量大、耗时长,期间显示页面统一的加载遮罩,避免点了没反应
IsLoading = true;
try
{
showList = await _service.QueryOrderRowsAsync(BuildQuery(), detailType)
?? new List<ShopOrderDetailResponse>();
}
catch (Exception ex)
{
MessageBox.Show("订单明细查询失败:" + ex.Message, "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
finally
{
IsLoading = false;
}
if (showList.Count == 0)
{
MessageBox.Show("未查询到符合条件的订单明细。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
new DialogWindow()
{
Title = "订单列表",
Content = new ShowOrderDialogView(showList, detailType, _service),
DataContext = showList
}.ShowDialog();
}
/// <summary>卡片“明细”按钮参数 → 订单明细类型(对应接口参数 OrderType)</summary>
private static ShopOrderDetailType ParseDetailType(string type)
{
switch (type)
{
case "RefundOrderCount":
return ShopOrderDetailType.Refund; //退款订单
case "BuyerRefundRemainingAmount":
return ShopOrderDetailType.BuyerUnrefunded; //买手未退回金额的订单
default:
return ShopOrderDetailType.Valid; //有效订单
}
}
}
}