diff --git a/29WxOrderWpf/Converters/ImageUrlToBitmapImageConverter.cs b/29WxOrderWpf/Converters/ImageUrlToBitmapImageConverter.cs index dfbf7e4..2063aaf 100644 --- a/29WxOrderWpf/Converters/ImageUrlToBitmapImageConverter.cs +++ b/29WxOrderWpf/Converters/ImageUrlToBitmapImageConverter.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -14,27 +15,38 @@ namespace _29WxOrderWpf.Converters { public class ImageUrlToBitmapImageConverter : IValueConverter { + /// 缓存上限,防止大量缩略图重复下载导致内存膨胀 + private const int MaxCacheCount = 200; + + private readonly ConcurrentDictionary _imageCache = new ConcurrentDictionary(); + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is string url && Uri.IsWellFormedUriString(url, UriKind.Absolute)) { + if (_imageCache.TryGetValue(url, out BitmapImage cached)) + { + return cached; + } + try { // 直接加载绑定如果遇到有问题的网络图片会直接崩溃。 - //var bitMap = new BitmapImage(new Uri(url)); - BitmapImage bitMap = null; - //bitMap = LoadImageFromUrl(url + "?imageView2/1/w/100/format/webp|imageMogr2/auto-orient="); - bitMap = AllBase.ImageBuild.LoadBitmipImageFromUrl(url); + BitmapImage bitMap = AllBase.ImageBuild.LoadBitmipImageFromUrl(url); + if (bitMap != null) + { + if (_imageCache.Count >= MaxCacheCount) + { + _imageCache.Clear(); + } + _imageCache[url] = bitMap; + } return bitMap; - //string errStr = ""; - //var bitmapImg = GetUrlImg(url, out errStr); - //return bitmapImg; } catch { } - //return Task.Run(() => new BitmapImage(new Uri(url))); // 使用 Task.Run 来异步加载图片。 } return null; } diff --git a/29WxOrderWpf/Ctrls/OrderCtrls/OrderListCtrl.Command.cs b/29WxOrderWpf/Ctrls/OrderCtrls/OrderListCtrl.Command.cs new file mode 100644 index 0000000..6625d74 --- /dev/null +++ b/29WxOrderWpf/Ctrls/OrderCtrls/OrderListCtrl.Command.cs @@ -0,0 +1,318 @@ +using _29WxOrderWpf.BaseWindows; +using _29WxOrderWpf.Commands; +using _29WxOrderWpf.Common; +using _29WxOrderWpf.Ctrls.PDDInvoice; +using _29WxOrderWpf.Ctrls.Statistics; +using _29WxOrderWpf.Services; +using AllBase.Helper; +using AllBase.Model.DBModel; +using NaiBa.Util.Extension; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using Work._29Tools.BLL; +using static _29WxOrderWpf.Services.ShipGiftService; + +namespace _29WxOrderWpf.Ctrls.OrderCtrls; + +public partial class OrderListCtrl +{ + #region 统计 + private DialogWindow _statisticsDialog; + public ICommand ShowShopRevenueStatisticsDialogViewCommand => new DelegateCommand(ShowShopRevenueStatisticsDialogView); + + private void ShowShopRevenueStatisticsDialogView() + { + if (_statisticsDialog == null) + { + _statisticsDialog = new DialogWindow() + { + Title = "统计", + Content = new ShopRevenueStatisticsView(), + + }; + _statisticsDialog.Closed += (s, e) => _statisticsDialog = null; + } + _statisticsDialog.Show(); + } + #endregion + + #region 礼物 + public ICommand MultipleShipGiftCommand => new AsyncDelegateCommand(MultipleShipGift); + /// + /// 批量发送礼品单 + /// + /// + /// + + private async Task MultipleShipGift() + { + + try + { + var list = GlobalConfig.lstFilterOrder.Where(a => a.IsSelect).ToList(); + int count = list.Count; + if (count == 0) + { + BaseMsgWindow.ShowError("请选取要发货的订单", "Error"); + return; + } + + + GiftType giftType = GiftType._160; + if (cb_gift.SelectedIndex == 0) + { + giftType = GiftType._155; + } + else + { + giftType = GiftType._160; + } + int index = 1; + int sucessCount = 0; + int errorCount = 0; + foreach (oms_wei_order_info item in list) + { + try + { + var rst = await WxApiBLL.WxSearchAndDropshipCancelAsync(item.Shop, item); + if (rst == "success") + { + var ret = await _shipGiftService.Send(item.order_id, giftType); + if (ret) + { + sucessCount++; + item.gift_order_status = 1; + this.FilterOneOrder(item, true); + } + else + { + errorCount++; + } + } + else + { + errorCount++; + } + } + catch (Exception ex) + { + errorCount++; + } + finally + { + item.IsSelect = false; + } + + SetProgress(index, count); + BottomMsg = $"礼品单发送进度:{index}/{count}\t\t{item.orderItems.FirstOrDefault()?.title ?? ""}"; + index++; + } + + BottomMsg = $"礼品单发送完成 成功:{sucessCount} 失败:{errorCount} 总数:{count}"; + } + catch (Exception ex) + { + BaseMsgWindow.ShowError("礼品单发货失败\n" + ex.Message, "Error"); + } + finally + { + + } + } + #endregion + #region 同步订单 + /// + /// 拉取微信订单到任务列表 + /// + public ICommand PullWXOrderCommand => new AsyncDelegateCommand(PullWXOrder); + + private async Task PullWXOrder() + { + try + { + var isAfterSales = GlobalConfig.OrderTabHeaderStr.StartsWith("需处理售后单");//是否售后 + + var selectedShops = GlobalConfig.ShopLoginList.Where(a => a.IsSelect).ToList(); + if (selectedShops.Count == 0) + { + selectedShops = GlobalConfig.ShopLoginList.ToList(); + } + + var selectedOrders = GlobalConfig.lstFilterOrder.Where(a => a.IsSelect).ToList(); + int count = selectedOrders.Count; + if (count > 0 && isAfterSales == false)//非售后订单 + { + if (BaseMsgWindow.ShowConfirm($"已选取:{count}个订单", "同步订单", MainWindow.Instance, MsgWindowLevel.MEDIUM).DialogResult == false) return; + BottomMsg = $"订单同步进度...."; + int index = 1; + int sucessCount = 0; + int errorCount = 0; + foreach (var item in selectedOrders) + { + var ret = await PullWXOrderService.Instance.PullOrderDetailAsync(item.shop_id, item.order_id); ; + if (ret) + { + sucessCount++; + this.GetOrder(item.order_id); + SetProgress(index, count); + item.IsSelect = false; + } + else + { + errorCount++; + } + BottomMsg = $"订单同步进度:{index}/{count}\t\t{item.orderItems.FirstOrDefault()?.title ?? ""}"; + index++; + } + BottomMsg = $"订单同步进度完成 成功:{sucessCount} 失败:{errorCount} 总数:{count}"; + } + else if (count > 0 && isAfterSales == true)//售后订单 + { + if (BaseMsgWindow.ShowConfirm($"已选取:{count}个售后订单", "同步订单", MainWindow.Instance, MsgWindowLevel.MEDIUM).DialogResult == false) return; + //var dictionaryResult = selectedOrders + // .Where(o => o?.refundItems != null) + // .SelectMany(o => o.refundItems + // .Where(item => item != null) + // .Select(item => new { o.shop_id, item.after_sale_order_id })) + // .GroupBy(x => x.shop_id) + // .ToDictionary( + // g => g.Key, + // g => g.Select(x => x.after_sale_order_id).ToArray() + // ); + + + //BottomMsg = $"售后订单同步进度...."; + //foreach (var item in dictionaryResult) + //{ + // var ret = await PullWXOrderService.Instance.PullAfterSaleOrdersAsync(item.Key, item.Value); + //} + //BottomMsg = $"售后同步任务已发送,等待同步完成"; + + + + int index = 1; + int sucessCount = 0; + int errorCount = 0; + BottomMsg = $"售后订单同步进度...."; + foreach (KeyValuePair> item in selectedOrders.GroupBy(a => a.shop_id).ToDictionary(a => a.Key, a => a.ToList())) + { + long shopid = item.Key; + string shopName = item.Value.FirstOrDefault()?.shop_name ?? ""; + string[] afterSaleOrderIds = item.Value.SelectMany(a => a.refundItems).Where(a => a != null).Select(a => a.after_sale_order_id).ToArray(); + var ret = await PullWXOrderService.Instance.PullAfterSaleOrdersAsync(shopid, afterSaleOrderIds); + if (ret) + { + sucessCount++; + SetProgress(index, count); + item.Value.ForEach(a => a.IsSelect = false); + } + else + { + errorCount++; + } + BottomMsg = $"售后订单同步进度:{index}/{count}\t\t{shopName}"; + index++; + + } + BottomMsg = $"售后同步任务已发送,等待同步完成"; + } + else + { + count = selectedShops.Count; + string prefix = $"已选取:{count}个商铺"; + if (count == GlobalConfig.ShopLoginList.Count) + { + prefix = "已选取全部商铺"; + } + + + var startTime = dateStart.SelectedDate.Value; + var endTime = dateEnd.SelectedDate.Value; + + string msg = ""; + if (isAfterSales) + { + msg = $"{prefix}"; + } + else + { + msg = $"{prefix}\n\n时间范围:{startTime.ToString("yyyy-MM-dd")}-{endTime.ToString("yyyy-MM-dd")}"; + } + + if (BaseMsgWindow.ShowConfirm(msg, $"{(isAfterSales ? "售后" : "")}同步订单", MainWindow.Instance, MsgWindowLevel.MEDIUM).DialogResult == false) return; + + if ((endTime - startTime).Days > 7) + { + throw new Exception("时间范围最大7天"); + } + + var ret = false; + if (isAfterSales) + { + ret = await PullWXOrderService.Instance.PullRefundOrderByShopsAsync(selectedShops.Select(a => a.ErpShopId.ParseToLong()).ToArray(), startTime, endTime); + } + else + { + ret = await PullWXOrderService.Instance.PullOrderByShopIdsAndTimeRangeAsync(selectedShops.Select(a => a.ErpShopId.ParseToLong()).ToArray(), startTime, endTime); + } + if (ret) + { + BaseMsgWindow.ShowInfo("同步任务已发送,等待同步完成\n任务详情查看任务列表"); + } + else + { + BaseMsgWindow.ShowError("同步任务失败"); + } + } + + + } + catch (Exception ex) + { + BaseMsgWindow.ShowError("同步订单失败\n" + ex.Message, "Error"); + } + //var service = PullWXOrderService.Instance; + //foreach (var item in list) + //{ + // await service.PullOrderDetailAsync(item.shop_id, item.order_id); + //} + } + #endregion + #region 显示任务列表 + private DialogWindow _taskDialog; + public ICommand ShowTaskDialogViewCommand => new DelegateCommand(ShowTaskDialogView); + + private void ShowTaskDialogView() + { + if (_taskDialog == null) + { + _taskDialog = new DialogWindow() + { + Title = "任务列表", + Content = new OrderTaskDialogView(), + + }; + _taskDialog.Closed += (s, e) => _taskDialog = null; + } + _taskDialog.Show(); + } + #endregion + + #region 拼多多发票 + public ICommand ShowPDDInvoiceDialogCommand => new DelegateCommand(ShowPDDInvoiceDialog); + + private void ShowPDDInvoiceDialog() + { + new DialogWindow + { + Title = "发票助手", + Content = new PDDInvoiceView() + }.ShowDialog(); + } + #endregion +} diff --git a/29WxOrderWpf/Ctrls/OrderCtrls/OrderListCtrl.xaml b/29WxOrderWpf/Ctrls/OrderCtrls/OrderListCtrl.xaml index c36abb5..129ea92 100644 --- a/29WxOrderWpf/Ctrls/OrderCtrls/OrderListCtrl.xaml +++ b/29WxOrderWpf/Ctrls/OrderCtrls/OrderListCtrl.xaml @@ -25,7 +25,8 @@ - + + @@ -40,11 +41,8 @@ - - - - + @@ -205,7 +203,8 @@ Padding="4" Click="btnFilter_Click" Content="过滤" - Style="{StaticResource BasePageButtonStyle}" /> + Style="{StaticResource BasePageButtonStyle}" + ToolTip="过滤" /> - + _statisticsWindow = null; - } - _statisticsWindow.Show(); - - } + /// /// @@ -2066,263 +2051,7 @@ namespace _29WxOrderWpf.Ctrls.OrderCtrls // set => SetProperty(ref _IsShipGift, value); //} - public ICommand MultipleShipGiftCommand => new AsyncDelegateCommand(MultipleShipGift); - /// - /// 批量发送礼品单 - /// - /// - /// - private async Task MultipleShipGift() - { - - try - { - var list = GlobalConfig.lstFilterOrder.Where(a => a.IsSelect).ToList(); - int count = list.Count; - if (count == 0) - { - BaseMsgWindow.ShowError("请选取要发货的订单", "Error"); - return; - } - - - GiftType giftType = GiftType._160; - if (cb_gift.SelectedIndex == 0) - { - giftType = GiftType._155; - } - else - { - giftType = GiftType._160; - } - int index = 1; - int sucessCount = 0; - int errorCount = 0; - foreach (oms_wei_order_info item in list) - { - try - { - var rst = await WxApiBLL.WxSearchAndDropshipCancelAsync(item.Shop, item); - if (rst == "success") - { - var ret = await _shipGiftService.Send(item.order_id, giftType); - if (ret) - { - sucessCount++; - item.gift_order_status = 1; - this.FilterOneOrder(item, true); - } - else - { - errorCount++; - } - } - else - { - errorCount++; - } - } - catch (Exception ex) - { - errorCount++; - } - finally - { - item.IsSelect = false; - } - - SetProgress(index, count); - BottomMsg = $"礼品单发送进度:{index}/{count}\t\t{item.orderItems.FirstOrDefault()?.title ?? ""}"; - index++; - } - - BottomMsg = $"礼品单发送完成 成功:{sucessCount} 失败:{errorCount} 总数:{count}"; - } - catch (Exception ex) - { - BaseMsgWindow.ShowError("礼品单发货失败\n" + ex.Message, "Error"); - } - finally - { - - } - } - - /// - /// 拉取微信订单到任务列表 - /// - public ICommand PullWXOrderCommand => new AsyncDelegateCommand(PullWXOrder); - - private async Task PullWXOrder() - { - try - { - var isAfterSales = GlobalConfig.OrderTabHeaderStr.StartsWith("需处理售后单");//是否售后 - - var selectedShops = GlobalConfig.ShopLoginList.Where(a => a.IsSelect).ToList(); - if (selectedShops.Count == 0) - { - selectedShops = GlobalConfig.ShopLoginList.ToList(); - } - - var selectedOrders = GlobalConfig.lstFilterOrder.Where(a => a.IsSelect).ToList(); - int count = selectedOrders.Count; - if (count > 0 && isAfterSales == false)//非售后订单 - { - if (BaseMsgWindow.ShowConfirm($"已选取:{count}个订单", "同步订单", MainWindow.Instance, MsgWindowLevel.MEDIUM).DialogResult == false) return; - BottomMsg = $"订单同步进度...."; - int index = 1; - int sucessCount = 0; - int errorCount = 0; - foreach (var item in selectedOrders) - { - var ret = await PullWXOrderService.Instance.PullOrderDetailAsync(item.shop_id, item.order_id); ; - if (ret) - { - sucessCount++; - this.GetOrder(item.order_id); - SetProgress(index, count); - item.IsSelect = false; - } - else - { - errorCount++; - } - BottomMsg = $"订单同步进度:{index}/{count}\t\t{item.orderItems.FirstOrDefault()?.title ?? ""}"; - index++; - } - BottomMsg = $"订单同步进度完成 成功:{sucessCount} 失败:{errorCount} 总数:{count}"; - } - else if (count > 0 && isAfterSales == true)//售后订单 - { - if (BaseMsgWindow.ShowConfirm($"已选取:{count}个售后订单", "同步订单", MainWindow.Instance, MsgWindowLevel.MEDIUM).DialogResult == false) return; - //var dictionaryResult = selectedOrders - // .Where(o => o?.refundItems != null) - // .SelectMany(o => o.refundItems - // .Where(item => item != null) - // .Select(item => new { o.shop_id, item.after_sale_order_id })) - // .GroupBy(x => x.shop_id) - // .ToDictionary( - // g => g.Key, - // g => g.Select(x => x.after_sale_order_id).ToArray() - // ); - - - //BottomMsg = $"售后订单同步进度...."; - //foreach (var item in dictionaryResult) - //{ - // var ret = await PullWXOrderService.Instance.PullAfterSaleOrdersAsync(item.Key, item.Value); - //} - //BottomMsg = $"售后同步任务已发送,等待同步完成"; - - - - int index = 1; - int sucessCount = 0; - int errorCount = 0; - BottomMsg = $"售后订单同步进度...."; - foreach (KeyValuePair> item in selectedOrders.GroupBy(a => a.shop_id).ToDictionary(a => a.Key, a => a.ToList())) - { - long shopid = item.Key; - string shopName = item.Value.FirstOrDefault()?.shop_name ?? ""; - string[] afterSaleOrderIds = item.Value.SelectMany(a => a.refundItems).Where(a => a != null).Select(a => a.after_sale_order_id).ToArray(); - var ret = await PullWXOrderService.Instance.PullAfterSaleOrdersAsync(shopid, afterSaleOrderIds); - if (ret) - { - sucessCount++; - SetProgress(index, count); - item.Value.ForEach(a => a.IsSelect = false); - } - else - { - errorCount++; - } - BottomMsg = $"售后订单同步进度:{index}/{count}\t\t{shopName}"; - index++; - - } - BottomMsg = $"售后同步任务已发送,等待同步完成"; - } - else - { - count = selectedShops.Count; - string prefix = $"已选取:{count}个商铺"; - if (count == GlobalConfig.ShopLoginList.Count) - { - prefix = "已选取全部商铺"; - } - - - var startTime = dateStart.SelectedDate.Value; - var endTime = dateEnd.SelectedDate.Value; - - string msg = ""; - if (isAfterSales) - { - msg = $"{prefix}"; - } - else - { - msg = $"{prefix}\n\n时间范围:{startTime.ToString("yyyy-MM-dd")}-{endTime.ToString("yyyy-MM-dd")}"; - } - - if (BaseMsgWindow.ShowConfirm(msg, $"{(isAfterSales ? "售后" : "")}同步订单", MainWindow.Instance, MsgWindowLevel.MEDIUM).DialogResult == false) return; - - if ((endTime - startTime).Days > 7) - { - throw new Exception("时间范围最大7天"); - } - - var ret = false; - if (isAfterSales) - { - ret = await PullWXOrderService.Instance.PullRefundOrderByShopsAsync(selectedShops.Select(a => a.ErpShopId.ParseToLong()).ToArray(), startTime, endTime); - } - else - { - ret = await PullWXOrderService.Instance.PullOrderByShopIdsAndTimeRangeAsync(selectedShops.Select(a => a.ErpShopId.ParseToLong()).ToArray(), startTime, endTime); - } - if (ret) - { - BaseMsgWindow.ShowInfo("同步任务已发送,等待同步完成\n任务详情查看任务列表"); - } - else - { - BaseMsgWindow.ShowError("同步任务失败"); - } - } - - - } - catch (Exception ex) - { - BaseMsgWindow.ShowError("同步订单失败\n" + ex.Message, "Error"); - } - //var service = PullWXOrderService.Instance; - //foreach (var item in list) - //{ - // await service.PullOrderDetailAsync(item.shop_id, item.order_id); - //} - } - - private DialogWindow _taskDialog; - public ICommand ShowTaskDialogViewCommand => new DelegateCommand(ShowTaskDialogView); - - private void ShowTaskDialogView() - { - if (_taskDialog == null) - { - _taskDialog = new DialogWindow() - { - Title = "任务列表", - Content = new OrderTaskDialogView(), - - }; - _taskDialog.Closed += (s, e) => _taskDialog = null; - } - _taskDialog.Show(); - } } public class OrderFilterConfig diff --git a/29WxOrderWpf/Ctrls/PDDInvoice/PDDInvoiceView.xaml b/29WxOrderWpf/Ctrls/PDDInvoice/PDDInvoiceView.xaml new file mode 100644 index 0000000..7efeba5 --- /dev/null +++ b/29WxOrderWpf/Ctrls/PDDInvoice/PDDInvoiceView.xaml @@ -0,0 +1,800 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/29WxOrderWpf/Ctrls/PDDInvoice/PDDInvoiceView.xaml.cs b/29WxOrderWpf/Ctrls/PDDInvoice/PDDInvoiceView.xaml.cs new file mode 100644 index 0000000..0e640ac --- /dev/null +++ b/29WxOrderWpf/Ctrls/PDDInvoice/PDDInvoiceView.xaml.cs @@ -0,0 +1,37 @@ +using _29WxOrderWpf.Ctrls.PDDInvoice.ViewModels; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +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.Navigation; +using System.Windows.Shapes; + +namespace _29WxOrderWpf.Ctrls.PDDInvoice; + +/// +/// PDDInvoiceView.xaml 的交互逻辑 +/// +public partial class PDDInvoiceView : UserControl +{ + public PDDInvoiceView() + { + InitializeComponent(); + DataContext = new PDDInvoiceViewModel(); + } + + private void txtSearch_KeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.Enter && DataContext is PDDInvoiceViewModel viewModel) + { + viewModel.SearchCommand.Execute(null); + } + } +} \ No newline at end of file diff --git a/29WxOrderWpf/Ctrls/PDDInvoice/ViewModels/PDDInvoiceOrderModel.cs b/29WxOrderWpf/Ctrls/PDDInvoice/ViewModels/PDDInvoiceOrderModel.cs new file mode 100644 index 0000000..128ff3a --- /dev/null +++ b/29WxOrderWpf/Ctrls/PDDInvoice/ViewModels/PDDInvoiceOrderModel.cs @@ -0,0 +1,70 @@ +using AllBase.Model.Result; +using System; +using System.Collections.Generic; +using System.Linq; +using WPFCommon.Helpers; +using WPFCommon.Models; + +namespace _29WxOrderWpf.Ctrls.PDDInvoice.ViewModels; + +/// +/// 订单发票Model +/// +public class PddBrowserOrderInvoiceViewModel : BindableBaseEx +{ + + public string buyerId { get; set; } // 买手Id + public string buyerName { get; set; } // 买手名称 + /// + /// 订单价 + /// + public int order_amount { get; set; } + /// + /// 实付款 + /// + public string display_amount { get; set; } + /// + /// 下单时间 + /// + public long order_time { get; set; } + /// + /// 订单号 + /// + public string order_sn { get; set; } + + public int status { get; set; } + public int pay_status { get; set; } + public string order_status_prompt { get; set; } + public List order_Goods { get; set; } + + + public List order_buttons { get; set; } + + + public string GoodsName { get => order_Goods?.FirstOrDefault()?.goods_name ?? ""; } + public bool IsInvoiced + { + get => order_buttons?.Any(a => a.button_code == 500) ?? false; + } + + public int Quantity => order_Goods?.Sum(a => a.goods_number.Value) ?? 0; + /// 发票状态文案:已开票 / 待开票 + public string InvoiceStatusText => IsInvoiced ? "已开票" : "待开票"; + public DateTime OrderTime + { + get => DateTimeHelper.ConvertFromUnixTimestampInSeconds(order_time); + } + public string ThumbUrl=> order_Goods?.FirstOrDefault()?.thumb_url ?? ""; + public string DisplayAmount => (order_amount / 100.0).ToString("F2"); +} + +public class OrderButtonItem +{ + public int button_code { get; set; } + public string brief_prompt { get; set; } + public ButtonTypeValue type_value { get; set; } +} +public class ButtonTypeValue +{ + public string Url { get; set; } +} \ No newline at end of file diff --git a/29WxOrderWpf/Ctrls/PDDInvoice/ViewModels/PDDInvoiceViewModel.Browser.cs b/29WxOrderWpf/Ctrls/PDDInvoice/ViewModels/PDDInvoiceViewModel.Browser.cs new file mode 100644 index 0000000..c661fa1 --- /dev/null +++ b/29WxOrderWpf/Ctrls/PDDInvoice/ViewModels/PDDInvoiceViewModel.Browser.cs @@ -0,0 +1,168 @@ +using _29WxOrderWpf.CefHelper; +using _29WxOrderWpf.Commands; +using AllBase; +using CefSharp; +using CefSharp.Handler; +using CefSharp.Wpf; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Input; +using Work._29Tools.CefTools; + +namespace _29WxOrderWpf.Ctrls.PDDInvoice.ViewModels; + +public partial class PDDInvoiceViewModel +{ + /// + /// 订单列表浏览器 + /// + public ChromiumWebBrowser Browser { get; private set; } + /// + /// 内容浏览器 + /// 订单详情 + /// 发票详情 + /// 申请发票 + /// + public ChromiumWebBrowser BrowserDetail { get; private set; } + + partial void InitBrowser() + { + BrowserZIndex = -1; + BrowserDetailZIndex = -1; + + Browser = WPFCEFBrowser.CreateChromiumWebBrowser("about:blank", false); + + LocalResourceRequestHandler resourceRequestHandler = new LocalResourceRequestHandler(); + //resourceRequestHandler.BeforeResourceLoadEvent += RequestHandler_BeforeResourceLoad; + resourceRequestHandler.ResourceLoadCompleteEvent += RequestHandler_ResourceLoadComplete; + Browser.RequestHandler = new LocalRequestHandler(resourceRequestHandler); + Browser.LifeSpanHandler = new LocalLifeSpanHandler(PopWindowType.SelfWindow); + Browser.KeyboardHandler = new KeyboardHandler(); + + + BrowserDetail = WPFCEFBrowser.CreateChromiumWebBrowser("about:blank", false); + LocalResourceRequestHandler detailResourceRequestHandler = new LocalResourceRequestHandler(); + detailResourceRequestHandler.ResourceLoadCompleteEvent += DetailRequestHandler_ResourceLoadComplete; + BrowserDetail.RequestHandler = new LocalRequestHandler(resourceRequestHandler); + BrowserDetail.LifeSpanHandler = new LocalLifeSpanHandler(PopWindowType.SelfWindow); + BrowserDetail.KeyboardHandler = new KeyboardHandler(); + } + + public ICommand CloseOrderBrowserCommand => new DelegateCommand(HidenOrderBrowser); + public ICommand CloseInvoiceDetailCommand => new DelegateCommand(HidenOrderBrowserDetail); + + public int BrowserZIndex + { + get => GetValue(); + set => SetValue(value); + } + public int BrowserDetailZIndex + { + get => GetValue(); + set => SetValue(value); + } + /// + /// 显示主浏览器 + /// + public void ShowOrderBrowser() + { + BrowserZIndex = 10; + BrowserDetailZIndex = -1; + } + /// + /// 显示子浏览器 + /// + public void ShowOrderBrowserDetail() + { + BrowserZIndex = -1; + BrowserDetailZIndex = 10; + } + public void HidenOrderBrowser() + { + BrowserZIndex = -1; + } + public void HidenOrderBrowserDetail() + { + BrowserDetailZIndex = -1; + } + public bool IsAutoScroll + { + get => GetValue(); + set => SetValue(value); + } + private int top = 0; + private DateTime lastGetOrderDataTime = DateTime.Now; + private void RequestHandler_ResourceLoadComplete(object sender, string url, string postData, string responseData, IRequest request) + { + if (url.StartsWith("https://mobile.yangkeduo.com/orders.html")) + { + //if (isAutoScroll == false) + //{ + // StartAutoScroll(); + //} + + StartAutoScroll(); + } + if (url.StartsWith("https://mobile.yangkeduo.com/proxy/api/api/aristotle/order_list_v")) + { + lastGetOrderDataTime = DateTime.Now; + parserData(responseData); + + //File.WriteAllText(@$"E:\{Guid.NewGuid().ToString()}.txt", responseData); + } + } + public void StartAutoScroll() + { + if (IsAutoScroll) return; + IsAutoScroll = true; + _cancellationTokenSource = new CancellationTokenSource(); + _ = autoScroll(_cancellationTokenSource.Token); + } + + // 停止滚动 + public void StopAutoScroll() + { + IsAutoScroll = false; + _cancellationTokenSource?.Cancel(); + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = null; + } + + private async Task autoScroll(CancellationToken token) + { + while (IsAutoScroll && !token.IsCancellationRequested) + { + if ((DateTime.Now - lastGetOrderDataTime).Seconds > 30) StopAutoScroll(); + + top += Randomx.GetId(450, 600); + var jsStr = @"window.scroll({ + top: " + top + @", + left: 0, + behavior: 'smooth' // 可选,使滚动平滑 + });"; + + var client = Browser.GetDevToolsClient(); + if (client != null) + { + var result = client.Runtime.EvaluateAsync(jsStr, null, true, null, null, true, null, true); + result.Wait(5000); + } + await Task.Delay(Randomx.GetId(100, 300)); + //Thread.Sleep(); + } + } + + + private void DetailRequestHandler_ResourceLoadComplete(object sender, string url, string postData, string responseData, IRequest request) + { + if (url.StartsWith("https://mobile.yangkeduo.com/transac_return_compensation.html")) + { + + } + } + +} diff --git a/29WxOrderWpf/Ctrls/PDDInvoice/ViewModels/PDDInvoiceViewModel.cs b/29WxOrderWpf/Ctrls/PDDInvoice/ViewModels/PDDInvoiceViewModel.cs new file mode 100644 index 0000000..182c4b8 --- /dev/null +++ b/29WxOrderWpf/Ctrls/PDDInvoice/ViewModels/PDDInvoiceViewModel.cs @@ -0,0 +1,391 @@ +using _29WxOrderWpf.CefHelper; +using _29WxOrderWpf.Commands; +using AllBase; +using AllBase.Helper; +using AllBase.Model.DBModel; +using AllBase.Model.Result; +using AllBase.Util; +using CefSharp; +using CefSharp.Handler; +using CefSharp.Wpf; +using NaiBa.Util.Extension; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Runtime; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Threading; +using Work._29Tools.CefTools; +using WPFCommon.Models; + +namespace _29WxOrderWpf.Ctrls.PDDInvoice.ViewModels; + +/// +/// 拼多多发票助手 视图模型 +/// +public partial class PDDInvoiceViewModel : BindableBaseEx +{ + private readonly ObservableCollection _allOrders = new(); + private readonly HashSet _orderSns = new(); + private string _searchText = ""; + private PddBrowserOrderInvoiceViewModel _selectedOrder; + private bool _isInvoiceDialogOpen; + private bool _isInvoiceDetailOpen; + private string _invoiceType = "电子普通发票"; + private string _invoiceTitle = "XX科技有限公司"; + private string _taxNo = "91110000XXXXXXXXXX"; + private CancellationTokenSource _cancellationTokenSource; public PDDInvoiceViewModel() + { + Orders = new ObservableCollection(); + + BuyerLoginList = GlobalConfig.PDDBuyerLoginList; + //Orders = new ObservableCollection(_allOrders); + + //_ = InitSampleData(); + + InitBrowser(); + } + partial void InitBrowser(); + + + + private void parserData(string data) + { + List list = JsonHelper.DeserializeJsonToList(JsonHelper.JObjectParse(data)["orders"].ParseToString()); + if (list.Count == 0) + { + StopAutoScroll(); + return; + } + + var matched = list.Where(item => item.status == 4 && item.pay_status == 2).ToList(); + if (matched.Count == 0) + { + return; + } + + // CEF 回调运行在后台线程,ObservableCollection 的更新需要切回 UI 线程 + var dispatcher = Application.Current?.Dispatcher; + if (dispatcher == null || dispatcher.CheckAccess()) + { + AddOrders(matched); + } + else + { + dispatcher.BeginInvoke(new Action(() => AddOrders(matched))); + } + } + + /// 将新订单写入总表,并按当前搜索关键字决定是否加入可见列表(自动去重) + private void AddOrders(IEnumerable items) + { + var keyword = SearchText?.Trim(); + foreach (var item in items) + { + if (!string.IsNullOrEmpty(item.order_sn) && !_orderSns.Add(item.order_sn)) + { + continue; + } + _allOrders.Add(item); + if (IsKeywordMatch(item, keyword)) + { + Orders.Add(item); + } + } + } + + + + public ObservableCollectionThreadSafe BuyerLoginList { get; set; } = new ObservableCollectionThreadSafe(); + public BuyerLoginInfo SelectedBuyer + { + get => GetProperty(() => SelectedBuyer); + set => SetProperty(() => SelectedBuyer, value, SelectedBuyerChanged); + } + + private void SelectedBuyerChanged() + { + WPFCEFBrowser.SetCookieAsync(SelectedBuyer.cookie, Browser.GetCookieManager()).GetAwaiter(); + WPFCEFBrowser.SetCookieAsync(SelectedBuyer.cookie, BrowserDetail.GetCookieManager()).GetAwaiter(); + + } + + /// 订单列表 + public ObservableCollection Orders { get; } + + /// 搜索关键字:订单号 / 商品名称 / 买家 + public string SearchText + { + get => _searchText; + set => SetProperty(ref _searchText, value, nameof(SearchText)); + } + + /// 开票弹窗是否打开 + public bool IsInvoiceDialogOpen + { + get => _isInvoiceDialogOpen; + set => SetProperty(ref _isInvoiceDialogOpen, value, nameof(IsInvoiceDialogOpen)); + } + + /// 发票详情弹窗是否打开 + public bool IsInvoiceDetailOpen + { + get => _isInvoiceDetailOpen; + set => SetProperty(ref _isInvoiceDetailOpen, value, nameof(IsInvoiceDetailOpen)); + } + + /// 当前操作的订单 + public PddBrowserOrderInvoiceViewModel SelectedOrder + { + get => _selectedOrder; + set => SetProperty(ref _selectedOrder, value, nameof(SelectedOrder)); + } + + /// 开票类型 + public string InvoiceType + { + get => _invoiceType; + set => SetProperty(ref _invoiceType, value, nameof(InvoiceType)); + } + + /// 发票抬头 + public string InvoiceTitle + { + get => _invoiceTitle; + set => SetProperty(ref _invoiceTitle, value, nameof(InvoiceTitle)); + } + + /// 税号 + public string TaxNo + { + get => _taxNo; + set => SetProperty(ref _taxNo, value, nameof(TaxNo)); + } + + public ICommand SearchCommand => new DelegateCommand(Search); + + public ICommand ResetCommand => new DelegateCommand(ResetSearch); + + public ICommand OpenInvoiceCommand => new DelegateCommand(OpenInvoice); + + public ICommand ShowInvoiceCommand => new DelegateCommand(ShowInvoice); + + public ICommand ViewOrderCommand => new DelegateCommand(ViewOrder); + + public ICommand CloseInvoiceDialogCommand => new DelegateCommand(() => IsInvoiceDialogOpen = false); + + public ICommand ConfirmInvoiceCommand => new DelegateCommand(ConfirmInvoice); + + public ICommand ReadOrderCommand => new DelegateCommand(ReadOrder, () => !IsAutoScroll); + + private void ReadOrder() + { + Browser.Load("https://mobile.yangkeduo.com/orders.html"); + + ShowOrderBrowser(); + } + + + /// 按 订单号 / 商品名称 过滤订单 + private void Search() + { + var keyword = SearchText?.Trim(); + Orders.Clear(); + foreach (var order in _allOrders.Where(o => IsKeywordMatch(o, keyword))) + { + Orders.Add(order); + } + } + + private static bool IsKeywordMatch(PddBrowserOrderInvoiceViewModel order, string keyword) + { + return string.IsNullOrEmpty(keyword) + || (!string.IsNullOrEmpty(order.order_sn) && order.order_sn.Contains(keyword)) + || (!string.IsNullOrEmpty(order.GoodsName) && order.GoodsName.Contains(keyword)); + } + + private void ResetSearch() + { + SearchText = ""; + Search(); + } + + private void OpenInvoice(PddBrowserOrderInvoiceViewModel order) + { + SelectedOrder = order; + BrowserDetail.Load($"https://mobile.yangkeduo.com/transac_return_compensation.html?_t_timestamp=transac_modify_invoice&order_sn={order.order_sn}"); + ShowOrderBrowserDetail(); + //InvoiceType = string.IsNullOrEmpty(order.InvoiceType) ? "电子普通发票" : order.InvoiceType; + //InvoiceTitle = string.IsNullOrEmpty(order.InvoiceTitle) ? "XX科技有限公司" : order.InvoiceTitle; + //TaxNo = string.IsNullOrEmpty(order.TaxNo) ? "91110000XXXXXXXXXX" : order.TaxNo; + //IsInvoiceDialogOpen = true; + } + + private void ConfirmInvoice() + { + if (SelectedOrder == null) + return; + + //SelectedOrder.IsInvoiced = true; + //SelectedOrder.InvoiceType = InvoiceType; + //SelectedOrder.InvoiceTitle = InvoiceTitle; + //SelectedOrder.TaxNo = TaxNo; + //SelectedOrder.InvoiceNo = "25" + DateTime.Now.ToString("MMddHHmmss"); + //SelectedOrder.InvoiceAmountText = SelectedOrder.AmountText; + //SelectedOrder.InvoiceTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + + IsInvoiceDialogOpen = false; + + //MessageBox.Show($"订单 {SelectedOrder.OrderNo} 开票成功"); + } + + private void ShowInvoice(PddBrowserOrderInvoiceViewModel order) + { + SelectedOrder = order; + IsInvoiceDetailOpen = true; + } + + private void ViewOrder(PddBrowserOrderInvoiceViewModel order) + { + //MessageBox.Show($"查看订单:{order.OrderNo}"); + } + + /// + /// 原型演示数据(后续可替换为真实接口数据) + /// + private Task InitSampleData() + { + string directoryPath = @"E:\820"; + + // 3. 获取所有 txt 文件 + var txtFiles = Directory.GetFiles(directoryPath, "*.txt"); + + if (txtFiles.Length == 0) + { + Console.WriteLine("未找到任何 .txt 文件"); + return Task.CompletedTask; + } + foreach (var filePath in txtFiles) + { + try + { + string jsonContent = File.ReadAllText(filePath); + parserData(jsonContent); + + + } + catch (Exception ex) + { + + } + } + return Task.CompletedTask; + } + + + #region 进度条 + + private string _bottomMsg = ""; + public string BottomMsg + { + get => _bottomMsg; + set => SetValue(ref _bottomMsg, value); + } + + private double _progressValue; + public double ProgressValue + { + get => _progressValue; + set => SetValue(ref _progressValue, value); + } + + private double _progressMaximum = 100; + public double ProgressMaximum + { + get => _progressMaximum; + set => SetValue(ref _progressMaximum, value); + } + public void SetProgress(int value, int maxValue) + { + StopUnknownTotalProgressTimer(); + ProgressValue = value; + ProgressMaximum = maxValue; + } + + #region 未知总数百分比模拟进度 + private System.Threading.Timer _unknownTotalProgressTimer; + private DateTime _unknownTotalProgressStartTime; + + /// + /// 开始未知总数的百分比模拟进度:80% 之前走得快,越到最后走得越慢, + /// 理论上永远走不到 95% 以上,直到调用 才会变成 100%。 + /// + public void StartProgress() + { + StopUnknownTotalProgressTimer(); + ProgressMaximum = 100; + ProgressValue = 0; + _unknownTotalProgressStartTime = DateTime.Now; + _unknownTotalProgressTimer = new System.Threading.Timer(_ => UpdateUnknownTotalProgress(), null, 0, 200); + } + + /// + /// 结束进度:停止模拟,进度条立即变为 100%。 + /// + public void CompleteProgress() + { + StopUnknownTotalProgressTimer(); + ProgressMaximum = 100; + ProgressValue = 100; + } + + private void UpdateUnknownTotalProgress() + { + try + { + System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => + { + var elapsed = (DateTime.Now - _unknownTotalProgressStartTime).TotalSeconds; + double progress; + if (elapsed <= 10) + { + // 前 10 秒内快速推进到 80% + progress = Math.Max(0, Math.Min(80, elapsed * 8)); + } + else + { + // 超过 80% 后指数衰减越来越慢,渐近线为 95%,理论上永远走不到 95% 以上 + progress = 95.0 - 15.0 * Math.Exp(-(elapsed - 10.0) / 20.0); + } + if (progress > 95) progress = 95; + ProgressValue = progress; + })); + } + catch + { + } + } + + private void StopUnknownTotalProgressTimer() + { + if (_unknownTotalProgressTimer == null) return; + try + { + _unknownTotalProgressTimer.Dispose(); + } + catch + { + } + _unknownTotalProgressTimer = null; + } + #endregion + #endregion +} + diff --git a/29WxOrderWpf/Ctrls/Statistics/ShopRevenueStatisticsView.xaml b/29WxOrderWpf/Ctrls/Statistics/ShopRevenueStatisticsView.xaml index 008a6df..a01248e 100644 --- a/29WxOrderWpf/Ctrls/Statistics/ShopRevenueStatisticsView.xaml +++ b/29WxOrderWpf/Ctrls/Statistics/ShopRevenueStatisticsView.xaml @@ -122,7 +122,7 @@ - + - + + + + @@ -307,11 +314,13 @@ Margin="0,-10,-10,0" HorizontalAlignment="Right" VerticalAlignment="Top" - Background="Transparent" Click="close" + Background="Transparent" + Click="close" Content="x" /> -