统计页面 修改 增加统计Border。优化部分统计DataGrid的列展示
This commit is contained in:
1 parent
f2d619eafb
commit
b6b72e6bc3
9 files changed
+883
-687
No files matched your search
@@ -15,80 +15,128 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Models
|
||||
/// <summary>结束时间</summary>
|
||||
public DateTime EndDate { get; set; }
|
||||
|
||||
/// <summary>店铺 app_key(A.app_key),空/Null 表示全部店铺</summary>
|
||||
/// <summary>店铺 shop_id(B.shop_id),空/Null 表示全部店铺</summary>
|
||||
public string ShopKey { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 店铺营收统计 - 接口返回的原始明细行。
|
||||
/// 对应接口返回字段:
|
||||
/// A.app_key, B.create_time, B.order_id, B.status,
|
||||
/// B.product_price, B.order_price, B.purchase_price, B.freight,
|
||||
/// B.predict_commission_fee, B.commission_amount, B.discounted_price, B.profit,
|
||||
/// B.purchase_status, c.refund_status
|
||||
/// 其中 A 为店铺表,B 为订单表,c 为售后/退款表。
|
||||
/// 金额字段单位均为“分”,展示时需除以 100。
|
||||
/// 与后端 WXOrderAmountInfo 保持一致(camelCase JSON):
|
||||
/// shopName, createTime, orderId, status, productPrice, orderPrice, purchasePrice, freight,
|
||||
/// predictCommissionFee, commissionAmount, discountedPrice, profit, purchaseStatus,
|
||||
/// outAmount, refundStatus, refundStatusString, refundAmount, externalProcurement
|
||||
/// 金额字段单位均为分(显示时需除以 100)。
|
||||
/// </summary>
|
||||
public class ShopRevenueStatisticsRow
|
||||
{
|
||||
/// <summary>A.app_key 店铺 app_key</summary>
|
||||
[JsonProperty("app_key")]
|
||||
public string AppKey { get; set; }
|
||||
/// <summary>
|
||||
/// 店铺名称
|
||||
/// </summary>
|
||||
[JsonProperty("shop_name")]
|
||||
public string ShopName { get; set; }
|
||||
|
||||
/// <summary>B.create_time 订单创建时间(接口为秒级时间戳,由 UnixDateTimeConverter 转换为 DateTime)</summary>
|
||||
/// <summary>
|
||||
/// 下单时间(秒级时间戳)
|
||||
/// </summary>
|
||||
[JsonProperty("create_time")]
|
||||
[JsonConverter(typeof(UnixDateTimeConverter))]
|
||||
public DateTime CreateTime { get; set; }
|
||||
|
||||
/// <summary>B.order_id 订单号</summary>
|
||||
/// <summary>
|
||||
/// 订单号
|
||||
/// </summary>
|
||||
[JsonProperty("order_id")]
|
||||
public string OrderId { get; set; }
|
||||
|
||||
/// <summary>B.status 订单状态:10-待付款;20-待发货;21-部分发货;30-待收货;100-完成;200-全部商品售后之后订单取消;250-未付款用户主动取消或超时未付款订单自动取消</summary>
|
||||
/// <summary>
|
||||
/// 订单状态
|
||||
/// </summary>
|
||||
[JsonProperty("status")]
|
||||
public int Status { get; set; }
|
||||
|
||||
/// <summary>B.product_price 商品总价(分)</summary>
|
||||
/// <summary>
|
||||
/// 商品总价(分)
|
||||
/// </summary>
|
||||
[JsonProperty("product_price")]
|
||||
public int ProductPrice { get; set; }
|
||||
|
||||
/// <summary>B.order_price 订单金额(分),计算公式:order_price = original_order_price - discounted_price - deduction_price - change_down_price</summary>
|
||||
/// <summary>
|
||||
/// 订单金额(分)
|
||||
/// </summary>
|
||||
[JsonProperty("order_price")]
|
||||
public int OrderPrice { get; set; }
|
||||
|
||||
/// <summary>B.purchase_price 采购价(分)</summary>
|
||||
/// <summary>
|
||||
/// 采购价(分)
|
||||
/// </summary>
|
||||
[JsonProperty("purchase_price")]
|
||||
public int PurchasePrice { get; set; }
|
||||
|
||||
/// <summary>B.freight 运费(分)</summary>
|
||||
/// <summary>
|
||||
/// 运费(分)
|
||||
/// </summary>
|
||||
[JsonProperty("freight")]
|
||||
public int Freight { get; set; }
|
||||
|
||||
/// <summary>B.predict_commission_fee 平台服务费(微信小店:预计技术服务费)(分)</summary>
|
||||
/// <summary>
|
||||
/// 平台服务费(预计技术服务费,分)
|
||||
/// </summary>
|
||||
[JsonProperty("predict_commission_fee")]
|
||||
public int PredictCommissionFee { get; set; }
|
||||
|
||||
/// <summary>B.commission_amount 达人佣金(微信小店:分账金额)(分)</summary>
|
||||
/// <summary>
|
||||
/// 达人佣金(分)
|
||||
/// </summary>
|
||||
[JsonProperty("commission_amount")]
|
||||
public int CommissionAmount { get; set; }
|
||||
|
||||
/// <summary>B.discounted_price 优惠券优惠金额(分)</summary>
|
||||
/// <summary>
|
||||
/// 优惠金额(分)
|
||||
/// </summary>
|
||||
[JsonProperty("discounted_price")]
|
||||
public int DiscountedPrice { get; set; }
|
||||
|
||||
/// <summary>B.profit 利润(分),计算公式:实付金额 - 采购价 - 平台服务费 - 达人佣金(采购价、达人佣金为空值则按 0)</summary>
|
||||
/// <summary>
|
||||
/// 利润(分)
|
||||
/// </summary>
|
||||
[JsonProperty("profit")]
|
||||
public int Profit { get; set; }
|
||||
|
||||
/// <summary>B.purchase_status 采购状态:0-未采购;1-采购(默认值 0,推送仓库时修改为 1)</summary>
|
||||
/// <summary>
|
||||
/// 采购状态
|
||||
/// </summary>
|
||||
[JsonProperty("purchase_status")]
|
||||
public int PurchaseStatus { get; set; }
|
||||
|
||||
/// <summary>c.refund_status 退款状态(4/11 视为已退款,与 RevenueStatisticsAggregator.IsRefunded 保持一致)</summary>
|
||||
/// <summary>
|
||||
/// 外采金额(分),out_amount为空或0时从中转仓订单表补全
|
||||
/// </summary>
|
||||
[JsonProperty("out_amount")]
|
||||
public int OutAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 售后状态(订单明细 refund_status)
|
||||
/// </summary>
|
||||
[JsonProperty("refund_status")]
|
||||
public int RefundStatus { get; set; }
|
||||
|
||||
/// <summary>店铺名称(由 app_key 关联店铺表得到,展示用,非接口原始字段)</summary>
|
||||
[JsonProperty("shop_name")]
|
||||
public string ShopName { get; set; }
|
||||
/// <summary>
|
||||
/// 售后状态(退款单 status)
|
||||
/// </summary>
|
||||
[JsonProperty("refund_status_string")]
|
||||
public string RefundStatusString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 售后退款金额(分)
|
||||
/// </summary>
|
||||
[JsonProperty("refund_amount")]
|
||||
public int RefundAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否外采:0=未从中转仓订单表查到,1=查到
|
||||
/// </summary>
|
||||
[JsonProperty("external_procurement")]
|
||||
public int ExternalProcurement { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -98,7 +146,6 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Models
|
||||
{
|
||||
/// <summary>销售额(最终有效销售额)</summary>
|
||||
public decimal SalesAmount { get; set; }
|
||||
|
||||
/// <summary>有效订单数(已扣除退款订单)</summary>
|
||||
public int ValidOrderCount { get; set; }
|
||||
|
||||
@@ -111,14 +158,20 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Models
|
||||
/// <summary>商品销量(有效销量)</summary>
|
||||
public int ProductSalesVolume { get; set; }
|
||||
|
||||
/// <summary>客户数(成交客户)</summary>
|
||||
public int CustomerCount { get; set; }
|
||||
/// <summary>平台服务费总额(元)</summary>
|
||||
public decimal PlatformFeeTotal { get; set; }
|
||||
|
||||
/// <summary>达人佣金(元)</summary>
|
||||
public decimal Commission { get; set; }
|
||||
|
||||
/// <summary>客单价(平均每单金额)</summary>
|
||||
public decimal AvgOrderAmount { get; set; }
|
||||
|
||||
/// <summary>退款率(退款订单占比,百分数,如 2.31 表示 2.31%)</summary>
|
||||
public decimal RefundRate { get; set; }
|
||||
|
||||
/// <summary>平均利润率(利润 / 采购金额,百分数,如 12.34 表示 12.34%)</summary>
|
||||
public decimal AverageProfitRate { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -174,11 +227,17 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Models
|
||||
/// <summary>退款金额(元)</summary>
|
||||
public decimal RefundAmount { get; set; }
|
||||
|
||||
/// <summary>客户数</summary>
|
||||
public int CustomerCount { get; set; }
|
||||
/// <summary>平台服务费总额(元)</summary>
|
||||
public decimal PlatformFeeTotal { get; set; }
|
||||
|
||||
/// <summary>达人佣金(元)</summary>
|
||||
public decimal Commission { get; set; }
|
||||
|
||||
/// <summary>客单价(元)</summary>
|
||||
public decimal AvgOrderAmount { get; set; }
|
||||
|
||||
/// <summary>平均利润率(利润 / 采购金额,百分数,如 12.34 表示 12.34%)</summary>
|
||||
public decimal AverageProfitRate { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -220,6 +279,15 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Models
|
||||
/// <summary>退款金额(元)</summary>
|
||||
public decimal RefundAmount { get; set; }
|
||||
|
||||
/// <summary>平台服务费总额(元)</summary>
|
||||
public decimal PlatformFeeTotal { get; set; }
|
||||
|
||||
/// <summary>达人佣金(元)</summary>
|
||||
public decimal Commission { get; set; }
|
||||
|
||||
/// <summary>平均利润率(利润 / 采购金额,百分数,如 12.34 表示 12.34%)</summary>
|
||||
public decimal AverageProfitRate { get; set; }
|
||||
|
||||
/// <summary>客户数</summary>
|
||||
public int CustomerCount { get; set; }
|
||||
|
||||
@@ -234,7 +302,7 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Models
|
||||
{
|
||||
/// <summary>店铺 app_key(A.app_key)</summary>
|
||||
public string AppKey { get; set; }
|
||||
|
||||
public string ShopId { get; set; }
|
||||
/// <summary>店铺名称</summary>
|
||||
public string ShopName { get; set; }
|
||||
}
|
||||
@@ -299,4 +367,4 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Models
|
||||
writer.WriteValue(milliseconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
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:stat="clr-namespace:_29WxOrderWpf.Ctrls.Statistics"
|
||||
xmlns:vm="clr-namespace:_29WxOrderWpf.Ctrls.Statistics.ViewModels"
|
||||
d:Background="#F5F7FB"
|
||||
d:DataContext="{d:DesignInstance Type=vm:PeriodRevenueStatisticsViewModel}"
|
||||
@@ -13,11 +14,6 @@
|
||||
<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" />
|
||||
@@ -50,219 +46,160 @@
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Background="#F5F7FB">
|
||||
<Grid Margin="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="12">
|
||||
|
||||
<!-- ============ 查询条件工具栏 ============ -->
|
||||
<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>
|
||||
<!-- ============ 查询条件工具栏(共享 UserControl) ============ -->
|
||||
<stat:RevenueStatisticsSearchBar />
|
||||
|
||||
<!-- ============ 周期×店铺统计表格 ============ -->
|
||||
<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>
|
||||
<!-- ============ 汇总卡片(共享 UserControl) ============ -->
|
||||
<stat:RevenueSummaryCards />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Margin="0,0,0,10"
|
||||
Style="{StaticResource PanelTitleStyle}"
|
||||
Text="{Binding PageTitle}" />
|
||||
<!-- ============ 周期统计表格 ============ -->
|
||||
<Border
|
||||
Margin="6,8,6,12"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<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>
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Margin="0,0,0,10"
|
||||
Style="{StaticResource PanelTitleStyle}"
|
||||
Text="{Binding PageTitle}" />
|
||||
|
||||
<!-- ============ 底部翻页 ============ -->
|
||||
<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>
|
||||
<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="Disabled"
|
||||
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 PlatformFeeTotal, StringFormat={}¥{0:N2}}"
|
||||
ElementStyle="{StaticResource CenterCellTextStyle}"
|
||||
Header="平台服务费总额" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding Commission, StringFormat={}¥{0:N2}}"
|
||||
ElementStyle="{StaticResource CenterCellTextStyle}"
|
||||
Header="达人佣金" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding AverageProfitRate, StringFormat={}{0:0.00}%}"
|
||||
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>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- ============ 加载遮罩 ============ -->
|
||||
<Border Background="#66FFFFFF" Visibility="{Binding IsLoading, Converter={StaticResource boolToVisibilityConverter}}" d:Visibility="Collapsed">
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
<UserControl
|
||||
x:Class="_29WxOrderWpf.Ctrls.Statistics.RevenueStatisticsSearchBar"
|
||||
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:DataContext="{d:DesignInstance Type=vm:ShopRevenueStatisticsViewModel,IsDesignTimeCreatable=True}"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<!-- 日期只允许从日历选择,禁止手工输入 -->
|
||||
<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>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Border
|
||||
Margin="6,6,6,0"
|
||||
Padding="14,10"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<WrapPanel>
|
||||
<TextBlock
|
||||
Margin="6,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Foreground="#666666"
|
||||
Text="时间" />
|
||||
<ComboBox IsEnabled="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 IsEnabled="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>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace _29WxOrderWpf.Ctrls.Statistics
|
||||
{
|
||||
/// <summary>
|
||||
/// 营收统计 - 查询条件工具栏(时间范围 + 日期区间 + 店铺 + 查询/重置)。
|
||||
/// 通过继承父级 DataContext 绑定,供综合统计 / 天周月周期统计共用。
|
||||
/// </summary>
|
||||
public partial class RevenueStatisticsSearchBar : UserControl
|
||||
{
|
||||
public RevenueStatisticsSearchBar()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
<UserControl
|
||||
x:Class="_29WxOrderWpf.Ctrls.Statistics.RevenueSummaryCards"
|
||||
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:DataContext="{d:DesignInstance Type=vm:ShopRevenueStatisticsViewModel,IsDesignTimeCreatable=True}"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<!-- 卡片样式(白底、圆角、阴影) -->
|
||||
<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>
|
||||
</UserControl.Resources>
|
||||
|
||||
<!-- 汇总卡片(2 行 x 5 列) -->
|
||||
<Grid Margin="3,8,3,0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 销售额 -->
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontSize="14"
|
||||
Foreground="#888888"
|
||||
Text="销售额" />
|
||||
<TextBlock
|
||||
Margin="0,12,0,0"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
Foreground="#333333"
|
||||
Text="{Binding Summary.SalesAmount, StringFormat={}¥{0:N2}}" />
|
||||
<TextBlock
|
||||
Margin="0,10,0,0"
|
||||
FontSize="12"
|
||||
Foreground="#999999"
|
||||
Text="最终有效销售额" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 有效订单 -->
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontSize="14"
|
||||
Foreground="#888888"
|
||||
Text="有效订单" />
|
||||
<TextBlock
|
||||
Margin="0,12,0,0"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
Foreground="#333333"
|
||||
Text="{Binding Summary.ValidOrderCount, StringFormat={}{0:N0}}" />
|
||||
<TextBlock
|
||||
Margin="0,10,0,0"
|
||||
FontSize="12"
|
||||
Foreground="#999999"
|
||||
Text="已扣除退款订单" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 利润 -->
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontSize="14"
|
||||
Foreground="#888888"
|
||||
Text="利润" />
|
||||
<TextBlock
|
||||
Margin="0,12,0,0"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
Foreground="#333333"
|
||||
Text="{Binding Summary.Profit, StringFormat={}¥{0:N2}}" />
|
||||
<TextBlock
|
||||
Margin="0,10,0,0"
|
||||
FontSize="12"
|
||||
Foreground="#999999"
|
||||
Text="实际利润" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 退款金额 -->
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
Grid.Column="3"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontSize="14"
|
||||
Foreground="#888888"
|
||||
Text="退款金额" />
|
||||
<TextBlock
|
||||
Margin="0,12,0,0"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
Foreground="#333333"
|
||||
Text="{Binding Summary.RefundAmount, StringFormat={}¥{0:N2}}" />
|
||||
<TextBlock
|
||||
Margin="0,10,0,0"
|
||||
FontSize="12"
|
||||
Foreground="#999999"
|
||||
Text="统计期间退款" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 商品销量 -->
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
Grid.Column="4"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontSize="14"
|
||||
Foreground="#888888"
|
||||
Text="商品销量" />
|
||||
<TextBlock
|
||||
Margin="0,12,0,0"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
Foreground="#333333"
|
||||
Text="{Binding Summary.ProductSalesVolume, StringFormat={}{0:N0}}" />
|
||||
<TextBlock
|
||||
Margin="0,10,0,0"
|
||||
FontSize="12"
|
||||
Foreground="#999999"
|
||||
Text="有效销量" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 平台服务费总额 -->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontSize="14"
|
||||
Foreground="#888888"
|
||||
Text="平台服务费总额" />
|
||||
<TextBlock
|
||||
Margin="0,12,0,0"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
Foreground="#333333"
|
||||
Text="{Binding Summary.PlatformFeeTotal, StringFormat={}¥{0:N2}}" />
|
||||
<TextBlock
|
||||
Margin="0,10,0,0"
|
||||
FontSize="12"
|
||||
Foreground="#999999"
|
||||
Text="预计技术服务费" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 达人佣金 -->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontSize="14"
|
||||
Foreground="#888888"
|
||||
Text="达人佣金" />
|
||||
<TextBlock
|
||||
Margin="0,12,0,0"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
Foreground="#333333"
|
||||
Text="{Binding Summary.Commission, StringFormat={}¥{0:N2}}" />
|
||||
<TextBlock
|
||||
Margin="0,10,0,0"
|
||||
FontSize="12"
|
||||
Foreground="#999999"
|
||||
Text="佣金支出" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 客单价 -->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontSize="14"
|
||||
Foreground="#888888"
|
||||
Text="客单价" />
|
||||
<TextBlock
|
||||
Margin="0,12,0,0"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
Foreground="#333333"
|
||||
Text="{Binding Summary.AvgOrderAmount, StringFormat={}¥{0:N2}}" />
|
||||
<TextBlock
|
||||
Margin="0,10,0,0"
|
||||
FontSize="12"
|
||||
Foreground="#999999"
|
||||
Text="平均每单金额" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 退款率 -->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Grid.Column="3"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontSize="14"
|
||||
Foreground="#888888"
|
||||
Text="退款率" />
|
||||
<TextBlock
|
||||
Margin="0,12,0,0"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
Foreground="#333333"
|
||||
Text="{Binding Summary.RefundRate, StringFormat={}{0:0.00}%}" />
|
||||
<TextBlock
|
||||
Margin="0,10,0,0"
|
||||
FontSize="12"
|
||||
Foreground="#999999"
|
||||
Text="退款订单占比" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 平均利润率 -->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Grid.Column="4"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontSize="14"
|
||||
Foreground="#888888"
|
||||
Text="平均利润率" />
|
||||
<TextBlock
|
||||
Margin="0,12,0,0"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
Foreground="#333333"
|
||||
Text="{Binding Summary.AverageProfitRate, StringFormat={}{0:0.00}%}" />
|
||||
<TextBlock
|
||||
Margin="0,10,0,0"
|
||||
FontSize="12"
|
||||
Foreground="#999999"
|
||||
Text="利润 / 采购金额" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace _29WxOrderWpf.Ctrls.Statistics
|
||||
{
|
||||
/// <summary>
|
||||
/// 营收统计 - 顶部汇总卡片(2 行 x 5 列)。
|
||||
/// 通过继承父级 DataContext 绑定 Summary(ShopRevenueSummary),
|
||||
/// 供综合统计 / 天周月周期统计共用。
|
||||
/// </summary>
|
||||
public partial class RevenueSummaryCards : UserControl
|
||||
{
|
||||
public RevenueSummaryCards()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,9 +108,12 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Services
|
||||
var valid = list.Where(IsValidOrder).ToList();
|
||||
var refunded = list.Where(IsRefunded).ToList();
|
||||
|
||||
decimal salesAmount = valid.Sum(r => (decimal)r.OrderPrice) / 100m;
|
||||
decimal profit = valid.Sum(r => (decimal)r.Profit) / 100m;
|
||||
decimal refundAmount = refunded.Sum(r => (decimal)r.OrderPrice) / 100m;
|
||||
decimal salesAmount = valid.Sum(r => (decimal)(r.OrderPrice)) / 100m;
|
||||
decimal profit = valid.Sum(r => (decimal)(r.Profit )) / 100m;
|
||||
decimal refundAmount = refunded.Sum(r => (decimal)(r.OrderPrice)) / 100m;
|
||||
decimal platformFeeTotal = valid.Sum(r => (decimal)(r.PredictCommissionFee)) / 100m;
|
||||
decimal commission = valid.Sum(r => (decimal)(r.CommissionAmount)) / 100m;
|
||||
decimal purchaseAmount = valid.Sum(r => (decimal)(r.PurchasePrice)) / 100m;
|
||||
int validOrderCount = valid.Select(r => r.OrderId).Distinct().Count();
|
||||
// 总订单数(退款率分母)= 已付款订单 + 已退款订单(含售后取消 200),不含未付款的 10/250
|
||||
int totalOrderCount = list.Where(r => IsPaidOrder(r) || IsRefunded(r)).Select(r => r.OrderId).Distinct().Count();
|
||||
@@ -123,9 +126,11 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Services
|
||||
Profit = profit,
|
||||
RefundAmount = refundAmount,
|
||||
ProductSalesVolume = valid.Count,
|
||||
CustomerCount = validOrderCount,
|
||||
PlatformFeeTotal = platformFeeTotal,
|
||||
Commission = commission,
|
||||
AvgOrderAmount = validOrderCount == 0 ? 0m : salesAmount / validOrderCount,
|
||||
RefundRate = totalOrderCount == 0 ? 0m : (decimal)refundOrderCount * 100m / totalOrderCount
|
||||
RefundRate = totalOrderCount == 0 ? 0m : (decimal)refundOrderCount * 100m / totalOrderCount,
|
||||
AverageProfitRate = purchaseAmount == 0m ? 0m : profit / purchaseAmount * 100m
|
||||
};
|
||||
}
|
||||
|
||||
@@ -140,7 +145,7 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Services
|
||||
g => new RevenueTrendPoint
|
||||
{
|
||||
Date = g.Key,
|
||||
SalesAmount = g.Sum(r => (decimal)r.OrderPrice) / 100m,
|
||||
SalesAmount = g.Sum(r => (decimal)(r.OrderPrice)) / 100m,
|
||||
OrderCount = g.Select(r => r.OrderId).Distinct().Count()
|
||||
});
|
||||
|
||||
@@ -159,15 +164,15 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Services
|
||||
public static List<ShopSalesProportionItem> GetShopSalesProportion(IEnumerable<ShopRevenueStatisticsRow> rows)
|
||||
{
|
||||
var list = (rows ?? new List<ShopRevenueStatisticsRow>()).Where(IsValidOrder).ToList();
|
||||
decimal total = list.Sum(r => (decimal)r.OrderPrice) / 100m;
|
||||
decimal total = list.Sum(r => (decimal)(r.OrderPrice )) / 100m;
|
||||
|
||||
return list
|
||||
.GroupBy(r => r.ShopName)
|
||||
.Select(g => new ShopSalesProportionItem
|
||||
{
|
||||
ShopName = string.IsNullOrEmpty(g.Key) ? "未知店铺" : g.Key,
|
||||
SalesAmount = g.Sum(r => (decimal)r.OrderPrice) / 100m,
|
||||
Percentage = total == 0m ? 0d : (double)(g.Sum(r => (decimal)r.OrderPrice) / 100m / total * 100m)
|
||||
SalesAmount = g.Sum(r => (decimal)(r.OrderPrice )) / 100m,
|
||||
Percentage = total == 0m ? 0d : (double)(g.Sum(r => (decimal)(r.OrderPrice)) / 100m / total * 100m)
|
||||
})
|
||||
.OrderByDescending(i => i.SalesAmount)
|
||||
.ToList();
|
||||
@@ -184,17 +189,21 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Services
|
||||
{
|
||||
var valid = g.Where(IsValidOrder).ToList();
|
||||
var refunded = g.Where(IsRefunded).ToList();
|
||||
decimal salesAmount = valid.Sum(r => (decimal)r.OrderPrice) / 100m;
|
||||
decimal salesAmount = valid.Sum(r => (decimal)(r.OrderPrice)) / 100m;
|
||||
int orderCount = valid.Select(r => r.OrderId).Distinct().Count();
|
||||
decimal profit = valid.Sum(r => (decimal)(r.Profit)) / 100m;
|
||||
decimal purchaseAmount = valid.Sum(r => (decimal)(r.PurchasePrice)) / 100m;
|
||||
return new ShopStatisticsItem
|
||||
{
|
||||
ShopName = string.IsNullOrEmpty(g.Key) ? "未知店铺" : g.Key,
|
||||
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
|
||||
Profit = profit,
|
||||
RefundAmount = refunded.Sum(r => (decimal)(r.OrderPrice)) / 100m,
|
||||
PlatformFeeTotal = valid.Sum(r => (decimal)(r.PredictCommissionFee)) / 100m,
|
||||
Commission = valid.Sum(r => (decimal)(r.CommissionAmount)) / 100m,
|
||||
AvgOrderAmount = orderCount == 0 ? 0m : salesAmount / orderCount,
|
||||
AverageProfitRate = purchaseAmount == 0m ? 0m : profit / purchaseAmount * 100m
|
||||
};
|
||||
})
|
||||
.OrderByDescending(i => i.SalesAmount)
|
||||
@@ -215,7 +224,9 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Services
|
||||
{
|
||||
var valid = g.Where(IsValidOrder).ToList();
|
||||
var refunded = g.Where(IsRefunded).ToList();
|
||||
decimal salesAmount = valid.Sum(r => (decimal)r.OrderPrice) / 100m;
|
||||
decimal salesAmount = valid.Sum(r => (decimal)(r.OrderPrice)) / 100m;
|
||||
decimal profit = valid.Sum(r => (decimal)(r.Profit)) / 100m;
|
||||
decimal purchaseAmount = valid.Sum(r => (decimal)(r.PurchasePrice)) / 100m;
|
||||
int orderCount = valid.Select(r => r.OrderId).Distinct().Count();
|
||||
return new ShopPeriodStatisticsItem
|
||||
{
|
||||
@@ -223,8 +234,11 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Services
|
||||
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,
|
||||
Profit = profit,
|
||||
RefundAmount = refunded.Sum(r => (decimal)(r.OrderPrice)) / 100m,
|
||||
PlatformFeeTotal = valid.Sum(r => (decimal)(r.PredictCommissionFee)) / 100m,
|
||||
Commission = valid.Sum(r => (decimal)(r.CommissionAmount)) / 100m,
|
||||
AverageProfitRate = purchaseAmount == 0m ? 0m : profit / purchaseAmount * 100m,
|
||||
CustomerCount = orderCount,
|
||||
AvgOrderAmount = orderCount == 0 ? 0m : salesAmount / orderCount
|
||||
};
|
||||
@@ -245,6 +259,9 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Services
|
||||
SalesAmount = 0m,
|
||||
Profit = 0m,
|
||||
RefundAmount = 0m,
|
||||
PlatformFeeTotal = 0m,
|
||||
Commission = 0m,
|
||||
AverageProfitRate = 0m,
|
||||
CustomerCount = 0,
|
||||
AvgOrderAmount = 0m
|
||||
});
|
||||
@@ -310,19 +327,22 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Services
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 默认实现:返回模拟数据,用于页面演示/联调。
|
||||
/// 接入真实接口时,新建实现类并注入即可(见 IShopRevenueStatisticsService 注释)。
|
||||
/// 默认实现:请求真实接口(shop_revenue_statistics/get_shop_order_amount)。
|
||||
/// 接入其他数据源时,新建实现类并注入即可(见 IShopRevenueStatisticsService 注释)。
|
||||
/// </summary>
|
||||
public class ShopRevenueStatisticsService : IShopRevenueStatisticsService
|
||||
{
|
||||
/// <summary>缓存有效期:订单/退款数据会持续变化,超过该时长后缓存自动失效并重新请求。</summary>
|
||||
private static readonly TimeSpan CacheDuration = TimeSpan.FromSeconds(60);
|
||||
|
||||
/// <summary>
|
||||
/// 明细数据缓存。
|
||||
/// key = 店铺id|yyyyMMdd(店铺 × 天),value = 该店铺当天的明细行。
|
||||
/// 查询按“店铺 × 日期”命中;完全未命中时所有店铺 + 整个时间范围一次请求,
|
||||
/// 部分命中时只请求缺失的店铺/日期段。
|
||||
/// 明细数据缓存(按天缓存,纯时间维度,不含店铺)。
|
||||
/// key = 日期(yyyyMMdd),value = 该天全部店铺的明细 + 缓存时间。
|
||||
/// 查询时按天命中:未命中的天合并成连续段一次请求,返回后按天拆分写入;
|
||||
/// 前台选某一个或某多个店铺时,按所选店铺(ShopName)过滤,复用同一批时间数据。
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, List<ShopRevenueStatisticsRow>> _rowCache =
|
||||
new Dictionary<string, List<ShopRevenueStatisticsRow>>();
|
||||
private readonly Dictionary<string, CacheEntry> _rowCache =
|
||||
new Dictionary<string, CacheEntry>();
|
||||
private readonly object _cacheLock = new object();
|
||||
|
||||
private sealed class DateRange
|
||||
@@ -331,91 +351,161 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Services
|
||||
public DateTime End;
|
||||
}
|
||||
|
||||
private static string CacheKey(string shopId, DateTime day)
|
||||
private sealed class CacheEntry
|
||||
{
|
||||
return shopId + "|" + day.ToString("yyyyMMdd");
|
||||
public List<ShopRevenueStatisticsRow> Rows;
|
||||
public DateTime CachedAt;
|
||||
}
|
||||
|
||||
private static string CacheKey(DateTime day)
|
||||
{
|
||||
return day.Date.ToString("yyyyMMdd");
|
||||
}
|
||||
|
||||
/// <summary>枚举 startDate ~ endDate 之间的每一天(含首尾)</summary>
|
||||
private static IEnumerable<DateTime> EnumerateDays(DateTime startDate, DateTime endDate)
|
||||
{
|
||||
for (var d = startDate.Date; d <= endDate.Date; d = d.AddDays(1))
|
||||
yield return d;
|
||||
}
|
||||
|
||||
/// <summary>该天缓存是否有效(存在且未过期);调用方需持有 _cacheLock</summary>
|
||||
private bool IsValidLocked(DateTime day)
|
||||
{
|
||||
return _rowCache.TryGetValue(CacheKey(day), out var entry)
|
||||
&& DateTime.Now - entry.CachedAt <= CacheDuration;
|
||||
}
|
||||
|
||||
/// <summary>把缺失日期合并为连续日期段(每段一次请求)</summary>
|
||||
private static List<DateRange> BuildRuns(List<DateTime> missingDays)
|
||||
{
|
||||
var sorted = missingDays.OrderBy(d => d).ToList();
|
||||
var runs = new List<DateRange>();
|
||||
int i = 0;
|
||||
while (i < sorted.Count)
|
||||
{
|
||||
var start = sorted[i];
|
||||
var end = start;
|
||||
i++;
|
||||
while (i < sorted.Count && sorted[i] == end.AddDays(1))
|
||||
{
|
||||
end = sorted[i];
|
||||
i++;
|
||||
}
|
||||
runs.Add(new DateRange { Start = start, End = end });
|
||||
}
|
||||
return runs;
|
||||
}
|
||||
|
||||
public async Task<List<ShopRevenueStatisticsRow>> QueryRowsAsync(ShopRevenueStatisticsQuery query)
|
||||
{
|
||||
// 1. 展开本次查询涉及的店铺(ShopKey 为空 = 全部店铺)
|
||||
List<string> shopIds;
|
||||
if (string.IsNullOrEmpty(query.ShopKey))
|
||||
shopIds = (await GetShopOptionsAsync()).Select(a => a.AppKey).ToList();
|
||||
else
|
||||
shopIds = new List<string> { query.ShopKey };
|
||||
if (shopIds.Count == 0)
|
||||
return new List<ShopRevenueStatisticsRow>();
|
||||
|
||||
var startDay = query.StartDate.Date;
|
||||
var endDay = query.EndDate.Date;
|
||||
int totalDays = (int)(endDay - startDay).Days + 1;
|
||||
if (startDay > endDay)
|
||||
{
|
||||
var tmp = startDay;
|
||||
startDay = endDay;
|
||||
endDay = tmp;
|
||||
}
|
||||
|
||||
// 2. 命中检测:按 店铺 × 天 找出缺失项
|
||||
var missingByShop = new Dictionary<string, List<DateTime>>();
|
||||
int missingCount = 0;
|
||||
// 1. 找出未命中或已过期的天
|
||||
List<DateTime> missingDays;
|
||||
lock (_cacheLock)
|
||||
{
|
||||
foreach (var shopId in shopIds)
|
||||
missingDays = EnumerateDays(startDay, endDay)
|
||||
.Where(d => !IsValidLocked(d))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// 2. 有缺失:按连续段请求整个时间范围的全部店铺明细,并按天写入缓存
|
||||
if (missingDays.Count > 0)
|
||||
{
|
||||
var shopIds = GetShopOptions().Select(a => a.ShopId).ToList();
|
||||
if (shopIds.Count == 0)
|
||||
return new List<ShopRevenueStatisticsRow>();
|
||||
|
||||
foreach (var run in BuildRuns(missingDays))
|
||||
{
|
||||
var missingDays = new List<DateTime>();
|
||||
for (var d = startDay; d <= endDay; d = d.AddDays(1))
|
||||
{
|
||||
if (!_rowCache.ContainsKey(CacheKey(shopId, d)))
|
||||
missingDays.Add(d);
|
||||
}
|
||||
missingCount += missingDays.Count;
|
||||
missingByShop[shopId] = missingDays;
|
||||
var rows = await RequestRowsAsync(shopIds, run.Start, run.End);
|
||||
CacheResponse(run.Start, run.End, rows);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 全部命中:直接从缓存拼装返回
|
||||
if (missingCount == 0)
|
||||
return GetCachedRows(shopIds, startDay, endDay);
|
||||
|
||||
// 4. 完全未命中:所有店铺 + 整个时间范围一次请求
|
||||
if (missingCount == shopIds.Count * totalDays)
|
||||
{
|
||||
var allRows = await RequestRowsAsync(shopIds, startDay, endDay);
|
||||
CacheResponse(shopIds, startDay, endDay, allRows, null);
|
||||
var cached = GetCachedRows(shopIds, startDay, endDay);
|
||||
// 若接口行内未返回 app_key 导致无法入库,则直接返回本次请求结果
|
||||
return cached.Count > 0 ? cached : allRows;
|
||||
}
|
||||
|
||||
// 5. 部分命中:只请求未命中的(店铺 × 连续缺失日期段)
|
||||
foreach (var pair in missingByShop)
|
||||
{
|
||||
if (pair.Value.Count == 0)
|
||||
continue;
|
||||
foreach (var run in BuildRuns(pair.Value))
|
||||
{
|
||||
var rows = await RequestRowsAsync(new List<string> { pair.Key }, run.Start, run.End);
|
||||
CacheResponse(new List<string> { pair.Key }, run.Start, run.End, rows, pair.Key);
|
||||
}
|
||||
}
|
||||
|
||||
return GetCachedRows(shopIds, startDay, endDay);
|
||||
// 3. 按天从缓存拼装完整范围,再按所选店铺过滤(ShopKey 为空 = 全部店铺)
|
||||
return FilterByShop(GetCachedRows(startDay, endDay), query.ShopKey);
|
||||
}
|
||||
|
||||
/// <summary>按 店铺 × 日期 从缓存拼装结果</summary>
|
||||
private List<ShopRevenueStatisticsRow> GetCachedRows(List<string> shopIds, DateTime startDate, DateTime endDate)
|
||||
/// <summary>
|
||||
/// 把一次请求的结果按天写入缓存。
|
||||
/// 本次请求覆盖的每一天都以返回数据为准整体替换(无数据的天记为空的已缓存,
|
||||
/// 避免“该天无数据”被反复判定为未命中而重复请求)。
|
||||
/// </summary>
|
||||
private void CacheResponse(DateTime startDate, DateTime endDate, List<ShopRevenueStatisticsRow> rows)
|
||||
{
|
||||
if (rows == null)
|
||||
rows = new List<ShopRevenueStatisticsRow>();
|
||||
|
||||
lock (_cacheLock)
|
||||
{
|
||||
RemoveExpiredEntriesLocked();
|
||||
|
||||
var byDay = rows
|
||||
.Where(r => r.CreateTime.Date >= startDate.Date && r.CreateTime.Date <= endDate.Date)
|
||||
.GroupBy(r => r.CreateTime.Date)
|
||||
.ToDictionary(g => g.Key, g => g.ToList());
|
||||
|
||||
foreach (var day in EnumerateDays(startDate, endDate))
|
||||
{
|
||||
byDay.TryGetValue(day, out var dayRows);
|
||||
_rowCache[CacheKey(day)] = new CacheEntry { Rows = dayRows ?? new List<ShopRevenueStatisticsRow>(), CachedAt = DateTime.Now };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>按天从缓存拼装某时间范围的明细(返回新列表,避免外部修改污染缓存)</summary>
|
||||
private List<ShopRevenueStatisticsRow> GetCachedRows(DateTime startDate, DateTime endDate)
|
||||
{
|
||||
var result = new List<ShopRevenueStatisticsRow>();
|
||||
lock (_cacheLock)
|
||||
{
|
||||
for (var d = startDate.Date; d <= endDate.Date; d = d.AddDays(1))
|
||||
foreach (var day in EnumerateDays(startDate, endDate))
|
||||
{
|
||||
foreach (var shopId in shopIds)
|
||||
{
|
||||
if (_rowCache.TryGetValue(CacheKey(shopId, d), out var list))
|
||||
result.AddRange(list);
|
||||
}
|
||||
if (_rowCache.TryGetValue(CacheKey(day), out var entry))
|
||||
result.AddRange(entry.Rows);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>按所选店铺过滤明细:ShopKey 为空返回全部;否则按店铺名(ShopName)精确匹配。</summary>
|
||||
private static List<ShopRevenueStatisticsRow> FilterByShop(List<ShopRevenueStatisticsRow> rows, string shopKey)
|
||||
{
|
||||
if (rows == null)
|
||||
return new List<ShopRevenueStatisticsRow>();
|
||||
|
||||
if (string.IsNullOrEmpty(shopKey))
|
||||
return new List<ShopRevenueStatisticsRow>(rows);
|
||||
|
||||
var shop = GetShopOptions().FirstOrDefault(a => a.AppKey == shopKey);
|
||||
if (shop == null || string.IsNullOrEmpty(shop.ShopName))
|
||||
return new List<ShopRevenueStatisticsRow>();
|
||||
|
||||
return rows
|
||||
.Where(r => string.Equals(r.ShopName, shop.ShopName, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>清理已过期的缓存条目(调用方需持有 _cacheLock)</summary>
|
||||
private void RemoveExpiredEntriesLocked()
|
||||
{
|
||||
var expiredKeys = _rowCache
|
||||
.Where(p => DateTime.Now - p.Value.CachedAt > CacheDuration)
|
||||
.Select(p => p.Key)
|
||||
.ToList();
|
||||
foreach (var key in expiredKeys)
|
||||
_rowCache.Remove(key);
|
||||
}
|
||||
|
||||
/// <summary>请求接口一次(可传多个店铺),返回原始明细行</summary>
|
||||
private async Task<List<ShopRevenueStatisticsRow>> RequestRowsAsync(List<string> shopIds, DateTime startDate, DateTime endDate)
|
||||
{
|
||||
@@ -447,81 +537,6 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Services
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 把一次请求的结果写入缓存(按 店铺id|日期 分桶)。
|
||||
/// 除有数据的 店铺×天 外,会把本次请求覆盖但无数据的 店铺×天 也标记为已缓存(空列表),
|
||||
/// 避免“该店铺当天无数据”被反复判定为未命中而重复请求数据库。
|
||||
/// </summary>
|
||||
private void CacheResponse(List<string> shopIds, DateTime startDate, DateTime endDate, List<ShopRevenueStatisticsRow> rows, string fallbackShopId)
|
||||
{
|
||||
if (rows == null)
|
||||
rows = new List<ShopRevenueStatisticsRow>();
|
||||
|
||||
// 能否按 店铺×天 归属:
|
||||
// 单店铺请求一定可以(用请求的店铺兜底);多店铺请求要求行内带 app_key;
|
||||
// 响应为空时也视为可归属(说明覆盖的 店铺×天 都无数据)。
|
||||
bool canAttribute = shopIds.Count == 1
|
||||
|| rows.Count == 0
|
||||
|| rows.Any(r => !string.IsNullOrEmpty(r.AppKey));
|
||||
|
||||
lock (_cacheLock)
|
||||
{
|
||||
if (canAttribute)
|
||||
{
|
||||
// 覆盖的 店铺×天 全部落缓存键(无数据的天记为空的已缓存)
|
||||
for (var d = startDate.Date; d <= endDate.Date; d = d.AddDays(1))
|
||||
{
|
||||
foreach (var shopId in shopIds)
|
||||
{
|
||||
var key = CacheKey(shopId, d);
|
||||
if (!_rowCache.TryGetValue(key, out var list))
|
||||
{
|
||||
list = new List<ShopRevenueStatisticsRow>();
|
||||
_rowCache[key] = list;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 有数据的行按 店铺×天 追加
|
||||
foreach (var row in rows)
|
||||
{
|
||||
// 行内未带 app_key 时,用本次请求的店铺兜底
|
||||
var shopId = string.IsNullOrEmpty(row.AppKey) ? fallbackShopId : row.AppKey;
|
||||
if (string.IsNullOrEmpty(shopId))
|
||||
continue;
|
||||
var key = CacheKey(shopId, row.CreateTime.Date);
|
||||
if (!_rowCache.TryGetValue(key, out var list))
|
||||
{
|
||||
list = new List<ShopRevenueStatisticsRow>();
|
||||
_rowCache[key] = list;
|
||||
}
|
||||
list.Add(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>把缺失日期合并为连续日期段(每段一次请求)</summary>
|
||||
private static List<DateRange> BuildRuns(List<DateTime> missingDays)
|
||||
{
|
||||
var sorted = missingDays.OrderBy(d => d).ToList();
|
||||
var runs = new List<DateRange>();
|
||||
int i = 0;
|
||||
while (i < sorted.Count)
|
||||
{
|
||||
var start = sorted[i];
|
||||
var end = start;
|
||||
i++;
|
||||
while (i < sorted.Count && sorted[i] == end.AddDays(1))
|
||||
{
|
||||
end = sorted[i];
|
||||
i++;
|
||||
}
|
||||
runs.Add(new DateRange { Start = start, End = end });
|
||||
}
|
||||
return runs;
|
||||
}
|
||||
|
||||
public async Task<ShopRevenueSummary> GetSummaryAsync(ShopRevenueStatisticsQuery query)
|
||||
{
|
||||
var rows = await QueryRowsAsync(query);
|
||||
@@ -553,16 +568,21 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Services
|
||||
return RevenueStatisticsAggregator.GetPeriodStatistics(rows, query.StartDate, query.EndDate, periodType);
|
||||
}
|
||||
|
||||
public Task<List<ShopOption>> GetShopOptionsAsync()
|
||||
/// <summary>店铺下拉列表(同步读取,供查询与过滤使用)</summary>
|
||||
private static List<ShopOption> GetShopOptions()
|
||||
{
|
||||
var list = GlobalConfig.ShopLoginList.Select(a =>
|
||||
return GlobalConfig.ShopLoginList.Select(a =>
|
||||
new ShopOption()
|
||||
{
|
||||
AppKey = a.ShopId,
|
||||
ShopId = a.ErpShopId,
|
||||
ShopName = a.ShopAccount
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
return Task.FromResult(list);
|
||||
public Task<List<ShopOption>> GetShopOptionsAsync()
|
||||
{
|
||||
return Task.FromResult(GetShopOptions());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -130,282 +130,11 @@
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="12">
|
||||
|
||||
<!-- ============ 查询条件工具栏 ============ -->
|
||||
<Border
|
||||
Margin="6,6,6,0"
|
||||
Padding="14,10"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<WrapPanel>
|
||||
<TextBlock
|
||||
Margin="6,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Foreground="#666666"
|
||||
Text="时间" />
|
||||
<ComboBox
|
||||
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
|
||||
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>
|
||||
<!-- ============ 查询条件工具栏(共享 UserControl) ============ -->
|
||||
<stat:RevenueStatisticsSearchBar />
|
||||
|
||||
<!-- ============ 汇总卡片(2 行 x 4 列) ============ -->
|
||||
<Grid Margin="3,8,3,0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 销售额 -->
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontSize="14"
|
||||
Foreground="#888888"
|
||||
Text="销售额" />
|
||||
<TextBlock
|
||||
Margin="0,12,0,0"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
Foreground="#333333"
|
||||
Text="{Binding Summary.SalesAmount, StringFormat={}¥{0:N2}}" />
|
||||
<TextBlock
|
||||
Margin="0,10,0,0"
|
||||
FontSize="12"
|
||||
Foreground="#999999"
|
||||
Text="最终有效销售额" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 有效订单 -->
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontSize="14"
|
||||
Foreground="#888888"
|
||||
Text="有效订单" />
|
||||
<TextBlock
|
||||
Margin="0,12,0,0"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
Foreground="#333333"
|
||||
Text="{Binding Summary.ValidOrderCount, StringFormat={}{0:N0}}" />
|
||||
<TextBlock
|
||||
Margin="0,10,0,0"
|
||||
FontSize="12"
|
||||
Foreground="#999999"
|
||||
Text="已扣除退款订单" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 利润 -->
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontSize="14"
|
||||
Foreground="#888888"
|
||||
Text="利润" />
|
||||
<TextBlock
|
||||
Margin="0,12,0,0"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
Foreground="#333333"
|
||||
Text="{Binding Summary.Profit, StringFormat={}¥{0:N2}}" />
|
||||
<TextBlock
|
||||
Margin="0,10,0,0"
|
||||
FontSize="12"
|
||||
Foreground="#999999"
|
||||
Text="实际利润" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 退款金额 -->
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
Grid.Column="3"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontSize="14"
|
||||
Foreground="#888888"
|
||||
Text="退款金额" />
|
||||
<TextBlock
|
||||
Margin="0,12,0,0"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
Foreground="#333333"
|
||||
Text="{Binding Summary.RefundAmount, StringFormat={}¥{0:N2}}" />
|
||||
<TextBlock
|
||||
Margin="0,10,0,0"
|
||||
FontSize="12"
|
||||
Foreground="#999999"
|
||||
Text="统计期间退款" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 商品销量 -->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontSize="14"
|
||||
Foreground="#888888"
|
||||
Text="商品销量" />
|
||||
<TextBlock
|
||||
Margin="0,12,0,0"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
Foreground="#333333"
|
||||
Text="{Binding Summary.ProductSalesVolume, StringFormat={}{0:N0}}" />
|
||||
<TextBlock
|
||||
Margin="0,10,0,0"
|
||||
FontSize="12"
|
||||
Foreground="#999999"
|
||||
Text="有效销量" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 客户数 -->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontSize="14"
|
||||
Foreground="#888888"
|
||||
Text="客户数" />
|
||||
<TextBlock
|
||||
Margin="0,12,0,0"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
Foreground="#333333"
|
||||
Text="{Binding Summary.CustomerCount, StringFormat={}{0:N0}}" />
|
||||
<TextBlock
|
||||
Margin="0,10,0,0"
|
||||
FontSize="12"
|
||||
Foreground="#999999"
|
||||
Text="成交客户" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 客单价 -->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontSize="14"
|
||||
Foreground="#888888"
|
||||
Text="客单价" />
|
||||
<TextBlock
|
||||
Margin="0,12,0,0"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
Foreground="#333333"
|
||||
Text="{Binding Summary.AvgOrderAmount, StringFormat={}¥{0:N2}}" />
|
||||
<TextBlock
|
||||
Margin="0,10,0,0"
|
||||
FontSize="12"
|
||||
Foreground="#999999"
|
||||
Text="平均每单金额" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 退款率 -->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Grid.Column="3"
|
||||
Style="{StaticResource CardBorderStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontSize="14"
|
||||
Foreground="#888888"
|
||||
Text="退款率" />
|
||||
<TextBlock
|
||||
Margin="0,12,0,0"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
Foreground="#333333"
|
||||
Text="{Binding Summary.RefundRate, StringFormat={}{0:0.00}%}" />
|
||||
<TextBlock
|
||||
Margin="0,10,0,0"
|
||||
FontSize="12"
|
||||
Foreground="#999999"
|
||||
Text="退款订单占比" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
<!-- ============ 汇总卡片(共享 UserControl) ============ -->
|
||||
<stat:RevenueSummaryCards />
|
||||
|
||||
<!-- ============ 图表区域 ============ -->
|
||||
<Grid Margin="3,8,3,0">
|
||||
@@ -500,9 +229,19 @@
|
||||
Header="退款金额" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding CustomerCount, StringFormat={}{0:N0}}"
|
||||
Binding="{Binding PlatformFeeTotal, StringFormat={}¥{0:N2}}"
|
||||
ElementStyle="{StaticResource CenterCellTextStyle}"
|
||||
Header="客户数" />
|
||||
Header="平台服务费总额" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding Commission, StringFormat={}¥{0:N2}}"
|
||||
ElementStyle="{StaticResource CenterCellTextStyle}"
|
||||
Header="达人佣金" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding AverageProfitRate, StringFormat={}{0:0.00}%}"
|
||||
ElementStyle="{StaticResource CenterCellTextStyle}"
|
||||
Header="平均利润率" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding AvgOrderAmount, StringFormat={}¥{0:N2}}"
|
||||
@@ -551,4 +290,4 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
@@ -30,6 +30,7 @@ namespace _29WxOrderWpf.Ctrls.Statistics.ViewModels
|
||||
private readonly DelegateCommand _lastPageCommand;
|
||||
|
||||
private ShopOption _selectedShop;
|
||||
private ShopRevenueSummary _summary;
|
||||
private ObservableCollection<ShopPeriodStatisticsItem> _statistics;
|
||||
private List<ShopPeriodStatisticsItem> _allItems = new List<ShopPeriodStatisticsItem>();
|
||||
private int _pageIndex = 1;
|
||||
@@ -209,6 +210,17 @@ namespace _29WxOrderWpf.Ctrls.Statistics.ViewModels
|
||||
|
||||
#region 统计结果
|
||||
|
||||
/// <summary>汇总卡片数据</summary>
|
||||
public ShopRevenueSummary Summary
|
||||
{
|
||||
get { return _summary; }
|
||||
private set
|
||||
{
|
||||
_summary = value;
|
||||
OnPropertyChanged(nameof(Summary));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>当前页的周期×店铺统计表格数据</summary>
|
||||
public ObservableCollection<ShopPeriodStatisticsItem> Statistics
|
||||
{
|
||||
@@ -486,6 +498,7 @@ namespace _29WxOrderWpf.Ctrls.Statistics.ViewModels
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
Reference in new issue
Block a user