diff --git a/29WxOrderWpf/Common/InputDialog.xaml b/29WxOrderWpf/Common/InputDialog.xaml
new file mode 100644
index 0000000..16b8e1d
--- /dev/null
+++ b/29WxOrderWpf/Common/InputDialog.xaml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/29WxOrderWpf/Common/InputDialog.xaml.cs b/29WxOrderWpf/Common/InputDialog.xaml.cs
new file mode 100644
index 0000000..4a355e0
--- /dev/null
+++ b/29WxOrderWpf/Common/InputDialog.xaml.cs
@@ -0,0 +1,163 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+
+namespace _29WxOrderWpf.Common;
+
+public enum InputType
+{
+ Text, // 任意文本
+ Integer, // 整数
+ Decimal // 小数
+}
+///
+/// InputDialog.xaml 的交互逻辑
+///
+public partial class InputDialog : Window
+{
+ private readonly InputType _type;
+ private readonly bool _allowEmpty;
+
+ // 输入过程中允许的"部分输入"格式(如 "-"、"1." 也允许)
+ private static readonly Regex IntPartial = new(@"^-?\d*$");
+ private static readonly Regex DecPartial = new(@"^-?\d*\.?\d*$");
+
+ /// 用户输入的原始文本
+ public string ResultText { get; private set; } = "";
+
+ private InputDialog(Window? owner, string title, string prompt,
+ string defaultValue, InputType type, bool allowEmpty)
+ {
+ InitializeComponent();
+ Owner = owner ?? Application.Current.Windows
+ .OfType()
+ .FirstOrDefault(w => w.IsActive) ?? Application.Current.MainWindow;
+ if (Owner == null) WindowStartupLocation = WindowStartupLocation.CenterScreen;
+
+ Title = title;
+ PromptText.Text = prompt;
+ InputBox.Text = defaultValue;
+ _type = type;
+ _allowEmpty = allowEmpty;
+
+ // 粘贴时同样校验
+ DataObject.AddPastingHandler(InputBox, (s, e) =>
+ {
+ if (_type == InputType.Text) return;
+ if (e.DataObject.GetData(DataFormats.Text) is not string pasted ||
+ !IsPartialValid(Preview(pasted)))
+ e.CancelCommand();
+ });
+ }
+
+ private void Window_ContentRendered(object? sender, EventArgs e)
+ {
+ InputBox.Focus();
+ InputBox.SelectAll();
+ }
+
+ // 按键输入时实时限制字符
+ private void InputBox_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
+ {
+ if (_type == InputType.Text) return;
+ e.Handled = !IsPartialValid(Preview(e.Text));
+ }
+
+ // 计算输入/粘贴后的文本
+ private string Preview(string incoming)
+ {
+ var t = InputBox.Text;
+ return t.Remove(InputBox.SelectionStart, InputBox.SelectionLength)
+ .Insert(InputBox.SelectionStart, incoming);
+ }
+
+ private bool IsPartialValid(string text) => _type switch
+ {
+ InputType.Integer => IntPartial.IsMatch(text),
+ InputType.Decimal => DecPartial.IsMatch(text),
+ _ => true
+ };
+
+ private void Ok_Click(object sender, RoutedEventArgs e)
+ {
+ var text = InputBox.Text.Trim();
+
+ if (text.Length == 0 && !_allowEmpty)
+ {
+ ShowError("内容不能为空");
+ return;
+ }
+
+ if (text.Length > 0)
+ {
+ if (_type == InputType.Integer && !int.TryParse(text, NumberStyles.AllowLeadingSign,
+ CultureInfo.InvariantCulture, out _))
+ {
+ ShowError("请输入有效的整数");
+ return;
+ }
+ if (_type == InputType.Decimal && !double.TryParse(text, NumberStyles.Float,
+ CultureInfo.InvariantCulture, out _))
+ {
+ ShowError("请输入有效的数字");
+ return;
+ }
+ }
+
+ ResultText = text;
+ DialogResult = true;
+ }
+
+ private void ShowError(string msg)
+ {
+ ErrorText.Text = msg;
+ ErrorText.Visibility = Visibility.Visible;
+ InputBox.Focus();
+ InputBox.SelectAll();
+ }
+
+ // ================= 静态帮助方法 =================
+
+ /// 输入文本。取消返回 null。
+ public static string? ShowText(string prompt, string title = "输入",
+ string defaultValue = "", bool allowEmpty = false, Window? owner = null)
+ {
+ var dlg = new InputDialog(owner, title, prompt, defaultValue, InputType.Text, allowEmpty);
+ return dlg.ShowDialog() == true ? dlg.ResultText : null;
+ }
+
+ /// 输入整数。取消返回 null。
+ public static int? ShowInt(string prompt, string title = "输入",
+ int? defaultValue = null, Window? owner = null)
+ {
+ var dlg = new InputDialog(owner, title, prompt,
+ defaultValue?.ToString(CultureInfo.InvariantCulture) ?? "", InputType.Integer, false);
+ return dlg.ShowDialog() == true
+ ? int.Parse(dlg.ResultText, CultureInfo.InvariantCulture)
+ : null;
+ }
+
+ /// 输入小数。取消返回 null。
+ public static double? ShowDouble(string prompt, string title = "输入",
+ double? defaultValue = null, Window? owner = null)
+ {
+ var dlg = new InputDialog(owner, title, prompt,
+ defaultValue?.ToString(CultureInfo.InvariantCulture) ?? "", InputType.Decimal, false);
+ return dlg.ShowDialog() == true
+ ? double.Parse(dlg.ResultText, CultureInfo.InvariantCulture)
+ : null;
+ }
+}
+
diff --git a/29WxOrderWpf/Ctrls/Statistics/Models/ShopRevenueStatisticsModels.cs b/29WxOrderWpf/Ctrls/Statistics/Models/ShopRevenueStatisticsModels.cs
index 24f5bc9..88b3386 100644
--- a/29WxOrderWpf/Ctrls/Statistics/Models/ShopRevenueStatisticsModels.cs
+++ b/29WxOrderWpf/Ctrls/Statistics/Models/ShopRevenueStatisticsModels.cs
@@ -179,6 +179,16 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Models
///
public int OrderRefundAmount { get; set; }
+ ///
+ /// 利润
+ ///
+ public long ProfitAmount { get; set; }
+
+ ///
+ /// 平台佣金
+ ///
+ public long PlatformFee { get; set; }
+
///
/// 外采金额(分),即该订单下所有买手订单的购买金额之和
///
@@ -228,6 +238,9 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Models
/// 退款状态
///
public string RefundStatusText { get; set; }
+
+ public DateTime CreateTime { get; set; }
+ public string Remark { get; set; }
}
///
@@ -401,7 +414,11 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Models
Refund = 2,
/// 买手未退回金额的订单:退款订单中买手款项还没退回的(对应统计里的未退回金额口径)
- BuyerUnrefunded = 3
+ BuyerUnrefunded = 3,
+ ///
+ /// 利润订单 有效订单+退款订单利润>0
+ ///
+ Profit = 4,
}
///
diff --git a/29WxOrderWpf/Ctrls/Statistics/RevenueSummaryCards.xaml b/29WxOrderWpf/Ctrls/Statistics/RevenueSummaryCards.xaml
index 18d4ee4..6ce6145 100644
--- a/29WxOrderWpf/Ctrls/Statistics/RevenueSummaryCards.xaml
+++ b/29WxOrderWpf/Ctrls/Statistics/RevenueSummaryCards.xaml
@@ -80,16 +80,6 @@
-
@@ -113,25 +103,37 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
@@ -255,7 +257,7 @@
+ Text="售后采购金额" />
/// 买手退款表ID(,即表格当前行的 id)
Task UpdateBuyerRefundStatusAsync(long id);
+ Task UpdateBuyerAfterOrderRemark(long id, string remark);
}
///
@@ -654,5 +655,22 @@ namespace _29WxOrderWpf.Ctrls.Statistics.Services
EnsureSuccess(ret);
return ret.Data;
}
+
+ public async Task UpdateBuyerAfterOrderRemark(long id, string remark)
+ {
+ if (id == 0)
+ throw new Exception("更新失败:缺少买手订单ID");
+
+ var url = BaseClass.Build29WxObjString("buyer/UpdateBuyerAfterRemark");
+ var html = await GetHtml.PostJsonAsync(url, UserLogin.GetSignPostData(new
+ {
+ id = id,
+ remark
+ }));
+
+ var ret = Deserialize>(html);
+ EnsureSuccess(ret);
+ return ret.Data;
+ }
}
}
diff --git a/29WxOrderWpf/Ctrls/Statistics/ShowOrderDialogView.xaml b/29WxOrderWpf/Ctrls/Statistics/ShowOrderDialogView.xaml
index f667ec3..5fa53bd 100644
--- a/29WxOrderWpf/Ctrls/Statistics/ShowOrderDialogView.xaml
+++ b/29WxOrderWpf/Ctrls/Statistics/ShowOrderDialogView.xaml
@@ -149,14 +149,14 @@
Header="退款金额" />
+ Header="平台费用" />
+ Header="利润" />
+ Header="外采金额" />
+
-
+
+
+
+
+
+
+
+
diff --git a/29WxOrderWpf/Ctrls/Statistics/ShowOrderDialogView.xaml.cs b/29WxOrderWpf/Ctrls/Statistics/ShowOrderDialogView.xaml.cs
index 2a7ba5c..f6bfcf5 100644
--- a/29WxOrderWpf/Ctrls/Statistics/ShowOrderDialogView.xaml.cs
+++ b/29WxOrderWpf/Ctrls/Statistics/ShowOrderDialogView.xaml.cs
@@ -4,6 +4,7 @@ using _29WxOrderWpf.Common;
using _29WxOrderWpf.Ctrls.Statistics.Models;
using _29WxOrderWpf.Ctrls.Statistics.Services;
using _29WxOrderWpf.ToolCtrls;
+using AllBase;
using NaiBa.Util.Extension;
using System;
using System.Collections.Generic;
@@ -22,6 +23,7 @@ using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
+using Work.BaseForms;
using WPFCommon.Models;
namespace _29WxOrderWpf.Ctrls.Statistics;
@@ -36,11 +38,14 @@ public partial class ShowOrderDialogView : UserControl
public ShowOrderDialogView(List list, ShopOrderDetailType detailType, IShopRevenueStatisticsService statisticsService)
{
InitializeComponent();
- this.DataContext = new ShowOrderDialogViewModel(list, statisticsService);
+ this.DataContext = new ShowOrderDialogViewModel(list, detailType, statisticsService);
// “操作”列(退款成功按钮)只在“买手未退回金额的订单”弹窗里显示
if (detailType == ShopOrderDetailType.BuyerUnrefunded)
+ {
colUpdateRefund.Visibility = Visibility.Visible;
+ //colRemark.Visibility = Visibility.Visible;
+ }
shortcutKeyHandler = ShowOrderDialogView_PreviewKeyDown;
@@ -158,7 +163,15 @@ class ShopOrderDetailRow : BindableBaseEx
public int OrderPrice { get; set; }
public int OrderRefundAmount { get; set; }
+ ///
+ /// 利润
+ ///
+ public long ProfitAmount { get; set; }
+ ///
+ /// 平台佣金
+ ///
+ public long PlatformFee { get; set; }
public int OutAmount { get; set; }
public int UnrefundedAmount { get; set; }
@@ -185,9 +198,13 @@ class ShopOrderDetailRow : BindableBaseEx
get => GetValue();
set => SetValue(value);
}
-
+ public string Remark
+ {
+ get => GetValue();
+ set => SetValue(value);
+ }
/// 把接口返回的订单明细按买手订单展平成表格行
- public static List Flatten(IEnumerable list)
+ public static List Flatten(IEnumerable list, ShopOrderDetailType detailType)
{
var rows = new List();
if (list == null)
@@ -203,9 +220,19 @@ class ShopOrderDetailRow : BindableBaseEx
rows.Add(Create(item, null));
continue;
}
+ if (detailType == ShopOrderDetailType.Profit)
+ {
+ // 利润订单只取最后一个买手订单
+ rows.Add(Create(item, item.BuyerOrders.LastOrDefault()));
+ }
+ else
+ {
+ foreach (var buyerOrder in item.BuyerOrders)
+ {
+ rows.Add(Create(item, buyerOrder));
+ }
+ }
- foreach (var buyerOrder in item.BuyerOrders)
- rows.Add(Create(item, buyerOrder));
}
return rows;
}
@@ -219,6 +246,8 @@ class ShopOrderDetailRow : BindableBaseEx
CreateTime = item.CreateTime,
OrderPrice = item.OrderPrice,
OrderRefundAmount = item.OrderRefundAmount,
+ ProfitAmount = item.ProfitAmount,
+ PlatformFee = item.PlatformFee,
OutAmount = item.OutAmount,
UnrefundedAmount = item.UnrefundedAmount,
BuyerId = buyerOrder == null ? 0 : buyerOrder.Id,
@@ -227,6 +256,7 @@ class ShopOrderDetailRow : BindableBaseEx
BuyerOutAmount = buyerOrder == null ? 0 : buyerOrder.OutAmount,
BuyerRefundAmount = buyerOrder == null ? 0 : buyerOrder.RefundAmount,
BuyerRefundStatusText = buyerOrder == null ? null : buyerOrder.RefundStatusText,
+ Remark = buyerOrder == null ? null : buyerOrder.Remark,
};
}
}
@@ -237,10 +267,11 @@ class ShowOrderDialogViewModel : BindableBaseEx
public ObservableCollection Data { get; set; }
public ICollectionView DataView { get; private set; }
- public ShowOrderDialogViewModel(List list, IShopRevenueStatisticsService statisticsService)
+ public ShowOrderDialogViewModel(List list, ShopOrderDetailType detailType, IShopRevenueStatisticsService statisticsService)
{
- // 按买手订单展平:一笔买手订单一一行,不合并
- Data = new ObservableCollection(ShopOrderDetailRow.Flatten(list));
+ IsBuyerUnrefunded = detailType == ShopOrderDetailType.BuyerUnrefunded;
+ // 按买手订单展平:一笔买手订单一一行, 利润明细合并,只展示一条
+ Data = new ObservableCollection(ShopOrderDetailRow.Flatten(list, detailType));
DataView = CollectionViewSource.GetDefaultView(Data);
DataView.Filter = FilterRow;
_statisticsService = statisticsService;
@@ -262,6 +293,11 @@ class ShowOrderDialogViewModel : BindableBaseEx
get => GetValue();
set => SetValue(value, () => DataView.Refresh());
}
+ public bool IsBuyerUnrefunded
+ {
+ get => GetValue();
+ set => SetValue(value);
+ }
/// 当前筛选后显示的行(按表格显示顺序)
public List GetFilteredRows()
@@ -310,7 +346,13 @@ class ShowOrderDialogViewModel : BindableBaseEx
ToastHelper.Show("该行缺少买手订单ID,无法更新", type: ToastType.Warning);
return;
}
+ var result = MessageBox.Show($"确定要把订单:{row.OrderId}\n设为已退款成功状态?", "提示",
+ MessageBoxButton.YesNo, MessageBoxImage.Question);
+ if (result != MessageBoxResult.Yes)
+ {
+ return;
+ }
try
{
var ret = await _statisticsService.UpdateBuyerRefundStatusAsync(row.BuyerRefundId);
@@ -331,6 +373,35 @@ class ShowOrderDialogViewModel : BindableBaseEx
{
}
}
+ public ICommand UpdateRemarkCommand => new AsyncDelegateCommand(UpdateRemark);
+
+ private async Task UpdateRemark(ShopOrderDetailRow row)
+ {
+ string? remark = InputDialog.ShowText("请输入备注:", "备注", row.Remark);
+ if (remark == null)
+ return;
+
+ try
+ {
+ var ret = await _statisticsService.UpdateBuyerAfterOrderRemark(row.BuyerRefundId, remark);
+ if (ret)
+ {
+ row.Remark = remark;
+ ToastHelper.Show("操作成功");
+ }
+ else
+ throw new Exception("备注更改失败");
+ }
+ catch (Exception ex)
+ {
+ ToastHelper.Show("操作失败 " + ex.Message, type: ToastType.Error);
+
+ }
+ finally
+ {
+ }
+
+ }
/// 把行拼成制表符分隔的文本(第一行是表头)
private string BuildCopyText(IEnumerable rows)
@@ -338,7 +409,7 @@ class ShowOrderDialogViewModel : BindableBaseEx
var sb = new StringBuilder();
// 表头
- sb.AppendLine("店名\t订单\t创建时间\t金额\t退款金额\t外采金额\t未退回金额\t买手单号\t买手金额\t买手退款\t退款状态");
+ sb.AppendLine("店名\t订单\t创建时间\t金额\t退款金额\t平台费用\t利润\t买手单号\t外采金额\t买手退款\t退款状态");
// 数据行
foreach (var item in rows)
@@ -348,14 +419,14 @@ class ShowOrderDialogViewModel : BindableBaseEx
string createTime = item.CreateTime.ToString("yyyy-MM-dd HH:mm");
string orderPrice = (item.OrderPrice / 100m).ToString("0.00");
string refundAmount = (item.OrderRefundAmount / 100m).ToString("0.00");
- string outAmount = (item.OutAmount / 100m).ToString("0.00");
- string unrefundedAmount = (item.UnrefundedAmount / 100m).ToString("0.00");
+ string platformFee = (item.PlatformFee / 100m).ToString("0.00");
+ string profitAmount = (item.ProfitAmount / 100m).ToString("0.00");
string buyerOrderSn = CsvEscape(item.OutOrderSn);
string buyerOutAmount = (item.BuyerOutAmount / 100m).ToString("0.00");
string buyerRefundAmount = (item.BuyerRefundAmount / 100m).ToString("0.00");
string buyerRefundStatus = CsvEscape(item.BuyerRefundStatusText);
- sb.AppendLine($"{shopName}\t{orderId}\t{createTime}\t{orderPrice}\t{refundAmount}\t{outAmount}\t{unrefundedAmount}\t{buyerOrderSn}\t{buyerOutAmount}\t{buyerRefundAmount}\t{buyerRefundStatus}");
+ sb.AppendLine($"{shopName}\t{orderId}\t{createTime}\t{orderPrice}\t{refundAmount}\t{platformFee}\t{profitAmount}\t{buyerOrderSn}\t{buyerOutAmount}\t{buyerRefundAmount}\t{buyerRefundStatus}");
}
return sb.ToString();
diff --git a/29WxOrderWpf/Ctrls/Statistics/ViewModels/ShopRevenueStatisticsViewModel.cs b/29WxOrderWpf/Ctrls/Statistics/ViewModels/ShopRevenueStatisticsViewModel.cs
index 865b794..5b88df9 100644
--- a/29WxOrderWpf/Ctrls/Statistics/ViewModels/ShopRevenueStatisticsViewModel.cs
+++ b/29WxOrderWpf/Ctrls/Statistics/ViewModels/ShopRevenueStatisticsViewModel.cs
@@ -333,7 +333,6 @@ namespace _29WxOrderWpf.Ctrls.Statistics.ViewModels
{
var detailType = ParseDetailType(type);
List showList;
- // 明细接口数据量大、耗时长,期间显示页面统一的加载遮罩,避免点了没反应
IsLoading = true;
try
{
@@ -373,6 +372,8 @@ namespace _29WxOrderWpf.Ctrls.Statistics.ViewModels
return ShopOrderDetailType.Refund; //退款订单
case "BuyerRefundRemainingAmount":
return ShopOrderDetailType.BuyerUnrefunded; //买手未退回金额的订单
+ case "ProfitOrder":
+ return ShopOrderDetailType.Profit; //买手未退回金额的订单
default:
return ShopOrderDetailType.Valid; //有效订单
}