主页增加发票助手
This commit is contained in:
1 parent
5456e6611d
commit
a15f97ac2e
22 files changed
+3825
-309
No files matched your search
@@ -63,8 +63,10 @@
|
||||
<!-- 资源文件:SDK 样式工程默认不会把图片/图标作为 Resource,这里按原工程显式声明 -->
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\**\*" />
|
||||
<None Remove="Ctrls\PDDInvoice\Resources\invoice.js" />
|
||||
<Resource Include="29wx.ico" />
|
||||
<Resource Include="29_common_icon.ico" />
|
||||
<Resource Include="Ctrls\PDDInvoice\Resources\invoice.js" />
|
||||
<Resource Include="gear.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary> 缓存上限,防止大量缩略图重复下载导致内存膨胀 </summary>
|
||||
private const int MaxCacheCount = 200;
|
||||
|
||||
private readonly ConcurrentDictionary<string, BitmapImage> _imageCache = new ConcurrentDictionary<string, BitmapImage>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
/// <summary>
|
||||
/// 批量发送礼品单
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
|
||||
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 同步订单
|
||||
/// <summary>
|
||||
/// 拉取微信订单到任务列表
|
||||
/// </summary>
|
||||
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<long, List<oms_wei_order_info>> 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()
|
||||
}.Show();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -25,7 +25,8 @@
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="18" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid x:Name="gridSearch" Margin="8,0,8,-2">
|
||||
|
||||
<Grid x:Name="gridSearch" Margin="8,0,290,-2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="1.2*" />
|
||||
@@ -40,11 +41,8 @@
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="1.4*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
|
||||
@@ -205,7 +203,8 @@
|
||||
Padding="4"
|
||||
Click="btnFilter_Click"
|
||||
Content="过滤"
|
||||
Style="{StaticResource BasePageButtonStyle}" />
|
||||
Style="{StaticResource BasePageButtonStyle}"
|
||||
ToolTip="过滤" />
|
||||
<Button
|
||||
x:Name="btnSearch"
|
||||
Grid.Column="10"
|
||||
@@ -274,16 +273,29 @@
|
||||
IsChecked="true" />
|
||||
</WrapPanel>
|
||||
|
||||
<wpf:SpacingStackPanel Grid.Column="13" HorizontalSpacing="5">
|
||||
<Button
|
||||
Width="30"
|
||||
Height="28"
|
||||
Padding="4"
|
||||
HorizontalAlignment="Center"
|
||||
Command="{Binding ShowShopRevenueStatisticsDialogViewCommand}"
|
||||
Style="{StaticResource BasePageButtonStyle}"
|
||||
ToolTip="利润统计">
|
||||
<TextBlock Style="{StaticResource TextBlockIcon}" Text="{StaticResource txt-data_pie}" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
Grid.Column="13"
|
||||
Width="60"
|
||||
Height="28"
|
||||
Padding="4"
|
||||
HorizontalAlignment="Center"
|
||||
Click="btn_ShowShopRevenueStatisticsView"
|
||||
Content="利润统计"
|
||||
Style="{StaticResource BasePageButtonStyle}" />
|
||||
<Button
|
||||
Width="30"
|
||||
Height="28"
|
||||
Padding="4"
|
||||
HorizontalAlignment="Center"
|
||||
Command="{Binding ShowPDDInvoiceDialogCommand}"
|
||||
Style="{StaticResource BasePageButtonStyle}"
|
||||
ToolTip="发票助手">
|
||||
<TextBlock Style="{StaticResource TextBlockIcon}" Text="{StaticResource txt-fapiao}" />
|
||||
</Button>
|
||||
</wpf:SpacingStackPanel>
|
||||
</Grid>
|
||||
|
||||
<Border
|
||||
|
||||
@@ -2149,22 +2149,7 @@ namespace _29WxOrderWpf.Ctrls.OrderCtrls
|
||||
FilterOrders();
|
||||
}
|
||||
|
||||
private DialogWindow _statisticsWindow;
|
||||
private void btn_ShowShopRevenueStatisticsView(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_statisticsWindow == null)
|
||||
{
|
||||
_statisticsWindow = new DialogWindow()
|
||||
{
|
||||
Title = "统计",
|
||||
Content = new ShopRevenueStatisticsView(),
|
||||
|
||||
};
|
||||
_statisticsWindow.Closed += (s, e) => _statisticsWindow = null;
|
||||
}
|
||||
_statisticsWindow.Show();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -2268,263 +2253,7 @@ namespace _29WxOrderWpf.Ctrls.OrderCtrls
|
||||
// set => SetProperty(ref _IsShipGift, value);
|
||||
//}
|
||||
|
||||
public ICommand MultipleShipGiftCommand => new AsyncDelegateCommand(MultipleShipGift);
|
||||
/// <summary>
|
||||
/// 批量发送礼品单
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拉取微信订单到任务列表
|
||||
/// </summary>
|
||||
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<long, List<oms_wei_order_info>> 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
|
||||
|
||||
@@ -0,0 +1,868 @@
|
||||
<UserControl
|
||||
x:Class="_29WxOrderWpf.Ctrls.PDDInvoice.PDDInvoiceView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:comm="clr-namespace:_29WxOrderWpf.Common"
|
||||
xmlns:common="clr-namespace:_29WxOrderWpf.Common"
|
||||
xmlns:converters="clr-namespace:_29WxOrderWpf.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:_29WxOrderWpf.Ctrls.PDDInvoice.ViewModels"
|
||||
xmlns:wpf="clr-namespace:WPFCommon.Controls;assembly=WPFCommon"
|
||||
comm:DialogWindowProperty.ResizeMode="CanResizeWithGrip"
|
||||
comm:DialogWindowProperty.ShowMinButton="False"
|
||||
common:DialogWindowProperty.Height="720"
|
||||
common:DialogWindowProperty.Width="1260"
|
||||
d:DataContext="{d:DesignInstance Type=vm:PDDInvoiceViewModel,
|
||||
IsDesignTimeCreatable=True}"
|
||||
d:DesignHeight="720"
|
||||
d:DesignWidth="1260"
|
||||
Background="White"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<UserControl.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="BooleanToVisibility" />
|
||||
<converters:ImageUrlToBitmapImageConverter x:Key="ImageUrlToBitmapImage" />
|
||||
<converters:InverseVisibleToVisibilityConverter x:Key="InverseBooleanToVisibility" />
|
||||
|
||||
<!-- 原型配色 -->
|
||||
<SolidColorBrush x:Key="PageBgBrush" Color="#F5F6F8" />
|
||||
<SolidColorBrush x:Key="CardBgBrush" Color="#FFFFFF" />
|
||||
<SolidColorBrush x:Key="RowBgBrush" Color="#FAFAFA" />
|
||||
<SolidColorBrush x:Key="SplitLineBrush" Color="#EBEEF5" />
|
||||
<SolidColorBrush x:Key="InputBorderBrush" Color="#DCDFE6" />
|
||||
<SolidColorBrush x:Key="PrimaryBrush" Color="#409EFF" />
|
||||
<SolidColorBrush x:Key="PrimaryHoverBrush" Color="#66B1FF" />
|
||||
<SolidColorBrush x:Key="TextPrimaryBrush" Color="#333333" />
|
||||
<SolidColorBrush x:Key="TextNormalBrush" Color="#666666" />
|
||||
<SolidColorBrush x:Key="TextSecondaryBrush" Color="#909399" />
|
||||
<SolidColorBrush x:Key="TextPlaceholderBrush" Color="#C0C4CC" />
|
||||
<SolidColorBrush x:Key="AmountBrush" Color="#F56C6C" />
|
||||
<SolidColorBrush x:Key="SuccessBrush" Color="#67C23A" />
|
||||
<SolidColorBrush x:Key="WarningBrush" Color="#E6A23C" />
|
||||
|
||||
<!-- 表头单元格 -->
|
||||
<Style x:Key="HeaderCellTextStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="Foreground" Value="{StaticResource TextNormalBrush}" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<!-- 搜索 / 表单输入框 -->
|
||||
<Style x:Key="SearchTextBoxStyle" TargetType="TextBox">
|
||||
<Setter Property="Height" Value="36" />
|
||||
<Setter Property="Padding" Value="12,0" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}" />
|
||||
<Setter Property="Background" Value="White" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource InputBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="TextBox">
|
||||
<Border
|
||||
x:Name="border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="4">
|
||||
<ScrollViewer x:Name="PART_ContentHost" Margin="{TemplateBinding Padding}" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsKeyboardFocused" Value="True">
|
||||
<Setter TargetName="border" Property="BorderBrush" Value="{StaticResource PrimaryBrush}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.6" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- 主按钮 -->
|
||||
<Style x:Key="PrimaryButtonStyle" TargetType="Button">
|
||||
<Setter Property="Height" Value="36" />
|
||||
<Setter Property="Padding" Value="18,0" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter Property="Background" Value="{StaticResource PrimaryBrush}" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="border"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Background="{TemplateBinding Background}"
|
||||
CornerRadius="4">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="border" Property="Background" Value="{StaticResource PrimaryHoverBrush}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.8" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.5" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- 默认按钮 -->
|
||||
<Style x:Key="DefaultButtonStyle" TargetType="Button">
|
||||
<Setter Property="Height" Value="36" />
|
||||
<Setter Property="Padding" Value="18,0" />
|
||||
<Setter Property="Foreground" Value="{StaticResource TextNormalBrush}" />
|
||||
<Setter Property="Background" Value="White" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource InputBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="border"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="4">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="border" Property="Background" Value="#F5F7FA" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.8" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- 文字链接按钮 -->
|
||||
<Style x:Key="LinkButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryBrush}" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryHoverBrush}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- 弹窗遮罩 -->
|
||||
<Style x:Key="ModalMaskStyle" TargetType="Border">
|
||||
<Setter Property="Background" Value="#80000000" />
|
||||
<Setter Property="Panel.ZIndex" Value="100" />
|
||||
</Style>
|
||||
|
||||
<!-- 发票详情 标签 / 值 -->
|
||||
<Style x:Key="DetailLabelStyle" TargetType="TextBlock">
|
||||
<Setter Property="Width" Value="100" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="Foreground" Value="{StaticResource TextSecondaryBrush}" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style x:Key="DetailValueStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
<Border Padding="10,10,10,0">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 功能区域 -->
|
||||
<Border Grid.Row="0">
|
||||
|
||||
<wpf:SpacingStackPanel Background="{StaticResource CardBgBrush}" HorizontalSpacing="10">
|
||||
<Grid Width="300">
|
||||
<TextBox
|
||||
x:Name="txtSearch"
|
||||
Height="30"
|
||||
Padding="5,0,0,0"
|
||||
KeyDown="txtSearch_KeyDown"
|
||||
Style="{StaticResource SearchTextBoxStyle}"
|
||||
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock
|
||||
Padding="12,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="13"
|
||||
Foreground="{StaticResource TextPlaceholderBrush}"
|
||||
IsHitTestVisible="False"
|
||||
Text="请输入订单号 / 商品名称"
|
||||
Visibility="{Binding Text, ElementName=txtSearch, Converter={StaticResource NotHasTextVisibility}}" />
|
||||
</Grid>
|
||||
<Button
|
||||
Width="50"
|
||||
Height="30"
|
||||
Command="{Binding SearchCommand}"
|
||||
Content="搜索"
|
||||
Style="{StaticResource BasePageButtonStyle}" />
|
||||
<Button
|
||||
Height="30"
|
||||
Command="{Binding ResetCommand}"
|
||||
Content="重置"
|
||||
Style="{StaticResource DefaultButtonStyle}" />
|
||||
|
||||
<wpf:TitleContent
|
||||
Title="买手号:"
|
||||
Placement="Left"
|
||||
TitleVerticalAlignment="Center">
|
||||
<ComboBox
|
||||
Width="150"
|
||||
Height="28"
|
||||
DisplayMemberPath="buyer_nick"
|
||||
ItemsSource="{Binding BuyerLoginList}"
|
||||
SelectedItem="{Binding SelectedBuyer}"
|
||||
Style="{StaticResource RoundedComboBox}" />
|
||||
</wpf:TitleContent>
|
||||
<Button
|
||||
Height="30"
|
||||
Padding="5,0"
|
||||
Command="{Binding ReadOrderCommand}"
|
||||
Content="读取订单"
|
||||
Style="{StaticResource BasePageButtonStyle}"
|
||||
Visibility="{Binding IsReadOrder, Converter={StaticResource Boolean2VisibilityReConverter}}" />
|
||||
<Button
|
||||
Height="30"
|
||||
Padding="5,0"
|
||||
Command="{Binding StopReadOrderCommand}"
|
||||
Content="停止读单"
|
||||
Style="{StaticResource BasePageButtonStyle}"
|
||||
Visibility="{Binding IsReadOrder, Converter={StaticResource Boolean2VisibilityConverter}}" />
|
||||
<Button
|
||||
Height="30"
|
||||
Padding="10,0"
|
||||
Command="{Binding BatchInvoiceCommand}"
|
||||
Content="批量开票"
|
||||
Style="{StaticResource BasePageButtonStyle}" />
|
||||
|
||||
<wpf:TitleContent
|
||||
Title="公司:"
|
||||
Placement="Left"
|
||||
TitleVerticalAlignment="Center">
|
||||
<TextBox
|
||||
Width="150"
|
||||
Height="30"
|
||||
Padding="5,0"
|
||||
VerticalContentAlignment="Center"
|
||||
Text="{Binding InvoiceTitle}" />
|
||||
</wpf:TitleContent>
|
||||
<wpf:TitleContent
|
||||
Title="税号:"
|
||||
Placement="Left"
|
||||
TitleVerticalAlignment="Center">
|
||||
<TextBox
|
||||
Width="150"
|
||||
Height="30"
|
||||
Padding="5,0"
|
||||
VerticalContentAlignment="Center"
|
||||
Text="{Binding TaxNo}" />
|
||||
</wpf:TitleContent>
|
||||
<CheckBox Content="显示订单窗口" IsChecked="{Binding IsOrderListDialogOpen}" />
|
||||
</wpf:SpacingStackPanel>
|
||||
</Border>
|
||||
<!-- 订单列表(已移除分页与订单分组) -->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Height="Auto"
|
||||
Margin="0,10,0,0"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="{StaticResource CardBgBrush}"
|
||||
CornerRadius="6">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
|
||||
<!-- 表头 -->
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
Padding="20,14"
|
||||
Background="{StaticResource RowBgBrush}"
|
||||
BorderBrush="{StaticResource SplitLineBrush}"
|
||||
BorderThickness="0,0,0,1">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="30" />
|
||||
<ColumnDefinition Width="3*" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="80" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
<ColumnDefinition Width="1.2*" />
|
||||
<ColumnDefinition Width="1.5*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<CheckBox
|
||||
VerticalAlignment="Center"
|
||||
FontSize="13"
|
||||
Foreground="{StaticResource TextNormalBrush}"
|
||||
IsChecked="{Binding IsSelectAll}" />
|
||||
<!--<Button
|
||||
Margin="8,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding InvertSelectionCommand}"
|
||||
Content="反选"
|
||||
Style="{StaticResource LinkButtonStyle}" />-->
|
||||
</StackPanel>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource HeaderCellTextStyle}"
|
||||
Text="商品信息" />
|
||||
<TextBlock
|
||||
Grid.Column="3"
|
||||
Style="{StaticResource HeaderCellTextStyle}"
|
||||
Text="数量" />
|
||||
<TextBlock
|
||||
Grid.Column="4"
|
||||
Style="{StaticResource HeaderCellTextStyle}"
|
||||
Text="实付金额" />
|
||||
<TextBlock
|
||||
Grid.Column="5"
|
||||
Style="{StaticResource HeaderCellTextStyle}"
|
||||
Text="发票" />
|
||||
<TextBlock
|
||||
Grid.Column="6"
|
||||
Style="{StaticResource HeaderCellTextStyle}"
|
||||
Text="操作" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<Grid Grid.Row="1">
|
||||
<Border Background="White">
|
||||
<!-- 列表(虚拟化滚动加载) -->
|
||||
<ItemsControl
|
||||
Margin="0,2,0,-2"
|
||||
Padding="0"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
ItemsSource="{Binding Orders}">
|
||||
<ItemsControl.Template>
|
||||
<ControlTemplate TargetType="ItemsControl">
|
||||
<ScrollViewer
|
||||
CanContentScroll="True"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<ItemsPresenter />
|
||||
</ScrollViewer>
|
||||
</ControlTemplate>
|
||||
</ItemsControl.Template>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel
|
||||
CacheLength="1,1"
|
||||
CacheLengthUnit="Page"
|
||||
IsVirtualizing="True"
|
||||
ScrollUnit="Pixel"
|
||||
VirtualizationMode="Recycling" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border BorderBrush="{StaticResource SplitLineBrush}" BorderThickness="0,0,0,1">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected}" Value="True">
|
||||
<Setter Property="Background" Value="#ECF5FF" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
<StackPanel>
|
||||
<!-- 订单信息栏 -->
|
||||
<Border Padding="20,12" Background="{StaticResource RowBgBrush}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
FontSize="12"
|
||||
Foreground="{StaticResource TextNormalBrush}"
|
||||
Text="{Binding order_sn, StringFormat=订单号:{0}}" />
|
||||
<TextBlock
|
||||
Margin="28,0,0,0"
|
||||
FontSize="12"
|
||||
Foreground="{StaticResource TextNormalBrush}"
|
||||
Text="{Binding OrderTime, StringFormat=下单时间:{0:yyyy-MM-dd}}" />
|
||||
<TextBlock
|
||||
Margin="28,0,0,0"
|
||||
FontSize="12"
|
||||
Foreground="{StaticResource TextNormalBrush}"
|
||||
Text="{Binding order_status_prompt}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 订单内容 -->
|
||||
<Border Padding="20">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="30" />
|
||||
<ColumnDefinition Width="3*" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="80" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
<ColumnDefinition Width="1.2*" />
|
||||
<ColumnDefinition Width="1.5*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 勾选 -->
|
||||
<CheckBox
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding IsSelected}" />
|
||||
|
||||
<!-- 商品 -->
|
||||
<Grid Grid.Column="1" VerticalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="72" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border
|
||||
Width="72"
|
||||
Height="72"
|
||||
Background="#EEEEEE"
|
||||
CornerRadius="5">
|
||||
<Image Source="{Binding ThumbUrl, Converter={StaticResource ImageUrlToBitmapImage}, IsAsync=True}" Stretch="UniformToFill" />
|
||||
</Border>
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
Margin="14,0,0,0"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock
|
||||
MaxHeight="44"
|
||||
FontSize="14"
|
||||
Foreground="{StaticResource TextPrimaryBrush}"
|
||||
LineHeight="22"
|
||||
Text="{Binding GoodsName}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
|
||||
<!-- 数量 -->
|
||||
<TextBlock
|
||||
Grid.Column="3"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="13"
|
||||
Foreground="{StaticResource TextPrimaryBrush}"
|
||||
Text="{Binding Quantity, StringFormat=×{0}}" />
|
||||
|
||||
<!-- 实付金额 -->
|
||||
<TextBlock
|
||||
Grid.Column="4"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{StaticResource AmountBrush}"
|
||||
Text="{Binding DisplayAmount}" />
|
||||
|
||||
<!-- 发票 -->
|
||||
<StackPanel Grid.Column="5" VerticalAlignment="Center">
|
||||
<TextBlock FontSize="13" Text="{Binding InvoiceStatusText}">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="{StaticResource WarningBrush}" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsInvoiced}" Value="True">
|
||||
<Setter Property="Foreground" Value="{StaticResource SuccessBrush}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 操作 -->
|
||||
<StackPanel Grid.Column="6" VerticalAlignment="Center">
|
||||
<Button
|
||||
d:Visibility="Visible"
|
||||
Command="{Binding DataContext.ShowInvoiceCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
CommandParameter="{Binding}"
|
||||
Content="查看发票"
|
||||
Style="{StaticResource LinkButtonStyle}"
|
||||
Visibility="{Binding IsInvoiced, Converter={StaticResource BooleanToVisibility}}" />
|
||||
<Button
|
||||
Margin="0,5"
|
||||
Command="{Binding DataContext.OpenInvoiceCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
CommandParameter="{Binding}"
|
||||
Content="申请开票"
|
||||
Style="{StaticResource LinkButtonStyle}"
|
||||
Visibility="{Binding IsInvoiced, Converter={StaticResource InverseBooleanToVisibility}}" />
|
||||
<Button
|
||||
Command="{Binding DataContext.ViewOrderCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
CommandParameter="{Binding}"
|
||||
Content="查看订单"
|
||||
Style="{StaticResource LinkButtonStyle}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Border>
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Panel.ZIndex="{Binding BrowserZIndex}"
|
||||
d:Panel.ZIndex="-10"
|
||||
Style="{StaticResource ModalMaskStyle}">
|
||||
<Grid>
|
||||
<ContentControl
|
||||
Grid.Row="0"
|
||||
Width="400"
|
||||
Height="600"
|
||||
Background="White"
|
||||
Content="{Binding Browser}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Panel.ZIndex="{Binding BrowserDetailZIndex}"
|
||||
d:Panel.ZIndex="-10"
|
||||
Style="{StaticResource ModalMaskStyle}">
|
||||
<Grid
|
||||
Width="400"
|
||||
Height="600"
|
||||
Background="White">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Border
|
||||
Padding="10"
|
||||
BorderBrush="LightGray"
|
||||
BorderThickness="0,0,0,1">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="50" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
FontSize="16"
|
||||
FontWeight="Black"
|
||||
Text="{Binding SelectedOrder.GoodsName}" />
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
Command="{Binding CloseInvoiceDetailCommand}"
|
||||
Content="关闭"
|
||||
Style="{StaticResource LinkButtonStyle}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ContentControl Grid.Row="1" Content="{Binding BrowserDetail}" />
|
||||
|
||||
</Grid>
|
||||
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Grid
|
||||
Grid.Row="2"
|
||||
Height="18"
|
||||
Margin="-10,5,-10,0"
|
||||
Background="{StaticResource pageColorBrush}">
|
||||
<ProgressBar
|
||||
VerticalAlignment="Stretch"
|
||||
d:IsIndeterminate="False"
|
||||
d:Maximum="100"
|
||||
d:Value="10"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Foreground="#FFA680"
|
||||
Maximum="{Binding ProgressMaximum}"
|
||||
Value="{Binding ProgressValue}" />
|
||||
<Grid Margin="10,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="100*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBox
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="4,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
d:Text="共计:{}"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Foreground="White"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding BottomMsg}" />
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
<!-- 开票弹窗 -->
|
||||
<!--<Border
|
||||
Grid.RowSpan="3"
|
||||
Style="{StaticResource ModalMaskStyle}"
|
||||
Visibility="{Binding IsInvoiceDialogOpen, Converter={StaticResource BooleanToVisibility}}">
|
||||
<Border
|
||||
Width="520"
|
||||
Padding="24"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Background="White"
|
||||
CornerRadius="8">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
Margin="0,0,0,24"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{StaticResource TextPrimaryBrush}"
|
||||
Text="开具发票" />
|
||||
|
||||
<Grid Margin="0,0,0,16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="90" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="13"
|
||||
Foreground="{StaticResource TextNormalBrush}"
|
||||
Text="订单号" />
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="13"
|
||||
Foreground="{StaticResource TextPrimaryBrush}"
|
||||
Text="{Binding SelectedOrder.order_sn}" />
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="0,0,0,16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="90" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="13"
|
||||
Foreground="{StaticResource TextNormalBrush}"
|
||||
Text="发票类型" />
|
||||
<ComboBox
|
||||
Grid.Column="1"
|
||||
Height="36"
|
||||
Padding="10,0"
|
||||
VerticalContentAlignment="Center"
|
||||
FontSize="13"
|
||||
SelectedValue="{Binding InvoiceType}"
|
||||
SelectedValuePath="Content">
|
||||
<ComboBoxItem Content="电子普通发票" />
|
||||
<ComboBoxItem Content="增值税专用发票" />
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="0,0,0,16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="90" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="13"
|
||||
Foreground="{StaticResource TextNormalBrush}"
|
||||
Text="发票抬头" />
|
||||
<TextBox
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource SearchTextBoxStyle}"
|
||||
Text="{Binding InvoiceTitle}" />
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="0,0,0,16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="90" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="13"
|
||||
Foreground="{StaticResource TextNormalBrush}"
|
||||
Text="税号" />
|
||||
<TextBox
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource SearchTextBoxStyle}"
|
||||
Text="{Binding TaxNo}" />
|
||||
</Grid>
|
||||
|
||||
<StackPanel
|
||||
Margin="0,25,0,0"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
Command="{Binding CloseInvoiceDialogCommand}"
|
||||
Content="取消"
|
||||
Style="{StaticResource DefaultButtonStyle}" />
|
||||
<Button
|
||||
Margin="10,0,0,0"
|
||||
Command="{Binding ConfirmInvoiceCommand}"
|
||||
Content="确认开票"
|
||||
Style="{StaticResource PrimaryButtonStyle}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Border>-->
|
||||
|
||||
<!-- 发票信息弹窗 -->
|
||||
<!--<Border
|
||||
Grid.RowSpan="3"
|
||||
Style="{StaticResource ModalMaskStyle}"
|
||||
Visibility="{Binding IsInvoiceDetailOpen, Converter={StaticResource BooleanToVisibility}}">
|
||||
<Border
|
||||
Width="520"
|
||||
Padding="24"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Background="White"
|
||||
CornerRadius="8">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
Margin="0,0,0,24"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{StaticResource TextPrimaryBrush}"
|
||||
Text="发票信息" />
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="30" />
|
||||
<RowDefinition Height="30" />
|
||||
<RowDefinition Height="30" />
|
||||
<RowDefinition Height="30" />
|
||||
<RowDefinition Height="30" />
|
||||
<RowDefinition Height="30" />
|
||||
<RowDefinition Height="30" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Style="{StaticResource DetailLabelStyle}"
|
||||
Text="订单号:" />
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource DetailValueStyle}"
|
||||
Text="{Binding SelectedOrder.OrderNo}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Style="{StaticResource DetailLabelStyle}"
|
||||
Text="发票类型:" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource DetailValueStyle}"
|
||||
Text="{Binding SelectedOrder.InvoiceType}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Style="{StaticResource DetailLabelStyle}"
|
||||
Text="发票号码:" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource DetailValueStyle}"
|
||||
Text="{Binding SelectedOrder.InvoiceNo}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Style="{StaticResource DetailLabelStyle}"
|
||||
Text="发票抬头:" />
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource DetailValueStyle}"
|
||||
Text="{Binding SelectedOrder.InvoiceTitle}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Style="{StaticResource DetailLabelStyle}"
|
||||
Text="税号:" />
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource DetailValueStyle}"
|
||||
Text="{Binding SelectedOrder.TaxNo}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="5"
|
||||
Style="{StaticResource DetailLabelStyle}"
|
||||
Text="开票金额:" />
|
||||
<TextBlock
|
||||
Grid.Row="5"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource DetailValueStyle}"
|
||||
Text="{Binding SelectedOrder.InvoiceAmountText}" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="6"
|
||||
Style="{StaticResource DetailLabelStyle}"
|
||||
Text="开票时间:" />
|
||||
<TextBlock
|
||||
Grid.Row="6"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource DetailValueStyle}"
|
||||
Text="{Binding SelectedOrder.InvoiceTime}" />
|
||||
</Grid>
|
||||
|
||||
<StackPanel
|
||||
Margin="0,25,0,0"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
Command="{Binding CloseInvoiceDetailCommand}"
|
||||
Content="关闭"
|
||||
Style="{StaticResource DefaultButtonStyle}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Border>-->
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// PDDInvoiceView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
// 填写信息
|
||||
function changeReactInputValue(inputDom, newText) {
|
||||
let lastValue = inputDom.value;
|
||||
inputDom.value = newText;
|
||||
let event = new Event('input', { bubbles: true });
|
||||
event.simulated = true;
|
||||
let tracker = inputDom._valueTracker;
|
||||
if (tracker) {
|
||||
tracker.setValue(lastValue);
|
||||
}
|
||||
inputDom.dispatchEvent(event);
|
||||
}
|
||||
|
||||
// 异步 sleep
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
// 查找元素(支持选择器或XPath)
|
||||
function findElement(selector, isXPath = true) {
|
||||
if (isXPath) {
|
||||
return document.evaluate(
|
||||
selector,
|
||||
document,
|
||||
null,
|
||||
XPathResult.FIRST_ORDERED_NODE_TYPE,
|
||||
null
|
||||
).singleNodeValue;
|
||||
} else {
|
||||
return document.querySelector(selector);
|
||||
}
|
||||
}
|
||||
|
||||
// 完整流程
|
||||
async function fillInvoiceAndApply(data = {}) {
|
||||
const {
|
||||
title = '某某科技有限公司',
|
||||
taxNumber = '91110000123456789X'
|
||||
} = data;
|
||||
|
||||
console.log('📝 开始填充发票信息...');
|
||||
|
||||
// 1. 点击"企业"按钮
|
||||
const invoiceType = findElement('//*[contains(text(), "企业")]');
|
||||
if (!invoiceType) {
|
||||
console.warn('⚠️ 找不到"企业"按钮');
|
||||
return false;
|
||||
}
|
||||
|
||||
invoiceType.click();
|
||||
console.log('✅ 已点击"企业"按钮');
|
||||
await sleep(500);
|
||||
|
||||
// 2. 查找并填充发票抬头
|
||||
const invoiceTitle = findElement('//label[contains(text(), "发票抬头")]/following-sibling::textarea[1]') ||
|
||||
findElement('//*[contains(text(), "发票抬头")]/following-sibling::textarea[1]');
|
||||
if (invoiceTitle) {
|
||||
changeReactInputValue(invoiceTitle, title);
|
||||
console.log('✅ 发票抬头已填充');
|
||||
} else {
|
||||
console.warn('⚠️ 未找到发票抬头控件');
|
||||
}
|
||||
|
||||
// 3. 查找并填充税号
|
||||
const taxNumberInput = findElement('//label[contains(text(), "税号")]/following-sibling::input[1]') ||
|
||||
findElement('//*[contains(text(), "税号")]/following-sibling::input[1]');
|
||||
if (taxNumberInput) {
|
||||
changeReactInputValue(taxNumberInput, taxNumber);
|
||||
console.log('✅ 税号已填充');
|
||||
} else {
|
||||
console.warn('⚠️ 未找到税号控件');
|
||||
}
|
||||
|
||||
await sleep(300);
|
||||
|
||||
// 4. 查找并点击"申请发票"按钮
|
||||
const applyBtn = findElement('//*[contains(text(), "申请发票")]');
|
||||
if (!applyBtn) {
|
||||
console.error('❌ 未找到"申请发票"按钮');
|
||||
return false;
|
||||
}
|
||||
|
||||
applyBtn.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
await sleep(300);
|
||||
applyBtn.click();
|
||||
console.log('✅ 已点击"申请发票"按钮');
|
||||
|
||||
// 5. 等待弹窗/状态出现
|
||||
await sleep(300);
|
||||
|
||||
// 6. 判断状态
|
||||
// 检查是否有"已经申请过"的提示
|
||||
const alreadyApplied = findElement('//*[contains(text(), "您已经提交过发票申请了")]')
|
||||
|
||||
|
||||
if (alreadyApplied) {
|
||||
console.log('✅ 已经申请过,无需重复操作');
|
||||
return true;
|
||||
}
|
||||
const successElement = findElement('//*[text()="确认您的发票信息无误后再提交"]');
|
||||
if (successElement == null) {
|
||||
return false;
|
||||
}
|
||||
// 检查是否有"确认开具"按钮或提示
|
||||
const confirmBtn = findElement('//*[text()= "确认"]');
|
||||
|
||||
if (confirmBtn) {
|
||||
console.log('✅ 找到"确认"按钮,准备点击...');
|
||||
await sleep(200);
|
||||
confirmBtn.click();
|
||||
console.log('✅ 已点击"确认"按钮');
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果都没有找到
|
||||
console.warn('⚠️ 未找到"已经申请过"或"确认"按钮,请手动检查');
|
||||
return false;
|
||||
}
|
||||
|
||||
// ========== 调用 ==========
|
||||
(async function () {
|
||||
try {
|
||||
const result = await fillInvoiceAndApply({
|
||||
title: '{公司}',
|
||||
taxNumber: '{税号}'
|
||||
});
|
||||
console.log('执行结果:', result);
|
||||
// 批量开票时,C# 端轮询该变量获取执行结果
|
||||
window.__invoiceApplyResult = result;
|
||||
} catch (ex) {
|
||||
console.error('执行异常:', ex);
|
||||
window.__invoiceApplyResult = false;
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,94 @@
|
||||
using AllBase.Model.Result;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using WPFCommon.Helpers;
|
||||
using WPFCommon.Models;
|
||||
|
||||
namespace _29WxOrderWpf.Ctrls.PDDInvoice.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// 订单发票Model
|
||||
/// </summary>
|
||||
public class PddBrowserOrderInvoiceViewModel : BindableBaseEx
|
||||
{
|
||||
private bool _isSelected;
|
||||
|
||||
/// <summary>
|
||||
/// 是否被勾选(用于批量开票)
|
||||
/// </summary>
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set => SetProperty(ref _isSelected, value, nameof(IsSelected));
|
||||
}
|
||||
|
||||
public string buyerId { get; set; } // 买手Id
|
||||
public string buyerName { get; set; } // 买手名称
|
||||
/// <summary>
|
||||
/// 订单价
|
||||
/// </summary>
|
||||
public int order_amount { get; set; }
|
||||
/// <summary>
|
||||
/// 实付款
|
||||
/// </summary>
|
||||
public string display_amount { get; set; }
|
||||
/// <summary>
|
||||
/// 下单时间
|
||||
/// </summary>
|
||||
public long order_time { get; set; }
|
||||
/// <summary>
|
||||
/// 订单号
|
||||
/// </summary>
|
||||
public string order_sn { get; set; }
|
||||
|
||||
public int status { get; set; }
|
||||
public int pay_status { get; set; }
|
||||
public string order_status_prompt { get; set; }
|
||||
/// <summary>
|
||||
/// 订单详情
|
||||
/// </summary>
|
||||
public string order_link_url { get; set; }
|
||||
public List<order_good> order_Goods { get; set; }
|
||||
|
||||
|
||||
public List<OrderButtonItem> order_buttons { get; set; }
|
||||
|
||||
|
||||
public string GoodsName { get => order_Goods?.FirstOrDefault()?.goods_name ?? ""; }
|
||||
/// <summary>
|
||||
/// 是否已开票
|
||||
/// </summary>
|
||||
public bool IsInvoiced
|
||||
{
|
||||
get => GetValue<bool>();
|
||||
set
|
||||
{
|
||||
SetValue(value);
|
||||
base.RaisePropertyChanged(nameof(InvoiceStatusText));
|
||||
}
|
||||
}
|
||||
|
||||
public int Quantity => order_Goods?.Sum(a => a.goods_number.Value) ?? 0;
|
||||
/// <summary> 发票状态文案:已开票 / 待开票 </summary>
|
||||
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; }
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
using _29WxOrderWpf.CefHelper;
|
||||
using _29WxOrderWpf.Commands;
|
||||
using AllBase;
|
||||
using CefSharp;
|
||||
using CefSharp.Handler;
|
||||
using CefSharp.Wpf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using Work._29Tools.CefTools;
|
||||
using WxOrder.Framework.Helpers;
|
||||
|
||||
namespace _29WxOrderWpf.Ctrls.PDDInvoice.ViewModels;
|
||||
|
||||
public partial class PDDInvoiceViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 订单列表浏览器
|
||||
/// </summary>
|
||||
public ChromiumWebBrowser Browser { get; private set; }
|
||||
/// <summary>
|
||||
/// 内容浏览器
|
||||
/// 订单详情
|
||||
/// 发票详情
|
||||
/// 申请发票
|
||||
/// </summary>
|
||||
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();
|
||||
Browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
||||
|
||||
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();
|
||||
BrowserDetail.LoadingStateChanged += BrowserDetail_LoadingStateChanged;
|
||||
}
|
||||
|
||||
private void Browser_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e)
|
||||
{
|
||||
if (e.IsLoading == false)
|
||||
{
|
||||
var b = (ChromiumWebBrowser)sender;
|
||||
b.Dispatcher.InvokeAsync(async () =>
|
||||
{
|
||||
if (b.Address != null && b.Address.StartsWith("https://mobile.yangkeduo.com/orders.html"))
|
||||
{
|
||||
StartAutoScroll();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void BrowserDetail_LoadingStateChanged(object sender, LoadingStateChangedEventArgs e)
|
||||
{
|
||||
if (e.IsLoading == false)
|
||||
{
|
||||
var b = (ChromiumWebBrowser)sender;
|
||||
b.Dispatcher.InvokeAsync(async () =>
|
||||
{
|
||||
if (b.Address != null && b.Address.StartsWith("https://mobile.yangkeduo.com/transac_return_compensation.html"))
|
||||
{
|
||||
string html = await b.GetBrowser().GetSourceAsync();
|
||||
if (html.Contains("window.rawData"))
|
||||
{
|
||||
await ExecuteInvoiceApplyScriptAsync(b);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 页面异常(未渲染出发票表单)时直接判定失败,避免批量开票长时间等待
|
||||
_currentInvoiceTcs?.TrySetResult(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注入发票填写脚本并执行。
|
||||
/// 单张申请模式保持原有行为;批量开票模式下轮询脚本写入的
|
||||
/// <c>window.__invoiceApplyResult</c>,完成后通过 <see cref="_currentInvoiceTcs"/> 回传结果。
|
||||
/// </summary>
|
||||
private async Task ExecuteInvoiceApplyScriptAsync(ChromiumWebBrowser b)
|
||||
{
|
||||
string content = ResourceHelper.GetResourceText("/Ctrls/PDDInvoice/Resources/invoice.js", "29WxFindOrder");
|
||||
content = content.Replace("{公司}", InvoiceTitle);
|
||||
content = content.Replace("{税号}", TaxNo);
|
||||
if (string.IsNullOrEmpty(content)) return;
|
||||
|
||||
await b.EvaluateScriptAsync(content);
|
||||
|
||||
if (_currentInvoiceTcs == null) return; // 单张申请模式:保持原有行为
|
||||
|
||||
// 批量模式:等待异步脚本执行完成
|
||||
// 捕获当前 TCS,避免批量循环超时进入下一个订单后,迟到的轮询误触发新订单的结果
|
||||
var tcs = _currentInvoiceTcs;
|
||||
for (int i = 0; i < 120; i++)
|
||||
{
|
||||
await Task.Delay(500);
|
||||
try
|
||||
{
|
||||
var res = await b.EvaluateScriptAsync("window.__invoiceApplyResult");
|
||||
if (res.Success && res.Result != null)
|
||||
{
|
||||
tcs.TrySetResult(Convert.ToBoolean(res.Result));
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
tcs.TrySetResult(false);
|
||||
}
|
||||
|
||||
public ICommand CloseInvoiceDetailCommand => new DelegateCommand(HidenOrderBrowserDetail);
|
||||
|
||||
public int BrowserZIndex
|
||||
{
|
||||
get => GetValue<int>();
|
||||
set => SetValue(value);
|
||||
}
|
||||
public int BrowserDetailZIndex
|
||||
{
|
||||
get => GetValue<int>();
|
||||
set => SetValue(value);
|
||||
}
|
||||
/// <summary>
|
||||
/// 显示主浏览器
|
||||
/// </summary>
|
||||
public void ShowOrderBrowser()
|
||||
{
|
||||
BrowserZIndex = 10;
|
||||
BrowserDetailZIndex = -1;
|
||||
}
|
||||
/// <summary>
|
||||
/// 显示子浏览器
|
||||
/// </summary>
|
||||
public void ShowOrderBrowserDetail()
|
||||
{
|
||||
BrowserZIndex = -1;
|
||||
BrowserDetailZIndex = 10;
|
||||
}
|
||||
public void HidenOrderBrowser()
|
||||
{
|
||||
BrowserZIndex = -1;
|
||||
}
|
||||
public void HidenOrderBrowserDetail()
|
||||
{
|
||||
BrowserDetailZIndex = -1;
|
||||
}
|
||||
public bool IsReadOrder
|
||||
{
|
||||
get => GetValue<bool>();
|
||||
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 (IsReadOrder) return;
|
||||
|
||||
IsReadOrder = true;
|
||||
_ = autoScroll(_cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
// 停止滚动
|
||||
public void StopAutoScroll()
|
||||
{
|
||||
IsReadOrder = false;
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
}
|
||||
|
||||
private async Task autoScroll(CancellationToken token)
|
||||
{
|
||||
while (IsReadOrder && !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"))
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,633 @@
|
||||
using _29WxOrderWpf.BaseWindows;
|
||||
using _29WxOrderWpf.CefHelper;
|
||||
using _29WxOrderWpf.Commands;
|
||||
using _29WxOrderWpf.Common;
|
||||
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.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime;
|
||||
using System.Text.RegularExpressions;
|
||||
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.Converters;
|
||||
using WPFCommon.Models;
|
||||
using WxOrder.Framework.Helpers;
|
||||
|
||||
namespace _29WxOrderWpf.Ctrls.PDDInvoice.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// 拼多多发票助手 视图模型
|
||||
/// </summary>
|
||||
public partial class PDDInvoiceViewModel : BindableBaseEx
|
||||
{
|
||||
private readonly ObservableCollection<PddBrowserOrderInvoiceViewModel> _allOrders = new();
|
||||
private readonly HashSet<string> _orderSns = new();
|
||||
private string _searchText = "";
|
||||
private PddBrowserOrderInvoiceViewModel _selectedOrder;
|
||||
private bool _isInvoiceDialogOpen;
|
||||
private bool _isInvoiceDetailOpen;
|
||||
private string _invoiceTitle = "";
|
||||
private string _taxNo = "";
|
||||
private CancellationTokenSource _cancellationTokenSource; public PDDInvoiceViewModel()
|
||||
{
|
||||
Orders = new ObservableCollection<PddBrowserOrderInvoiceViewModel>();
|
||||
Orders.CollectionChanged += Orders_CollectionChanged;
|
||||
|
||||
BuyerLoginList = GlobalConfig.PDDBuyerLoginList;
|
||||
//Orders = new ObservableCollection<PDDInvoiceOrderModel>(_allOrders);
|
||||
|
||||
//_ = InitSampleData();
|
||||
|
||||
InitBrowser();
|
||||
}
|
||||
partial void InitBrowser();
|
||||
|
||||
/// <summary> 列表增删时订阅/退订订单勾选状态,用于刷新表头全选状态 </summary>
|
||||
private void Orders_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (e.NewItems != null)
|
||||
{
|
||||
foreach (PddBrowserOrderInvoiceViewModel item in e.NewItems)
|
||||
{
|
||||
item.PropertyChanged += Order_PropertyChanged;
|
||||
}
|
||||
}
|
||||
if (e.OldItems != null)
|
||||
{
|
||||
foreach (PddBrowserOrderInvoiceViewModel item in e.OldItems)
|
||||
{
|
||||
item.PropertyChanged -= Order_PropertyChanged;
|
||||
}
|
||||
}
|
||||
RefreshSelectAllState();
|
||||
}
|
||||
|
||||
private void Order_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(PddBrowserOrderInvoiceViewModel.IsSelected))
|
||||
{
|
||||
RefreshSelectAllState();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public ObservableCollectionThreadSafe<BuyerLoginInfo> BuyerLoginList { get; set; } = new ObservableCollectionThreadSafe<BuyerLoginInfo>();
|
||||
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();
|
||||
|
||||
}
|
||||
|
||||
/// <summary> 订单列表 </summary>
|
||||
public ObservableCollection<PddBrowserOrderInvoiceViewModel> Orders { get; }
|
||||
|
||||
/// <summary> 搜索关键字:订单号 / 商品名称 / 买家 </summary>
|
||||
public string SearchText
|
||||
{
|
||||
get => _searchText;
|
||||
set => SetProperty(ref _searchText, value, nameof(SearchText));
|
||||
}
|
||||
|
||||
/// <summary> 开票弹窗是否打开 </summary>
|
||||
public bool IsOrderListDialogOpen
|
||||
{
|
||||
get => GetValue<bool>();
|
||||
set
|
||||
{
|
||||
SetValue(value);
|
||||
if (value)
|
||||
{
|
||||
ShowOrderBrowser();
|
||||
}
|
||||
else
|
||||
{
|
||||
HidenOrderBrowser();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> 发票详情弹窗是否打开 </summary>
|
||||
public bool IsInvoiceDetailOpen
|
||||
{
|
||||
get => _isInvoiceDetailOpen;
|
||||
set => SetProperty(ref _isInvoiceDetailOpen, value, nameof(IsInvoiceDetailOpen));
|
||||
}
|
||||
|
||||
/// <summary> 当前操作的订单 </summary>
|
||||
public PddBrowserOrderInvoiceViewModel SelectedOrder
|
||||
{
|
||||
get => _selectedOrder;
|
||||
set => SetProperty(ref _selectedOrder, value, nameof(SelectedOrder));
|
||||
}
|
||||
|
||||
/// <summary> 发票抬头 </summary>
|
||||
public string InvoiceTitle
|
||||
{
|
||||
get => _invoiceTitle;
|
||||
set => SetProperty(ref _invoiceTitle, value, nameof(InvoiceTitle));
|
||||
}
|
||||
|
||||
/// <summary> 税号 </summary>
|
||||
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<PddBrowserOrderInvoiceViewModel>(OpenInvoice);
|
||||
|
||||
public ICommand ShowInvoiceCommand => new DelegateCommand<PddBrowserOrderInvoiceViewModel>(ShowInvoice);
|
||||
|
||||
public ICommand ViewOrderCommand => new DelegateCommand<PddBrowserOrderInvoiceViewModel>(ViewOrder);
|
||||
|
||||
|
||||
|
||||
public ICommand ReadOrderCommand => new AsyncDelegateCommand(ReadOrder);
|
||||
|
||||
private async Task ReadOrder()
|
||||
{
|
||||
if (SelectedBuyer == null)
|
||||
{
|
||||
BaseMsgWindow.ShowError("未填选择买手号");
|
||||
return;
|
||||
}
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
Debug.WriteLine("开始读单");
|
||||
BottomMsg = $"开始读取订单";
|
||||
StartProgress();
|
||||
try
|
||||
{
|
||||
if (!IsReadOrder)
|
||||
{
|
||||
_orderSns.Clear();
|
||||
_allOrders.Clear();
|
||||
Orders.Clear();
|
||||
Browser.Load("https://mobile.yangkeduo.com/orders.html");
|
||||
}
|
||||
|
||||
await Task.Delay(Timeout.Infinite, _cancellationTokenSource.Token);
|
||||
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
Debug.WriteLine("停止读单");
|
||||
}
|
||||
//ShowOrderBrowser();
|
||||
}
|
||||
|
||||
public ICommand StopReadOrderCommand => new AsyncDelegateCommand(StopReadOrder);
|
||||
private async Task StopReadOrder()
|
||||
{
|
||||
StopAutoScroll();
|
||||
CompleteProgress();
|
||||
}
|
||||
|
||||
|
||||
#region 勾选 / 全选 / 反选
|
||||
|
||||
private bool? _isSelectAll;
|
||||
private bool _isUpdatingSelectAll;
|
||||
|
||||
/// <summary> 表头全选状态(三态:null 表示部分选中) </summary>
|
||||
public bool? IsSelectAll
|
||||
{
|
||||
get => _isSelectAll;
|
||||
set
|
||||
{
|
||||
if (_isSelectAll == value) return;
|
||||
_isSelectAll = value;
|
||||
RaisePropertyChanged(nameof(IsSelectAll));
|
||||
if (_isUpdatingSelectAll) return;
|
||||
if (value == true)
|
||||
{
|
||||
foreach (var order in Orders) order.IsSelected = true;
|
||||
}
|
||||
else if (value == false)
|
||||
{
|
||||
foreach (var order in Orders) order.IsSelected = false;
|
||||
}
|
||||
RefreshSelectAllState();
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand InvertSelectionCommand => new DelegateCommand(InvertSelection);
|
||||
|
||||
/// <summary> 反选当前可见列表 </summary>
|
||||
private void InvertSelection()
|
||||
{
|
||||
foreach (var order in Orders) order.IsSelected = !order.IsSelected;
|
||||
RefreshSelectAllState();
|
||||
}
|
||||
|
||||
/// <summary> 根据当前可见列表的勾选情况刷新表头全选状态 </summary>
|
||||
private void RefreshSelectAllState()
|
||||
{
|
||||
_isUpdatingSelectAll = true;
|
||||
try
|
||||
{
|
||||
if (Orders.Count == 0)
|
||||
{
|
||||
IsSelectAll = false;
|
||||
return;
|
||||
}
|
||||
int selected = Orders.Count(o => o.IsSelected);
|
||||
IsSelectAll = selected == 0 ? false : selected == Orders.Count ? true : (bool?)null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isUpdatingSelectAll = false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 批量开票
|
||||
|
||||
private bool _isBatchInvoicing;
|
||||
/// <summary> 是否正在批量开票 </summary>
|
||||
public bool IsBatchInvoicing
|
||||
{
|
||||
get => _isBatchInvoicing;
|
||||
set => SetProperty(ref _isBatchInvoicing, value, nameof(IsBatchInvoicing));
|
||||
}
|
||||
|
||||
/// <summary> 当前正在批量处理的订单,页面脚本执行完成后通过它回传结果 </summary>
|
||||
private TaskCompletionSource<bool> _currentInvoiceTcs;
|
||||
|
||||
public ICommand BatchInvoiceCommand => new AsyncDelegateCommand(BatchInvoice);
|
||||
|
||||
/// <summary>
|
||||
/// 批量开票:统计勾选的待开票订单 → 确认 → 后台逐单自动开票
|
||||
/// </summary>
|
||||
private async Task BatchInvoice()
|
||||
{
|
||||
if (IsBatchInvoicing) return;
|
||||
|
||||
if (string.IsNullOrEmpty(InvoiceTitle) || string.IsNullOrEmpty(TaxNo))
|
||||
{
|
||||
BaseMsgWindow.ShowError("未填写公司或税号");
|
||||
return;
|
||||
}
|
||||
|
||||
var targets = Orders.Where(o => o.IsSelected && !o.IsInvoiced).ToList();
|
||||
if (targets.Count == 0)
|
||||
{
|
||||
BaseMsgWindow.ShowError("请先勾选需要开票的订单");
|
||||
return;
|
||||
}
|
||||
|
||||
var confirm = BaseMsgWindow.ShowConfirm(
|
||||
$"已勾选 {targets.Count} 个待开票订单,确定开始批量开票吗?\n请确认发票抬头与税号无误。",
|
||||
"批量开票");
|
||||
if (confirm.DialogResult != true) return;
|
||||
|
||||
await BatchInvoiceAsync(targets);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 后台批量开票:逐单加载开票页并自动填写提交,底部进度条实时更新,成功后更新列表状态
|
||||
/// </summary>
|
||||
private async Task BatchInvoiceAsync(List<PddBrowserOrderInvoiceViewModel> targets)
|
||||
{
|
||||
IsBatchInvoicing = true;
|
||||
int success = 0, fail = 0;
|
||||
try
|
||||
{
|
||||
ProgressMaximum = targets.Count;
|
||||
ProgressValue = 0;
|
||||
BottomMsg = $"正在批量开票:0/{targets.Count}";
|
||||
|
||||
for (int i = 0; i < targets.Count; i++)
|
||||
{
|
||||
var order = targets[i];
|
||||
BottomMsg = $"正在批量开票:{i}/{targets.Count},当前订单:{order.order_sn}";
|
||||
ProgressValue = i;
|
||||
|
||||
_currentInvoiceTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
BrowserDetail.Load($"https://mobile.yangkeduo.com/transac_return_compensation.html?_t_timestamp=transac_modify_invoice&order_sn={order.order_sn}");
|
||||
|
||||
// 等待页面脚本执行完成(最长 60 秒)
|
||||
var done = await Task.WhenAny(_currentInvoiceTcs.Task, Task.Delay(TimeSpan.FromSeconds(60)));
|
||||
bool ok = done == _currentInvoiceTcs.Task && _currentInvoiceTcs.Task.Result;
|
||||
if (ok)
|
||||
{
|
||||
order.IsInvoiced = true;
|
||||
success++;
|
||||
}
|
||||
else
|
||||
{
|
||||
fail++;
|
||||
}
|
||||
ProgressValue = success + fail;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
fail = targets.Count - success;
|
||||
BaseMsgWindow.ShowError("批量开票异常:" + ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_currentInvoiceTcs = null;
|
||||
ProgressMaximum = targets.Count;
|
||||
ProgressValue = targets.Count;
|
||||
BottomMsg = $"批量开票完成:成功 {success} 个,失败 {fail} 个";
|
||||
IsBatchInvoicing = false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary> 按 订单号 / 商品名称 过滤订单 </summary>
|
||||
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();
|
||||
}
|
||||
/// <summary>
|
||||
/// 申请开票
|
||||
/// </summary>
|
||||
/// <param name="order"></param>
|
||||
private void OpenInvoice(PddBrowserOrderInvoiceViewModel order)
|
||||
{
|
||||
SelectedOrder = order;
|
||||
if (string.IsNullOrEmpty(InvoiceTitle) || string.IsNullOrEmpty(TaxNo))
|
||||
{
|
||||
BaseMsgWindow.ShowError("未填写公司或税号");
|
||||
return;
|
||||
}
|
||||
BrowserDetail.Load($"https://mobile.yangkeduo.com/transac_return_compensation.html?_t_timestamp=transac_modify_invoice&order_sn={order.order_sn}");
|
||||
ShowOrderBrowserDetail();
|
||||
}
|
||||
|
||||
private void ShowInvoice(PddBrowserOrderInvoiceViewModel order)
|
||||
{
|
||||
SelectedOrder = order;
|
||||
BrowserDetail.Load($"https://mobile.yangkeduo.com/transac_return_compensation.html?_t_timestamp=transac_invoice&order_sn={order.order_sn}");
|
||||
ShowOrderBrowserDetail();
|
||||
}
|
||||
|
||||
private void ViewOrder(PddBrowserOrderInvoiceViewModel order)
|
||||
{
|
||||
SelectedOrder = order;
|
||||
BrowserDetail.Load($"https://mobile.yangkeduo.com/{order.order_link_url}");
|
||||
ShowOrderBrowserDetail();
|
||||
//MessageBox.Show($"查看订单:{order.OrderNo}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#region 数据解析
|
||||
|
||||
private void parserData(string data)
|
||||
{
|
||||
List<PddBrowserOrderInvoiceViewModel> list = JsonHelper.DeserializeJsonToList<PddBrowserOrderInvoiceViewModel>(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);
|
||||
BottomMsg = $"正在读取订单,读取日期{matched.Last().OrderTime.ToString("yyyy-MM-dd")}";
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary> 将新订单写入总表,并按当前搜索关键字决定是否加入可见列表(自动去重) </summary>
|
||||
private void AddOrders(IEnumerable<PddBrowserOrderInvoiceViewModel> 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))
|
||||
{
|
||||
item.IsInvoiced = item.order_buttons?.Any(a => a.brief_prompt == "发票详情") ?? false;
|
||||
Orders.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 原型演示数据(后续可替换为真实接口数据)
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 开始未知总数的百分比模拟进度:80% 之前走得快,越到最后走得越慢,
|
||||
/// 理论上永远走不到 95% 以上,直到调用 <see cref="CompleteProgress"/> 才会变成 100%。
|
||||
/// </summary>
|
||||
public void StartProgress()
|
||||
{
|
||||
StopUnknownTotalProgressTimer();
|
||||
ProgressMaximum = 100;
|
||||
ProgressValue = 0;
|
||||
_unknownTotalProgressStartTime = DateTime.Now;
|
||||
_unknownTotalProgressTimer = new System.Threading.Timer(_ => UpdateUnknownTotalProgress(), null, 0, 200);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 结束进度:停止模拟,进度条立即变为 100%。
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Background="#F5F7FB">
|
||||
<TabControl Style="{StaticResource StatisticsTabControlStyle}" SelectionChanged="TabControl_SelectionChanged">
|
||||
<TabControl SelectionChanged="TabControl_SelectionChanged" Style="{StaticResource StatisticsTabControlStyle}">
|
||||
<TabItem Header="综合统计" Style="{StaticResource StatisticsTabItemStyle}">
|
||||
<ScrollViewer
|
||||
wpf:ScrollViewerBehavior.FixMouseWheel="True"
|
||||
@@ -283,12 +283,19 @@
|
||||
BorderThickness="1"
|
||||
Cursor="Hand"
|
||||
MouseLeftButtonDown="help_mouseLeftButtonDown">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="Black"
|
||||
Foreground="White"
|
||||
Text="利润计算逻辑" />
|
||||
<WrapPanel>
|
||||
<TextBlock
|
||||
Margin="0,0,10,0"
|
||||
Foreground="White"
|
||||
Style="{StaticResource TextBlockIcon}"
|
||||
Text="{StaticResource txt-chat_help}" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="Black"
|
||||
Foreground="White"
|
||||
Text="利润计算逻辑" />
|
||||
</WrapPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Popup 弹出层 -->
|
||||
@@ -307,11 +314,13 @@
|
||||
Margin="0,-10,-10,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Background="Transparent" Click="close"
|
||||
Background="Transparent"
|
||||
Click="close"
|
||||
Content="x" />
|
||||
<TextBlock Margin="0,10,0,0"
|
||||
<TextBlock
|
||||
x:Name="PopupText"
|
||||
MaxWidth="500"
|
||||
Margin="0,10,0,0"
|
||||
FontSize="16"
|
||||
Foreground="White"
|
||||
LineHeight="25"
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WxOrder.Framework.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Cookie对象模型
|
||||
/// </summary>
|
||||
public class CookieItem
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Value { get; set; }
|
||||
public string Domain { get; set; }
|
||||
public string Path { get; set; }
|
||||
public bool Secure { get; set; }
|
||||
public bool HttpOnly { get; set; }
|
||||
public DateTime? Expires { get; set; }
|
||||
public DateTime? Creation { get; set; }
|
||||
public DateTime? LastAccess { get; set; }
|
||||
public int SameSite { get; set; }
|
||||
public int Priority { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Cookie转换帮助类
|
||||
/// </summary>
|
||||
public static class CookieConvertHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 将CookieItem列表转换为Cookie字符串
|
||||
/// </summary>
|
||||
/// <param name="cookieItems">Cookie对象列表</param>
|
||||
/// <returns>Cookie字符串,格式:name1=value1; name2=value2</returns>
|
||||
public static string ToCookieString(List<CookieItem> cookieItems)
|
||||
{
|
||||
if (cookieItems == null || cookieItems.Count == 0)
|
||||
return string.Empty;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
for (int i = 0; i < cookieItems.Count; i++)
|
||||
{
|
||||
var item = cookieItems[i];
|
||||
if (!string.IsNullOrEmpty(item.Name) && item.Value != null)
|
||||
{
|
||||
builder.Append($"{item.Name}={item.Value}");
|
||||
if (i < cookieItems.Count - 1)
|
||||
builder.Append("; ");
|
||||
}
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将单个CookieItem转换为Cookie字符串
|
||||
/// </summary>
|
||||
/// <param name="cookieItem">Cookie对象</param>
|
||||
/// <returns>Cookie字符串,格式:name=value</returns>
|
||||
public static string ToCookieString(CookieItem cookieItem)
|
||||
{
|
||||
if (cookieItem == null || string.IsNullOrEmpty(cookieItem.Name))
|
||||
return string.Empty;
|
||||
|
||||
return $"{cookieItem.Name}={cookieItem.Value}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将CookieItem列表转换为字典
|
||||
/// </summary>
|
||||
/// <param name="cookieItems">Cookie对象列表</param>
|
||||
/// <returns>Cookie字典</returns>
|
||||
public static Dictionary<string, string> ToDictionary(List<CookieItem> cookieItems)
|
||||
{
|
||||
var dict = new Dictionary<string, string>();
|
||||
if (cookieItems == null || cookieItems.Count == 0)
|
||||
return dict;
|
||||
|
||||
foreach (var item in cookieItems)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(item.Name) && !dict.ContainsKey(item.Name))
|
||||
{
|
||||
dict.Add(item.Name, item.Value);
|
||||
}
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将CookieItem列表转换为System.Net.CookieCollection
|
||||
/// </summary>
|
||||
/// <param name="cookieItems">Cookie对象列表</param>
|
||||
/// <param name="domain">域名</param>
|
||||
/// <returns>CookieCollection</returns>
|
||||
public static System.Net.CookieCollection ToCookieCollection(List<CookieItem> cookieItems, string domain = "mobile.yangkeduo.com")
|
||||
{
|
||||
var cookieCollection = new System.Net.CookieCollection();
|
||||
if (cookieItems == null || cookieItems.Count == 0)
|
||||
return cookieCollection;
|
||||
|
||||
foreach (var item in cookieItems)
|
||||
{
|
||||
if (string.IsNullOrEmpty(item.Name))
|
||||
continue;
|
||||
|
||||
var cookie = new System.Net.Cookie
|
||||
{
|
||||
Name = item.Name,
|
||||
Value = item.Value ?? string.Empty,
|
||||
Domain = string.IsNullOrEmpty(item.Domain) ? domain : item.Domain,
|
||||
Path = string.IsNullOrEmpty(item.Path) ? "/" : item.Path,
|
||||
Secure = item.Secure,
|
||||
HttpOnly = item.HttpOnly,
|
||||
Expires = item.Expires ?? DateTime.MinValue
|
||||
};
|
||||
cookieCollection.Add(cookie);
|
||||
}
|
||||
return cookieCollection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从JSON数组字符串转换为Cookie字符串
|
||||
/// </summary>
|
||||
/// <param name="jsonArray">JSON数组字符串</param>
|
||||
/// <returns>Cookie字符串</returns>
|
||||
public static string FromJsonToCookieString(string cookie)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cookieItems = Newtonsoft.Json.JsonConvert.DeserializeObject<List<CookieItem>>(cookie);
|
||||
return ToCookieString(cookieItems);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从JSON数组字符串转换为字典
|
||||
/// </summary>
|
||||
/// <param name="jsonArray">JSON数组字符串</param>
|
||||
/// <returns>Cookie字典</returns>
|
||||
public static Dictionary<string, string> FromJsonToDictionary(string jsonArray)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cookieItems = Newtonsoft.Json.JsonConvert.DeserializeObject<List<CookieItem>>(jsonArray);
|
||||
return ToDictionary(cookieItems);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,839 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WxOrder.Framework.Helpers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Http连接操作帮助类
|
||||
/// </summary>
|
||||
public class HttpHelper
|
||||
{
|
||||
public static HttpResult Get(string Url)
|
||||
{
|
||||
return new HttpHelper().GetHtml(new HttpItem
|
||||
{
|
||||
URL = Url,
|
||||
Method = MethodType.Get,
|
||||
});
|
||||
}
|
||||
|
||||
public static HttpResult Post(string Url, string PostData)
|
||||
{
|
||||
return new HttpHelper().GetHtml(new HttpItem
|
||||
{
|
||||
URL = Url,
|
||||
Method = MethodType.Get,
|
||||
Postdata = PostData,
|
||||
PostDataType = PostDataType.String
|
||||
});
|
||||
}
|
||||
public static HttpResult Request(HttpItem item)
|
||||
{
|
||||
return new HttpHelper().GetHtml(item);
|
||||
}
|
||||
/// <summary>
|
||||
/// 下载文件
|
||||
/// </summary>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="fileName"></param>
|
||||
public static byte[] DownloadFile(string address)
|
||||
{
|
||||
HttpItem item = new HttpItem();
|
||||
item.URL = address;
|
||||
item.Timeout = 3000;
|
||||
item.ResultType = ResultType.Byte;
|
||||
HttpHelper http = new HttpHelper();
|
||||
HttpResult result = http.GetHtml(item);
|
||||
if (result.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
return result.ResultByte;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new HttpListenerException((int)result.StatusCode, result.StatusDescription);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载文件
|
||||
/// </summary>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="fileName"></param>
|
||||
public static bool DownloadFile(string address, string fileName)
|
||||
{
|
||||
HttpItem item = new HttpItem();
|
||||
item.URL = address;
|
||||
item.Timeout = 3000;
|
||||
item.ResultType = ResultType.Byte;
|
||||
HttpHelper http = new HttpHelper();
|
||||
HttpResult result = http.GetHtml(item);
|
||||
if (result.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
FileStream fs = new FileStream(fileName, FileMode.CreateNew);
|
||||
BinaryWriter bw = new BinaryWriter(fs);
|
||||
bw.Write(result.ResultByte);
|
||||
fs.Close();
|
||||
bw.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new HttpListenerException((int)result.StatusCode, result.StatusDescription);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#region 预定义方法或者变更
|
||||
//默认的编码
|
||||
private Encoding encoding = Encoding.Default;
|
||||
//Post数据编码
|
||||
private Encoding postencoding = Encoding.Default;
|
||||
//HttpWebRequest对象用来发起请求
|
||||
private HttpWebRequest request = null;
|
||||
//获取影响流的数据对象
|
||||
private HttpWebResponse response = null;
|
||||
/// <summary>
|
||||
/// 根据相传入的数据,得到相应页面数据
|
||||
/// </summary>
|
||||
/// <param name="item">参数类对象</param>
|
||||
/// <returns>返回HttpResult类型</returns>
|
||||
public HttpResult GetHtml(HttpItem item)
|
||||
{
|
||||
//返回参数
|
||||
HttpResult result = new HttpResult();
|
||||
result.item = item;
|
||||
try
|
||||
{
|
||||
//准备参数
|
||||
SetRequest(item);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result = new HttpResult();
|
||||
result.Cookie = string.Empty;
|
||||
result.Header = null;
|
||||
result.Html = ex.Message;
|
||||
result.StatusDescription = "配置参数时出错:" + ex.Message;
|
||||
return result;
|
||||
}
|
||||
try
|
||||
{
|
||||
#region 得到请求的response
|
||||
using (response = (HttpWebResponse)request.GetResponse())
|
||||
{
|
||||
result.StatusCode = response.StatusCode;
|
||||
result.StatusDescription = response.StatusDescription;
|
||||
result.Header = response.Headers;
|
||||
if (response.Cookies != null) result.CookieCollection = response.Cookies;
|
||||
if (response.Headers["set-cookie"] != null) result.Cookie = response.Headers["set-cookie"];
|
||||
MemoryStream _stream = new MemoryStream();
|
||||
//GZIIP处理
|
||||
if (response.ContentEncoding != null && response.ContentEncoding.Equals("gzip", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
//开始读取流并设置编码方式
|
||||
//new GZipStream(response.GetResponseStream(), CompressionMode.Decompress).CopyTo(_stream, 10240);
|
||||
//.net4.0以下写法
|
||||
_stream = GetMemoryStream(new GZipStream(response.GetResponseStream(), CompressionMode.Decompress));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//开始读取流并设置编码方式
|
||||
//response.GetResponseStream().CopyTo(_stream, 10240);
|
||||
//.net4.0以下写法
|
||||
_stream = GetMemoryStream(response.GetResponseStream());
|
||||
}
|
||||
//获取Byte
|
||||
byte[] ResponseByte = _stream.ToArray();
|
||||
_stream.Close();
|
||||
if (ResponseByte != null & ResponseByte.Length > 0)
|
||||
{
|
||||
//是否返回Byte类型数据
|
||||
if (item.ResultType == ResultType.Byte) result.ResultByte = ResponseByte;
|
||||
//从这里开始我们要无视编码了
|
||||
if (encoding == null)
|
||||
{
|
||||
Match meta = Regex.Match(Encoding.Default.GetString(ResponseByte), "<meta([^<]*)charset=([^<]*)[\"']", RegexOptions.IgnoreCase);
|
||||
string c = (meta.Groups.Count > 1) ? meta.Groups[2].Value.ToLower().Trim() : string.Empty;
|
||||
if (c.Length > 2)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (c.IndexOf(" ") > 0) c = c.Substring(0, c.IndexOf(" "));
|
||||
encoding = Encoding.GetEncoding(c.Replace("\"", "").Replace("'", "").Replace(";", "").Replace("iso-8859-1", "gbk").Trim());
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (string.IsNullOrEmpty(response.CharacterSet)) encoding = Encoding.UTF8;
|
||||
else encoding = Encoding.GetEncoding(response.CharacterSet);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(response.CharacterSet)) encoding = Encoding.UTF8;
|
||||
else encoding = Encoding.GetEncoding(response.CharacterSet);
|
||||
}
|
||||
}
|
||||
//得到返回的HTML
|
||||
result.Html = encoding.GetString(ResponseByte);
|
||||
}
|
||||
else
|
||||
{
|
||||
//得到返回的HTML
|
||||
result.Html = "本次请求并未返回任何数据";
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
//这里是在发生异常时返回的错误信息
|
||||
response = (HttpWebResponse)ex.Response;
|
||||
result.Html = ex.Message;
|
||||
if (response != null)
|
||||
{
|
||||
result.StatusCode = response.StatusCode;
|
||||
result.StatusDescription = response.StatusDescription;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.Html = ex.Message;
|
||||
}
|
||||
if (item.IsToLower) result.Html = result.Html.ToLower();
|
||||
return result;
|
||||
}
|
||||
/// <summary>
|
||||
/// 4.0以下.net版本取数据使用
|
||||
/// </summary>
|
||||
/// <param name="streamResponse">流</param>
|
||||
private MemoryStream GetMemoryStream(Stream streamResponse)
|
||||
{
|
||||
MemoryStream _stream = new MemoryStream();
|
||||
int Length = 256;
|
||||
Byte[] buffer = new Byte[Length];
|
||||
int bytesRead = streamResponse.Read(buffer, 0, Length);
|
||||
while (bytesRead > 0)
|
||||
{
|
||||
_stream.Write(buffer, 0, bytesRead);
|
||||
bytesRead = streamResponse.Read(buffer, 0, Length);
|
||||
}
|
||||
return _stream;
|
||||
}
|
||||
/// <summary>
|
||||
/// 为请求准备参数
|
||||
/// </summary>
|
||||
///<param name="item">参数列表</param>
|
||||
private void SetRequest(HttpItem item)
|
||||
|
||||
{
|
||||
// 验证证书
|
||||
SetCer(item);
|
||||
//设置Header参数
|
||||
if (item.Header != null && item.Header.Count > 0) foreach (string key in item.Header.AllKeys)
|
||||
{
|
||||
request.Headers.Add(key, item.Header[key]);
|
||||
}
|
||||
// 设置代理
|
||||
SetProxy(item);
|
||||
if (item.ProtocolVersion != null) request.ProtocolVersion = item.ProtocolVersion;
|
||||
request.ServicePoint.Expect100Continue = item.Expect100Continue;
|
||||
//请求方式Get或者Post
|
||||
request.Method = item.Method.ToString();
|
||||
request.Timeout = item.Timeout;
|
||||
request.KeepAlive = item.KeepAlive;
|
||||
request.ReadWriteTimeout = item.ReadWriteTimeout;
|
||||
//Accept
|
||||
request.Accept = item.Accept;
|
||||
//ContentType返回类型
|
||||
request.ContentType = item.ContentType;
|
||||
//UserAgent客户端的访问类型,包括浏览器版本和操作系统信息
|
||||
request.UserAgent = item.UserAgent;
|
||||
// 编码
|
||||
encoding = item.Encoding;
|
||||
//设置安全凭证
|
||||
request.Credentials = item.ICredentials;
|
||||
//设置Cookie
|
||||
SetCookie(item);
|
||||
//来源地址
|
||||
request.Referer = item.Referer;
|
||||
//是否执行跳转功能
|
||||
request.AllowAutoRedirect = item.Allowautoredirect;
|
||||
//设置Post数据
|
||||
SetPostData(item);
|
||||
//设置最大连接
|
||||
if (item.Connectionlimit > 0) request.ServicePoint.ConnectionLimit = item.Connectionlimit;
|
||||
}
|
||||
/// <summary>
|
||||
/// 设置证书
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
private void SetCer(HttpItem item)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(item.CerPath))
|
||||
{
|
||||
//这一句一定要写在创建连接的前面。使用回调的方法进行证书验证。
|
||||
ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(CheckValidationResult);
|
||||
//初始化对像,并设置请求的URL地址
|
||||
request = (HttpWebRequest)WebRequest.Create(item.URL);
|
||||
SetCerList(item);
|
||||
//将证书添加到请求里
|
||||
request.ClientCertificates.Add(new X509Certificate(item.CerPath));
|
||||
}
|
||||
else
|
||||
{
|
||||
//初始化对像,并设置请求的URL地址
|
||||
request = (HttpWebRequest)WebRequest.Create(item.URL);
|
||||
SetCerList(item);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 设置多个证书
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
private void SetCerList(HttpItem item)
|
||||
{
|
||||
if (item.ClentCertificates != null && item.ClentCertificates.Count > 0)
|
||||
{
|
||||
foreach (X509Certificate c in item.ClentCertificates)
|
||||
{
|
||||
request.ClientCertificates.Add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 设置Cookie
|
||||
/// </summary>
|
||||
/// <param name="item">Http参数</param>
|
||||
private void SetCookie(HttpItem item)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(item.Cookie)) request.Headers[HttpRequestHeader.Cookie] = item.Cookie;
|
||||
//设置CookieCollection
|
||||
if (item.ResultCookieType == ResultCookieType.CookieCollection)
|
||||
{
|
||||
request.CookieContainer = new CookieContainer();
|
||||
if (item.CookieCollection != null && item.CookieCollection.Count > 0)
|
||||
request.CookieContainer.Add(item.CookieCollection);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 设置Post数据
|
||||
/// </summary>
|
||||
/// <param name="item">Http参数</param>
|
||||
private void SetPostData(HttpItem item)
|
||||
{
|
||||
//验证在得到结果时是否有传入数据
|
||||
if (request.Method.Trim().ToLower().Contains("post"))
|
||||
{
|
||||
if (item.PostEncoding != null)
|
||||
{
|
||||
postencoding = item.PostEncoding;
|
||||
}
|
||||
byte[] buffer = null;
|
||||
//写入Byte类型
|
||||
if (item.PostDataType == PostDataType.Byte && item.PostdataByte != null && item.PostdataByte.Length > 0)
|
||||
{
|
||||
//验证在得到结果时是否有传入数据
|
||||
buffer = item.PostdataByte;
|
||||
}//写入文件
|
||||
else if (item.PostDataType == PostDataType.FilePath && !string.IsNullOrEmpty(item.Postdata))
|
||||
{
|
||||
StreamReader r = new StreamReader(item.Postdata, postencoding);
|
||||
buffer = postencoding.GetBytes(r.ReadToEnd());
|
||||
r.Close();
|
||||
} //写入字符串
|
||||
else if (!string.IsNullOrEmpty(item.Postdata))
|
||||
{
|
||||
buffer = postencoding.GetBytes(item.Postdata);
|
||||
}
|
||||
if (buffer != null)
|
||||
{
|
||||
request.ContentLength = buffer.Length;
|
||||
request.GetRequestStream().Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 设置代理
|
||||
/// </summary>
|
||||
/// <param name="item">参数对象</param>
|
||||
private void SetProxy(HttpItem item)
|
||||
{
|
||||
bool isIeProxy = item.ProxyIp.ToLower().Contains("ieproxy");
|
||||
if (!string.IsNullOrEmpty(item.ProxyIp) && !isIeProxy)
|
||||
{
|
||||
//设置代理服务器
|
||||
if (item.ProxyIp.Contains(":"))
|
||||
{
|
||||
string[] plist = item.ProxyIp.Split(':');
|
||||
WebProxy myProxy = new WebProxy(plist[0].Trim(),int.Parse(plist[1].Trim()));
|
||||
//建议连接
|
||||
myProxy.Credentials = new NetworkCredential(item.ProxyUserName, item.ProxyPwd);
|
||||
//给当前请求对象
|
||||
request.Proxy = myProxy;
|
||||
}
|
||||
else
|
||||
{
|
||||
WebProxy myProxy = new WebProxy(item.ProxyIp, false);
|
||||
//建议连接
|
||||
myProxy.Credentials = new NetworkCredential(item.ProxyUserName, item.ProxyPwd);
|
||||
//给当前请求对象
|
||||
request.Proxy = myProxy;
|
||||
}
|
||||
}
|
||||
else if (isIeProxy)
|
||||
{
|
||||
//设置为IE代理
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Proxy = item.WebProxy;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 回调验证证书问题
|
||||
/// </summary>
|
||||
/// <param name="sender">流对象</param>
|
||||
/// <param name="certificate">证书</param>
|
||||
/// <param name="chain">X509Chain</param>
|
||||
/// <param name="errors">SslPolicyErrors</param>
|
||||
/// <returns>bool</returns>
|
||||
public bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) { return true; }
|
||||
#endregion
|
||||
}
|
||||
/// <summary>
|
||||
/// Http请求参考类
|
||||
/// </summary>
|
||||
public class HttpItem
|
||||
{
|
||||
string _URL = string.Empty;
|
||||
/// <summary>
|
||||
/// 请求URL必须填写
|
||||
/// </summary>
|
||||
public string URL
|
||||
{
|
||||
get { return _URL; }
|
||||
set { _URL = value; }
|
||||
}
|
||||
MethodType _Method = MethodType.Get;
|
||||
/// <summary>
|
||||
/// 请求方式默认为GET方式,当为POST方式时必须设置Postdata的值
|
||||
public MethodType Method
|
||||
/// </summary>
|
||||
{
|
||||
get { return _Method; }
|
||||
set
|
||||
{
|
||||
_Method = value;
|
||||
if (_Method == MethodType.Post)
|
||||
{
|
||||
_ContentType = "application/x-www-form-urlencoded";
|
||||
}
|
||||
}
|
||||
}
|
||||
int _Timeout = 100000;
|
||||
/// <summary>
|
||||
/// 默认请求超时时间
|
||||
/// </summary>
|
||||
public int Timeout
|
||||
{
|
||||
get { return _Timeout; }
|
||||
set { _Timeout = value; }
|
||||
}
|
||||
int _ReadWriteTimeout = 30000;
|
||||
/// <summary>
|
||||
/// 默认写入Post数据超时间
|
||||
/// </summary>
|
||||
public int ReadWriteTimeout
|
||||
{
|
||||
get { return _ReadWriteTimeout; }
|
||||
set { _ReadWriteTimeout = value; }
|
||||
}
|
||||
Boolean _KeepAlive = true;
|
||||
/// <summary>
|
||||
/// 获取或设置一个值,该值指示是否与 Internet 资源建立持久性连接默认为true。
|
||||
/// </summary>
|
||||
public Boolean KeepAlive
|
||||
{
|
||||
get { return _KeepAlive; }
|
||||
set { _KeepAlive = value; }
|
||||
}
|
||||
string _Accept = "text/html, application/xhtml+xml, */*";
|
||||
/// <summary>
|
||||
/// 请求标头值 默认为text/html, application/xhtml+xml, */*
|
||||
/// </summary>
|
||||
public string Accept
|
||||
{
|
||||
get { return _Accept; }
|
||||
set { _Accept = value; }
|
||||
}
|
||||
string _ContentType = "text/html";
|
||||
/// <summary>
|
||||
/// 请求返回类型默认 text/html
|
||||
/// </summary>
|
||||
public string ContentType
|
||||
{
|
||||
get { return _ContentType; }
|
||||
set { _ContentType = value; }
|
||||
}
|
||||
string _UserAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)";
|
||||
/// <summary>
|
||||
/// 客户端访问信息默认Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)
|
||||
/// </summary>
|
||||
public string UserAgent
|
||||
{
|
||||
get { return _UserAgent; }
|
||||
set { _UserAgent = value; }
|
||||
}
|
||||
Encoding _Encoding = null;
|
||||
/// <summary>
|
||||
/// 返回数据编码默认为NUll,可以自动识别,一般为utf-8,gbk,gb2312
|
||||
/// </summary>
|
||||
public Encoding Encoding
|
||||
{
|
||||
get { return _Encoding; }
|
||||
set { _Encoding = value; }
|
||||
}
|
||||
private PostDataType _PostDataType = PostDataType.String;
|
||||
/// <summary>
|
||||
/// Post的数据类型
|
||||
/// </summary>
|
||||
public PostDataType PostDataType
|
||||
{
|
||||
get { return _PostDataType; }
|
||||
set { _PostDataType = value; }
|
||||
}
|
||||
string _Postdata = string.Empty;
|
||||
/// <summary>
|
||||
/// Post请求时要发送的字符串Post数据
|
||||
/// </summary>
|
||||
public string Postdata
|
||||
{
|
||||
get { return _Postdata; }
|
||||
set { _Postdata = value; }
|
||||
}
|
||||
private byte[] _PostdataByte = null;
|
||||
/// <summary>
|
||||
/// Post请求时要发送的Byte类型的Post数据
|
||||
/// </summary>
|
||||
public byte[] PostdataByte
|
||||
{
|
||||
get { return _PostdataByte; }
|
||||
set { _PostdataByte = value; }
|
||||
}
|
||||
private WebProxy _WebProxy;
|
||||
/// <summary>
|
||||
/// 设置代理对象,不想使用IE默认配置就设置为Null,而且不要设置ProxyIp
|
||||
/// </summary>
|
||||
public WebProxy WebProxy
|
||||
{
|
||||
get { return _WebProxy; }
|
||||
set { _WebProxy = value; }
|
||||
}
|
||||
|
||||
CookieCollection cookiecollection = null;
|
||||
/// <summary>
|
||||
/// Cookie对象集合
|
||||
/// </summary>
|
||||
public CookieCollection CookieCollection
|
||||
{
|
||||
get { return cookiecollection; }
|
||||
set { cookiecollection = value; }
|
||||
}
|
||||
string _Cookie = string.Empty;
|
||||
/// <summary>
|
||||
/// 请求时的Cookie
|
||||
/// </summary>
|
||||
public string Cookie
|
||||
{
|
||||
get { return _Cookie; }
|
||||
set { _Cookie = value; }
|
||||
}
|
||||
string _Referer = string.Empty;
|
||||
/// <summary>
|
||||
/// 来源地址,上次访问地址
|
||||
/// </summary>
|
||||
public string Referer
|
||||
{
|
||||
get { return _Referer; }
|
||||
set { _Referer = value; }
|
||||
}
|
||||
string _CerPath = string.Empty;
|
||||
/// <summary>
|
||||
/// 证书绝对路径
|
||||
/// </summary>
|
||||
public string CerPath
|
||||
{
|
||||
get { return _CerPath; }
|
||||
set { _CerPath = value; }
|
||||
}
|
||||
private Boolean isToLower = false;
|
||||
/// <summary>
|
||||
/// 是否设置为全文小写,默认为不转化
|
||||
/// </summary>
|
||||
public Boolean IsToLower
|
||||
{
|
||||
get { return isToLower; }
|
||||
set { isToLower = value; }
|
||||
}
|
||||
private Boolean allowautoredirect = true;
|
||||
/// <summary>
|
||||
/// 支持跳转页面,查询结果将是跳转后的页面
|
||||
/// </summary>
|
||||
public Boolean Allowautoredirect
|
||||
{
|
||||
get { return allowautoredirect; }
|
||||
set { allowautoredirect = value; }
|
||||
}
|
||||
private int connectionlimit = 1024;
|
||||
/// <summary>
|
||||
/// 最大连接数
|
||||
/// </summary>
|
||||
public int Connectionlimit
|
||||
{
|
||||
get { return connectionlimit; }
|
||||
set { connectionlimit = value; }
|
||||
}
|
||||
private string proxyusername = string.Empty;
|
||||
/// <summary>
|
||||
/// 代理Proxy 服务器用户名
|
||||
/// </summary>
|
||||
public string ProxyUserName
|
||||
{
|
||||
get { return proxyusername; }
|
||||
set { proxyusername = value; }
|
||||
}
|
||||
private string proxypwd = string.Empty;
|
||||
/// <summary>
|
||||
/// 代理 服务器密码
|
||||
/// </summary>
|
||||
public string ProxyPwd
|
||||
{
|
||||
get { return proxypwd; }
|
||||
set { proxypwd = value; }
|
||||
}
|
||||
private string proxyip = string.Empty;
|
||||
/// <summary>
|
||||
/// 代理 服务IP ,如果要使用IE代理就设置为ieproxy
|
||||
/// </summary>
|
||||
public string ProxyIp
|
||||
{
|
||||
get { return proxyip; }
|
||||
set { proxyip = value; }
|
||||
}
|
||||
private ResultType resulttype = ResultType.String;
|
||||
/// <summary>
|
||||
/// 设置返回类型String和Byte
|
||||
/// </summary>
|
||||
public ResultType ResultType
|
||||
{
|
||||
get { return resulttype; }
|
||||
set { resulttype = value; }
|
||||
}
|
||||
private WebHeaderCollection header = new WebHeaderCollection();
|
||||
/// <summary>
|
||||
/// header对象
|
||||
/// </summary>
|
||||
public WebHeaderCollection Header
|
||||
{
|
||||
get { return header; }
|
||||
set { header = value; }
|
||||
}
|
||||
|
||||
private Version _ProtocolVersion;
|
||||
|
||||
/// <summary>
|
||||
// 获取或设置用于请求的 HTTP 版本。返回结果:用于请求的 HTTP 版本。默认为 System.Net.HttpVersion.Version11。
|
||||
/// </summary>
|
||||
public Version ProtocolVersion
|
||||
{
|
||||
get { return _ProtocolVersion; }
|
||||
set { _ProtocolVersion = value; }
|
||||
}
|
||||
private Boolean _expect100continue = true;
|
||||
/// <summary>
|
||||
/// 获取或设置一个 System.Boolean 值,该值确定是否使用 100-Continue 行为。如果 POST 请求需要 100-Continue 响应,则为 true;否则为 false。默认值为 true。
|
||||
/// </summary>
|
||||
public Boolean Expect100Continue
|
||||
{
|
||||
get { return _expect100continue; }
|
||||
set { _expect100continue = value; }
|
||||
}
|
||||
private X509CertificateCollection _ClentCertificates;
|
||||
/// <summary>
|
||||
/// 设置509证书集合
|
||||
/// </summary>
|
||||
public X509CertificateCollection ClentCertificates
|
||||
{
|
||||
get { return _ClentCertificates; }
|
||||
set { _ClentCertificates = value; }
|
||||
}
|
||||
private Encoding _PostEncoding;
|
||||
/// <summary>
|
||||
/// 设置或获取Post参数编码,默认的为Default编码
|
||||
/// </summary>
|
||||
public Encoding PostEncoding
|
||||
{
|
||||
get { return _PostEncoding; }
|
||||
set { _PostEncoding = value; }
|
||||
}
|
||||
private ResultCookieType _ResultCookieType = ResultCookieType.String;
|
||||
/// <summary>
|
||||
/// Cookie返回类型,默认的是只返回字符串类型
|
||||
/// </summary>
|
||||
public ResultCookieType ResultCookieType
|
||||
{
|
||||
get { return _ResultCookieType; }
|
||||
set { _ResultCookieType = value; }
|
||||
}
|
||||
|
||||
private ICredentials _ICredentials = CredentialCache.DefaultCredentials;
|
||||
/// <summary>
|
||||
/// 获取或设置请求的身份验证信息。
|
||||
/// </summary>
|
||||
public ICredentials ICredentials
|
||||
{
|
||||
get { return _ICredentials; }
|
||||
set { _ICredentials = value; }
|
||||
}
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// Http返回参数类
|
||||
/// </summary>
|
||||
public class HttpResult
|
||||
{
|
||||
private string _Cookie;
|
||||
/// <summary>
|
||||
/// Http请求返回的Cookie
|
||||
/// </summary>
|
||||
public string Cookie
|
||||
{
|
||||
get { return _Cookie; }
|
||||
set { _Cookie = value; }
|
||||
}
|
||||
|
||||
private CookieCollection _CookieCollection;
|
||||
/// <summary>
|
||||
/// Cookie对象集合
|
||||
/// </summary>
|
||||
public CookieCollection CookieCollection
|
||||
{
|
||||
get { return _CookieCollection; }
|
||||
set { _CookieCollection = value; }
|
||||
}
|
||||
private string _Html;
|
||||
/// <summary>
|
||||
/// 返回的String类型数据 只有ResultType.String时才返回数据,其它情况为空
|
||||
/// </summary>
|
||||
public string Html
|
||||
{
|
||||
get { return _Html; }
|
||||
set { _Html = value; }
|
||||
}
|
||||
private byte[] _ResultByte;
|
||||
/// <summary>
|
||||
/// 返回的Byte数组 只有ResultType.Byte时才返回数据,其它情况为空
|
||||
/// </summary>
|
||||
public byte[] ResultByte
|
||||
{
|
||||
get { return _ResultByte; }
|
||||
set { _ResultByte = value; }
|
||||
}
|
||||
|
||||
private WebHeaderCollection _Header;
|
||||
/// <summary>
|
||||
/// header对象
|
||||
/// </summary>
|
||||
public WebHeaderCollection Header
|
||||
{
|
||||
get { return _Header; }
|
||||
set { _Header = value; }
|
||||
}
|
||||
|
||||
private string _StatusDescription;
|
||||
/// <summary>
|
||||
/// 返回状态说明
|
||||
/// </summary>
|
||||
public string StatusDescription
|
||||
{
|
||||
get { return _StatusDescription; }
|
||||
set { _StatusDescription = value; }
|
||||
}
|
||||
private HttpStatusCode _StatusCode;
|
||||
/// <summary>
|
||||
/// 返回状态码,默认为OK
|
||||
/// </summary>
|
||||
public HttpStatusCode StatusCode
|
||||
{
|
||||
get { return _StatusCode; }
|
||||
set { _StatusCode = value; }
|
||||
}
|
||||
/// <summary>
|
||||
/// HttpItem类,用于存储实际执行所用到的HttpItem对象
|
||||
/// </summary>
|
||||
public HttpItem item { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 返回类型
|
||||
/// </summary>
|
||||
public enum ResultType
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示只返回字符串 只有Html有数据
|
||||
/// </summary>
|
||||
String,
|
||||
/// <summary>
|
||||
/// 表示返回字符串和字节流 ResultByte和Html都有数据返回
|
||||
/// </summary>
|
||||
Byte
|
||||
}
|
||||
/// <summary>
|
||||
/// Post的数据格式默认为string
|
||||
/// </summary>
|
||||
public enum PostDataType
|
||||
{
|
||||
/// <summary>
|
||||
/// 字符串类型,这时编码Encoding可不设置
|
||||
/// </summary>
|
||||
String,
|
||||
/// <summary>
|
||||
/// Byte类型,需要设置PostdataByte参数的值编码Encoding可设置为空
|
||||
/// </summary>
|
||||
Byte,
|
||||
/// <summary>
|
||||
/// 传文件,Postdata必须设置为文件的绝对路径,必须设置Encoding的值
|
||||
/// </summary>
|
||||
FilePath
|
||||
}
|
||||
/// <summary>
|
||||
/// Cookie返回类型
|
||||
/// </summary>
|
||||
public enum ResultCookieType
|
||||
{
|
||||
/// <summary>
|
||||
/// 只返回字符串类型的Cookie
|
||||
/// </summary>
|
||||
String,
|
||||
/// <summary>
|
||||
/// CookieCollection格式的Cookie集合同时也返回String类型的cookie
|
||||
/// </summary>
|
||||
CookieCollection
|
||||
}
|
||||
public enum MethodType
|
||||
{
|
||||
Get,
|
||||
Post
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Resources;
|
||||
|
||||
namespace WxOrder.Framework.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// WPF 资源读取帮助类
|
||||
/// 支持 Resource、Embedded Resource、Content 三种方式
|
||||
/// </summary>
|
||||
public static class ResourceHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 从 WPF 应用程序资源中读取文本内容(Build Action = Resource)
|
||||
/// </summary>
|
||||
/// <param name="relativePath">相对路径,如 "Ctrls/PDDInvoice/Resources/invoice.js"</param>
|
||||
/// <param name="assemblyName">程序集名称,可选,默认使用当前程序集</param>
|
||||
/// <returns>文件内容字符串</returns>
|
||||
public static string GetResourceText(string relativePath, string assemblyName = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 构建 pack URI
|
||||
string uriString;
|
||||
if (string.IsNullOrEmpty(assemblyName))
|
||||
{
|
||||
// 当前程序集
|
||||
uriString = $"pack://application:,,,/{relativePath}";
|
||||
}
|
||||
else
|
||||
{
|
||||
// 指定程序集
|
||||
uriString = $"pack://application:,,,/{assemblyName};component/{relativePath}";
|
||||
}
|
||||
|
||||
var uri = new Uri(uriString, UriKind.Absolute);
|
||||
StreamResourceInfo resourceInfo = Application.GetResourceStream(uri);
|
||||
|
||||
if (resourceInfo == null)
|
||||
{
|
||||
throw new FileNotFoundException($"资源未找到: {relativePath}");
|
||||
}
|
||||
|
||||
using (var reader = new StreamReader(resourceInfo.Stream))
|
||||
{
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从嵌入资源中读取文本内容(Build Action = Embedded Resource)
|
||||
/// </summary>
|
||||
/// <param name="resourceName">完整资源名称,如 "YourNamespace.Ctrls.PDDInvoice.Resources.invoice.js"</param>
|
||||
/// <param name="assembly">程序集,默认使用当前程序集</param>
|
||||
/// <returns>文件内容字符串</returns>
|
||||
public static string GetEmbeddedResourceText(string resourceName, Assembly assembly = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
assembly ??= Assembly.GetExecutingAssembly();
|
||||
|
||||
using (var stream = assembly.GetManifestResourceStream(resourceName))
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new FileNotFoundException($"嵌入资源未找到: {resourceName}");
|
||||
}
|
||||
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"读取嵌入资源失败: {resourceName}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从嵌入资源中读取文本内容,自动查找资源名称(支持部分匹配)
|
||||
/// </summary>
|
||||
/// <param name="fileName">文件名,如 "invoice.js"</param>
|
||||
/// <param name="assembly">程序集,默认使用当前程序集</param>
|
||||
/// <returns>文件内容字符串</returns>
|
||||
public static string GetEmbeddedResourceTextByFileName(string fileName, Assembly assembly = null)
|
||||
{
|
||||
assembly ??= Assembly.GetExecutingAssembly();
|
||||
|
||||
// 查找匹配的资源名称
|
||||
var resourceNames = assembly.GetManifestResourceNames();
|
||||
var matchedName = Array.Find(resourceNames, name => name.EndsWith(fileName));
|
||||
|
||||
if (string.IsNullOrEmpty(matchedName))
|
||||
{
|
||||
throw new FileNotFoundException($"未找到名为 {fileName} 的嵌入资源");
|
||||
}
|
||||
|
||||
return GetEmbeddedResourceText(matchedName, assembly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从文件系统读取文本内容(Build Action = Content)
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件物理路径或相对路径</param>
|
||||
/// <returns>文件内容字符串</returns>
|
||||
public static string GetFileText(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
throw new FileNotFoundException($"文件未找到: {filePath}");
|
||||
}
|
||||
|
||||
return File.ReadAllText(filePath, System.Text.Encoding.UTF8);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"读取文件失败: {filePath}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取 JS 资源(专门针对 JS 文件的便捷方法)
|
||||
/// </summary>
|
||||
/// <param name="relativePath">JS 文件相对路径</param>
|
||||
/// <returns>JS 文件内容</returns>
|
||||
public static string GetJavaScriptResource(string relativePath)
|
||||
{
|
||||
return GetResourceText(relativePath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace WxOrder.Framework.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 拼多多API请求类
|
||||
/// </summary>
|
||||
public class PddApi
|
||||
{
|
||||
private const string BASE_URL = "https://mobile.yangkeduo.com";
|
||||
|
||||
/// <summary>
|
||||
/// 获取发票详情页
|
||||
/// </summary>
|
||||
/// <param name="orderSn">订单号</param>
|
||||
/// <param name="cookie">用户Cookie</param>
|
||||
/// <returns>返回页面HTML内容</returns>
|
||||
public static string GetReturnCompensation(string orderSn, string cookie)
|
||||
{
|
||||
string url = $"{BASE_URL}/transac_return_compensation.html?_t_timestamp=transac_modify_invoice&order_sn={orderSn}";
|
||||
|
||||
var item = new HttpItem
|
||||
{
|
||||
URL = url,
|
||||
Method = MethodType.Get,
|
||||
Timeout = 30000,
|
||||
ReadWriteTimeout = 30000,
|
||||
KeepAlive = true,
|
||||
Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
||||
ContentType = "text/html",
|
||||
UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
|
||||
Cookie = cookie,
|
||||
Referer = BASE_URL,
|
||||
Allowautoredirect = true,
|
||||
Encoding = Encoding.UTF8,
|
||||
ResultType = ResultType.String
|
||||
};
|
||||
|
||||
// 添加自定义Header
|
||||
item.Header.Add("sec-ch-ua", "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\"");
|
||||
item.Header.Add("sec-ch-ua-mobile", "?0");
|
||||
item.Header.Add("sec-ch-ua-platform", "\"Windows\"");
|
||||
item.Header.Add("upgrade-insecure-requests", "1");
|
||||
item.Header.Add("sec-fetch-site", "none");
|
||||
item.Header.Add("sec-fetch-mode", "navigate");
|
||||
item.Header.Add("sec-fetch-user", "?1");
|
||||
item.Header.Add("sec-fetch-dest", "document");
|
||||
item.Header.Add("priority", "u=0, i");
|
||||
item.ProxyIp = "127.0.0.1:8080";
|
||||
var result = new HttpHelper().GetHtml(item);
|
||||
|
||||
if (result.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new Exception($"请求失败: {result.StatusDescription}, 状态码: {result.StatusCode}");
|
||||
}
|
||||
|
||||
return result.Html;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取退货补偿页面信息
|
||||
/// </summary>
|
||||
/// <param name="orderSn">订单号</param>
|
||||
/// <param name="cookie">用户Cookie</param>
|
||||
/// <returns>返回页面HTML内容</returns>
|
||||
public static async System.Threading.Tasks.Task<string> GetReturnCompensationAsync(string orderSn, string cookie)
|
||||
{
|
||||
return await System.Threading.Tasks.Task.Run(() => GetReturnCompensation(orderSn, cookie));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取退货补偿页面信息(带完整Cookie字符串)
|
||||
/// </summary>
|
||||
/// <param name="orderSn">订单号</param>
|
||||
/// <param name="accessToken">PDD访问令牌</param>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <param name="userUin">用户UIN</param>
|
||||
/// <param name="extraCookies">额外Cookie键值对</param>
|
||||
/// <returns>返回页面HTML内容</returns>
|
||||
public static string GetReturnCompensationWithCookies(
|
||||
string orderSn,
|
||||
string accessToken,
|
||||
string userId,
|
||||
string userUin,
|
||||
System.Collections.Generic.Dictionary<string, string> extraCookies = null)
|
||||
{
|
||||
// 构建Cookie字符串
|
||||
var cookieBuilder = new StringBuilder();
|
||||
cookieBuilder.Append($"PDDAccessToken={accessToken}; ");
|
||||
cookieBuilder.Append($"pdd_user_id={userId}; ");
|
||||
cookieBuilder.Append($"pdd_user_uin={userUin}; ");
|
||||
|
||||
// 添加额外Cookie
|
||||
if (extraCookies != null)
|
||||
{
|
||||
foreach (var kv in extraCookies)
|
||||
{
|
||||
cookieBuilder.Append($"{kv.Key}={kv.Value}; ");
|
||||
}
|
||||
}
|
||||
|
||||
// 添加一些常用的固定Cookie
|
||||
cookieBuilder.Append("api_uid=CiNLVGqFh3IfyACAene3Ag==; ");
|
||||
cookieBuilder.Append("webp=1; ");
|
||||
cookieBuilder.Append("dilx=3EDqgJtFtp4E9OYvXLozM; ");
|
||||
|
||||
string cookie = cookieBuilder.ToString().TrimEnd(' ', ';');
|
||||
|
||||
return GetReturnCompensation(orderSn, cookie);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建完整的拼多多请求Cookie
|
||||
/// </summary>
|
||||
/// <param name="cookies">Cookie键值对</param>
|
||||
/// <returns>Cookie字符串</returns>
|
||||
public static string BuildCookieString(System.Collections.Generic.Dictionary<string, string> cookies)
|
||||
{
|
||||
if (cookies == null || cookies.Count == 0)
|
||||
return string.Empty;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
foreach (var kv in cookies)
|
||||
{
|
||||
builder.Append($"{kv.Key}={kv.Value}; ");
|
||||
}
|
||||
return builder.ToString().TrimEnd(' ', ';');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通用拼多多请求方法
|
||||
/// </summary>
|
||||
/// <param name="path">请求路径</param>
|
||||
/// <param name="queryParams">查询参数</param>
|
||||
/// <param name="cookie">Cookie字符串</param>
|
||||
/// <param name="method">请求方法</param>
|
||||
/// <param name="postData">POST数据</param>
|
||||
/// <returns>返回结果</returns>
|
||||
public static string Request(
|
||||
string path,
|
||||
System.Collections.Generic.Dictionary<string, string> queryParams = null,
|
||||
string cookie = null,
|
||||
MethodType method = MethodType.Get,
|
||||
string postData = null)
|
||||
{
|
||||
// 构建完整URL
|
||||
var urlBuilder = new StringBuilder(BASE_URL);
|
||||
urlBuilder.Append(path);
|
||||
|
||||
if (queryParams != null && queryParams.Count > 0)
|
||||
{
|
||||
urlBuilder.Append('?');
|
||||
int index = 0;
|
||||
foreach (var kv in queryParams)
|
||||
{
|
||||
if (index > 0) urlBuilder.Append('&');
|
||||
urlBuilder.Append($"{Uri.EscapeDataString(kv.Key)}={Uri.EscapeDataString(kv.Value)}");
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
var item = new HttpItem
|
||||
{
|
||||
URL = urlBuilder.ToString(),
|
||||
Method = method,
|
||||
Timeout = 30000,
|
||||
ReadWriteTimeout = 30000,
|
||||
KeepAlive = true,
|
||||
Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
||||
ContentType = "application/x-www-form-urlencoded",
|
||||
UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
|
||||
Cookie = cookie,
|
||||
Referer = BASE_URL,
|
||||
Allowautoredirect = true,
|
||||
Encoding = Encoding.UTF8,
|
||||
ResultType = ResultType.String,
|
||||
Postdata = postData
|
||||
};
|
||||
|
||||
// 添加安全相关Header
|
||||
item.Header.Add("sec-ch-ua", "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\"");
|
||||
item.Header.Add("sec-ch-ua-mobile", "?0");
|
||||
item.Header.Add("sec-ch-ua-platform", "\"Windows\"");
|
||||
item.Header.Add("upgrade-insecure-requests", "1");
|
||||
item.Header.Add("sec-fetch-site", "none");
|
||||
item.Header.Add("sec-fetch-mode", "navigate");
|
||||
item.Header.Add("sec-fetch-dest", "document");
|
||||
|
||||
var result = new HttpHelper().GetHtml(item);
|
||||
|
||||
if (result.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new Exception($"请求失败: {result.StatusDescription}, 状态码: {result.StatusCode}");
|
||||
}
|
||||
|
||||
return result.Html;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拼多多Cookie构建帮助类
|
||||
/// </summary>
|
||||
public static class PddCookieHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 从各个Cookie片段构建完整Cookie
|
||||
/// </summary>
|
||||
public static string BuildCookie(PddCookieInfo info)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
if (!string.IsNullOrEmpty(info.ApiUid))
|
||||
builder.Append($"api_uid={info.ApiUid}; ");
|
||||
if (!string.IsNullOrEmpty(info.NanoFp))
|
||||
builder.Append($"_nano_fp={info.NanoFp}; ");
|
||||
if (!string.IsNullOrEmpty(info.AccessToken))
|
||||
builder.Append($"PDDAccessToken={info.AccessToken}; ");
|
||||
if (!string.IsNullOrEmpty(info.UserId))
|
||||
builder.Append($"pdd_user_id={info.UserId}; ");
|
||||
if (!string.IsNullOrEmpty(info.UserUin))
|
||||
builder.Append($"pdd_user_uin={info.UserUin}; ");
|
||||
if (!string.IsNullOrEmpty(info.PddVds))
|
||||
builder.Append($"pdd_vds={info.PddVds}; ");
|
||||
|
||||
// 添加固定值
|
||||
builder.Append("webp=1; ");
|
||||
builder.Append("dilx=3EDqgJtFtp4E9OYvXLozM; ");
|
||||
|
||||
return builder.ToString().TrimEnd(' ', ';');
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拼多多Cookie信息类
|
||||
/// </summary>
|
||||
public class PddCookieInfo
|
||||
{
|
||||
public string ApiUid { get; set; } = "CiNLVGqFh3IfyACAene3Ag==";
|
||||
public string NanoFp { get; set; } = "XpmJnqgqnpCynqEYnC_vqOqYD6xToHisZXFzP~Qy";
|
||||
public string AccessToken { get; set; }
|
||||
public string UserId { get; set; }
|
||||
public string UserUin { get; set; }
|
||||
public string PddVds { get; set; }
|
||||
public string Jrpl { get; set; } = "m4Tc1seqEgg0YoYo7U4GeUxzylfpG8W7";
|
||||
public string Njrpl { get; set; } = "m4Tc1seqEgg0YoYo7U4GeUxzylfpG8W7";
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
>
|
||||
|
||||
|
||||
<sys:String x:Key="txt-fapiao"></sys:String>
|
||||
<sys:String x:Key="txt-calendar_work_week"></sys:String>
|
||||
<sys:String x:Key="txt-document_endnote"></sys:String>
|
||||
<sys:String x:Key="txt-leaf_three"></sys:String>
|
||||
@@ -1522,6 +1523,9 @@
|
||||
<sys:String x:Key="txt-task_list_square_ltr"></sys:String>
|
||||
|
||||
|
||||
<Geometry x:Key="icon-fapiao" PresentationOptions:Freeze="True">
|
||||
M170.666667 868.693333l98.346666-94.72a21.333333 21.333333 0 0 1 30.72 1.173334l98.176 110.122666 98.176-110.122666a21.333333 21.333333 0 0 1 31.829334 0l98.176 110.122666 98.176-110.122666a21.333333 21.333333 0 0 1 30.72-1.173334L853.333333 868.693333V128H170.666667v740.693333z m-42.666667 49.386667V106.709333C128 94.890667 137.706667 85.333333 149.333333 85.333333h725.333334c11.776 0 21.333333 9.642667 21.333333 21.184v811.541334a21.333333 21.333333 0 0 1-36.117333 16.149333l-118.506667-114.133333-99.349333 111.445333a21.333333 21.333333 0 0 1-31.850667 0L512 821.397333l-98.176 110.122667a21.333333 21.333333 0 0 1-31.850667 0l-99.349333-111.445333-118.485333 114.133333A21.333333 21.333333 0 0 1 128 918.08zM298.666667 320c0-11.776 9.493333-21.333333 21.184-21.333333h341.632c11.690667 0 21.184 9.472 21.184 21.333333 0 11.776-9.493333 21.333333-21.184 21.333333H319.850667A21.205333 21.205333 0 0 1 298.666667 320z m0 128c0-11.776 9.6-21.333333 21.482666-21.333333h255.701334c11.861333 0 21.482667 9.472 21.482666 21.333333 0 11.776-9.6 21.333333-21.482666 21.333333H320.149333A21.354667 21.354667 0 0 1 298.666667 448z m0 128c0-11.776 9.6-21.333333 21.482666-21.333333h255.701334c11.861333 0 21.482667 9.472 21.482666 21.333333 0 11.776-9.6 21.333333-21.482666 21.333333H320.149333A21.354667 21.354667 0 0 1 298.666667 576z
|
||||
</Geometry>
|
||||
<Geometry x:Key="icon-calendar_work_week" PresentationOptions:Freeze="True">
|
||||
M330.666667 298.666667a32 32 0 0 0-32 32v170.666666c0 17.664 14.336 32 32 32h362.666666a32 32 0 0 0 32-32v-170.666666a32 32 0 0 0-32-32h-362.666666z m32 170.666666V362.666667h298.666666V469.333333h-298.666666z
|
||||
M757.333333 896A138.666667 138.666667 0 0 0 896 757.333333V266.666667A138.666667 138.666667 0 0 0 757.333333 128H266.666667A138.666667 138.666667 0 0 0 128 266.666667v490.666666A138.666667 138.666667 0 0 0 266.666667 896h490.666666zM832 266.666667v490.666666a74.666667 74.666667 0 0 1-74.666667 74.666667H266.666667a74.666667 74.666667 0 0 1-74.666667-74.666667V266.666667c0-41.216 33.450667-74.666667 74.666667-74.666667h490.666666c41.216 0 74.666667 33.450667 74.666667 74.666667z
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 5223831 */
|
||||
src: url('iconfont.woff2?t=1786935726171') format('woff2'),
|
||||
url('iconfont.woff?t=1786935726171') format('woff'),
|
||||
url('iconfont.ttf?t=1786935726171') format('truetype');
|
||||
src: url('iconfont.woff2?t=1787127285848') format('woff2'),
|
||||
url('iconfont.woff?t=1787127285848') format('woff'),
|
||||
url('iconfont.ttf?t=1787127285848') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,10 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-fapiao:before {
|
||||
content: "\ed35";
|
||||
}
|
||||
|
||||
.icon-calendar_work_week:before {
|
||||
content: "\e850";
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
window._iconfont_svg_string_5223831='<svg><symbol id="icon-calendar_work_week" viewBox="0 0 1024 1024"><path d="M330.666667 298.666667a32 32 0 0 0-32 32v170.666666c0 17.664 14.336 32 32 32h362.666666a32 32 0 0 0 32-32v-170.666666a32 32 0 0 0-32-32h-362.666666z m32 170.666666V362.666667h298.666666V469.333333h-298.666666z" ></path><path d="M757.333333 896A138.666667 138.666667 0 0 0 896 757.333333V266.666667A138.666667 138.666667 0 0 0 757.333333 128H266.666667A138.666667 138.666667 0 0 0 128 266.666667v490.666666A138.666667 138.666667 0 0 0 266.666667 896h490.666666zM832 266.666667v490.666666a74.666667 74.666667 0 0 1-74.666667 74.666667H266.666667a74.666667 74.666667 0 0 1-74.666667-74.666667V266.666667c0-41.216 33.450667-74.666667 74.666667-74.666667h490.666666c41.216 0 74.666667 33.450667 74.666667 74.666667z" ></path></symbol><symbol id="icon-document_endnote" viewBox="0 0 1024 1024"><path d="M234.666667 181.333333a32 32 0 0 1 32-32H512V341.333333a85.333333 85.333333 0 0 0 85.333333 85.333334h192v416a32 32 0 0 1-32 32H554.666667V938.666667h202.666666A96 96 0 0 0 853.333333 842.666667V412.330667c0-19.797333-7.850667-38.784-21.888-52.778667l-252.330666-252.330667A74.666667 74.666667 0 0 0 526.336 85.333333H266.666667A96 96 0 0 0 170.666667 181.333333V512h64V181.333333z m341.333333 160V194.56l168.106667 168.106667H597.333333a21.333333 21.333333 0 0 1-21.333333-21.333334zM192 586.666667a32 32 0 0 1-32 32H106.666667v298.666666h53.333333a32 32 0 0 1 0 64H106.666667A64 64 0 0 1 42.666667 917.333333v-298.666666A64 64 0 0 1 106.666667 554.666667h53.333333a32 32 0 0 1 32 32z" ></path><path d="M394.666667 618.666667a32 32 0 0 1 0-64h53.333333a64 64 0 0 1 64 64v298.666666a64 64 0 0 1-64 64H394.666667a32 32 0 0 1 0-64h53.333333v-298.666666H394.666667zM277.333333 714.666667a32 32 0 0 1 32 32V896a32 32 0 0 1-64 0v-149.333333a32 32 0 0 1 32-32zM277.333333 682.666667a32 32 0 1 0 0-64 32 32 0 0 0 0 64z" ></path></symbol><symbol id="icon-leaf_three" viewBox="0 0 1024 1024"><path d="M810.666667 320c0 22.186667-3.072 43.648-8.832 64h69.12C908.373333 384 938.666667 414.293333 938.666667 451.669333V661.333333a277.333333 277.333333 0 0 1-449.493334 217.429334L438.613333 929.28a32 32 0 1 1-45.226666-45.226667l50.517333-50.56a277.077333 277.077333 0 0 1-40.704-70.613333A256 256 0 0 1 96 512V320.597333C96 284.928 124.928 256 160.597333 256h189.568A234.752 234.752 0 0 1 576 85.333333h173.226667c33.92 0 61.44 27.52 61.44 61.44V320zM414.805333 263.765333a256.554667 256.554667 0 0 1 165.504 132.266667A277.290667 277.290667 0 0 1 661.333333 384h72.96c7.978667-19.754667 12.373333-41.386667 12.373334-64v-170.666667h-170.666667a170.752 170.752 0 0 0-161.194667 114.432z m120.021334 569.344A213.333333 213.333333 0 0 0 874.666667 661.333333v-209.664a3.669333 3.669333 0 0 0-3.669334-3.669333H661.333333a213.333333 213.333333 0 0 0-171.776 339.84l181.162667-181.12a32 32 0 1 1 45.226667 45.226667l-181.12 181.162666zM384 661.333333c0-25.258667 3.413333-49.749333 9.728-73.002666L276.053333 470.613333a32 32 0 1 1 45.226667-45.226666l98.901333 98.858666a278.698667 278.698667 0 0 1 101.333334-102.485333A192 192 0 0 0 352 320H160.384l-0.213333 0.170667a0.938667 0.938667 0 0 0-0.128 0.213333l-0.042667 0.213333V512a192 192 0 0 0 226.816 188.842667A279.765333 279.765333 0 0 1 384 661.333333z" ></path></symbol><symbol id="icon-re_order_dots_horizontal" viewBox="0 0 1024 1024"><path d="M298.666667 661.333333a64 64 0 1 1-128 0 64 64 0 0 1 128 0z m0-298.666666a64 64 0 1 1-128 0 64 64 0 0 1 128 0z m298.666666 298.666666a64 64 0 1 1-128 0 64 64 0 0 1 128 0z m0-298.666666a64 64 0 1 1-128 0 64 64 0 0 1 128 0z m298.666667 298.666666a64 64 0 1 1-128 0 64 64 0 0 1 128 0z m0-298.666666a64 64 0 1 1-128 0 64 64 0 0 1 128 0z" ></path></symbol><symbol id="icon-task_list_square_rtl" viewBox="0 0 1024 1024"><path d="M277.333333 629.333333a32 32 0 0 1 32-32h149.333334a32 32 0 0 1 0 64h-149.333334a32 32 0 0 1-32-32z m32-266.666666a32 32 0 0 0 0 64h149.333334a32 32 0 0 0 0-64h-149.333334z m449.28-33.28a32 32 0 0 1 0 45.226666l-85.333333 85.333334a32 32 0 0 1-45.226667 0l-42.666666-42.666667a32 32 0 0 1 45.226666-45.226667l20.053334 20.053334 62.72-62.72a32 32 0 0 1 45.226666 0z m0 279.893333a32 32 0 1 0-45.226666-45.226667l-62.72 62.72-20.053334-20.053333a32 32 0 1 0-45.226666 45.226667l42.666666 42.666666a32 32 0 0 0 45.226667 0l85.333333-85.333333zM224 128A96 96 0 0 0 128 224v576A96 96 0 0 0 224 896h576A96 96 0 0 0 896 800V224A96 96 0 0 0 800 128H224zM192 224a32 32 0 0 1 32-32h576a32 32 0 0 1 32 32v576a32 32 0 0 1-32 32H224a32 32 0 0 1-32-32V224z" ></path></symbol><symbol id="icon-calendar_sync" viewBox="0 0 1024 1024"><path d="M757.333333 128A138.666667 138.666667 0 0 1 896 266.666667v246.272a276.096 276.096 0 0 0-64-30.250667V362.666667h-640v394.666666c0 41.216 33.450667 74.666667 74.666667 74.666667h216.021333c7.381333 22.784 17.578667 44.245333 30.250667 64H266.666667A138.666667 138.666667 0 0 1 128 757.333333V266.666667A138.666667 138.666667 0 0 1 266.666667 1Line truncated
|
||||
window._iconfont_svg_string_5223831='<svg><symbol id="icon-fapiao" viewBox="0 0 1024 1024"><path d="M170.666667 868.693333l98.346666-94.72a21.333333 21.333333 0 0 1 30.72 1.173334l98.176 110.122666 98.176-110.122666a21.333333 21.333333 0 0 1 31.829334 0l98.176 110.122666 98.176-110.122666a21.333333 21.333333 0 0 1 30.72-1.173334L853.333333 868.693333V128H170.666667v740.693333z m-42.666667 49.386667V106.709333C128 94.890667 137.706667 85.333333 149.333333 85.333333h725.333334c11.776 0 21.333333 9.642667 21.333333 21.184v811.541334a21.333333 21.333333 0 0 1-36.117333 16.149333l-118.506667-114.133333-99.349333 111.445333a21.333333 21.333333 0 0 1-31.850667 0L512 821.397333l-98.176 110.122667a21.333333 21.333333 0 0 1-31.850667 0l-99.349333-111.445333-118.485333 114.133333A21.333333 21.333333 0 0 1 128 918.08zM298.666667 320c0-11.776 9.493333-21.333333 21.184-21.333333h341.632c11.690667 0 21.184 9.472 21.184 21.333333 0 11.776-9.493333 21.333333-21.184 21.333333H319.850667A21.205333 21.205333 0 0 1 298.666667 320z m0 128c0-11.776 9.6-21.333333 21.482666-21.333333h255.701334c11.861333 0 21.482667 9.472 21.482666 21.333333 0 11.776-9.6 21.333333-21.482666 21.333333H320.149333A21.354667 21.354667 0 0 1 298.666667 448z m0 128c0-11.776 9.6-21.333333 21.482666-21.333333h255.701334c11.861333 0 21.482667 9.472 21.482666 21.333333 0 11.776-9.6 21.333333-21.482666 21.333333H320.149333A21.354667 21.354667 0 0 1 298.666667 576z" fill="#3D3D3D" ></path></symbol><symbol id="icon-calendar_work_week" viewBox="0 0 1024 1024"><path d="M330.666667 298.666667a32 32 0 0 0-32 32v170.666666c0 17.664 14.336 32 32 32h362.666666a32 32 0 0 0 32-32v-170.666666a32 32 0 0 0-32-32h-362.666666z m32 170.666666V362.666667h298.666666V469.333333h-298.666666z" ></path><path d="M757.333333 896A138.666667 138.666667 0 0 0 896 757.333333V266.666667A138.666667 138.666667 0 0 0 757.333333 128H266.666667A138.666667 138.666667 0 0 0 128 266.666667v490.666666A138.666667 138.666667 0 0 0 266.666667 896h490.666666zM832 266.666667v490.666666a74.666667 74.666667 0 0 1-74.666667 74.666667H266.666667a74.666667 74.666667 0 0 1-74.666667-74.666667V266.666667c0-41.216 33.450667-74.666667 74.666667-74.666667h490.666666c41.216 0 74.666667 33.450667 74.666667 74.666667z" ></path></symbol><symbol id="icon-document_endnote" viewBox="0 0 1024 1024"><path d="M234.666667 181.333333a32 32 0 0 1 32-32H512V341.333333a85.333333 85.333333 0 0 0 85.333333 85.333334h192v416a32 32 0 0 1-32 32H554.666667V938.666667h202.666666A96 96 0 0 0 853.333333 842.666667V412.330667c0-19.797333-7.850667-38.784-21.888-52.778667l-252.330666-252.330667A74.666667 74.666667 0 0 0 526.336 85.333333H266.666667A96 96 0 0 0 170.666667 181.333333V512h64V181.333333z m341.333333 160V194.56l168.106667 168.106667H597.333333a21.333333 21.333333 0 0 1-21.333333-21.333334zM192 586.666667a32 32 0 0 1-32 32H106.666667v298.666666h53.333333a32 32 0 0 1 0 64H106.666667A64 64 0 0 1 42.666667 917.333333v-298.666666A64 64 0 0 1 106.666667 554.666667h53.333333a32 32 0 0 1 32 32z" ></path><path d="M394.666667 618.666667a32 32 0 0 1 0-64h53.333333a64 64 0 0 1 64 64v298.666666a64 64 0 0 1-64 64H394.666667a32 32 0 0 1 0-64h53.333333v-298.666666H394.666667zM277.333333 714.666667a32 32 0 0 1 32 32V896a32 32 0 0 1-64 0v-149.333333a32 32 0 0 1 32-32zM277.333333 682.666667a32 32 0 1 0 0-64 32 32 0 0 0 0 64z" ></path></symbol><symbol id="icon-leaf_three" viewBox="0 0 1024 1024"><path d="M810.666667 320c0 22.186667-3.072 43.648-8.832 64h69.12C908.373333 384 938.666667 414.293333 938.666667 451.669333V661.333333a277.333333 277.333333 0 0 1-449.493334 217.429334L438.613333 929.28a32 32 0 1 1-45.226666-45.226667l50.517333-50.56a277.077333 277.077333 0 0 1-40.704-70.613333A256 256 0 0 1 96 512V320.597333C96 284.928 124.928 256 160.597333 256h189.568A234.752 234.752 0 0 1 576 85.333333h173.226667c33.92 0 61.44 27.52 61.44 61.44V320zM414.805333 263.765333a256.554667 256.554667 0 0 1 165.504 132.266667A277.290667 277.290667 0 0 1 661.333333 384h72.96c7.978667-19.754667 12.373333-41.386667 12.373334-64v-170.666667h-170.666667a170.752 170.752 0 0 0-161.194667 114.432z m120.021334 569.344A213.333333 213.333333 0 0 0 874.666667 661.333333v-209.664a3.669333 3.669333 0 0 0-3.669334-3.669333H661.333333a213.333333 213.333333 0 0 0-171.776 339.84l181.162667-181.12a32 32 0 1 1 45.226667 45.226667l-181.12 181.162666zM384 661.333333c0-25.258667 3.413333-49.749333 9.728-73.002666L276.053333 470.613333a32 32 0 1 1 45.226667-45.226666l98.901333 98.858666a278.698667 278.698667 0 0 1 101.333334-102.485333A192 192 0 0 0 352 320H160.384l-0.213333 0.170667a0.938667 0.938667 0 0 0-0.128 0.213333l-0.042667 0.213333V512a192 192 0 0 0 226.816 188.842667A279.765333 279.765333 0 0 1 384 661.333333z" ></path></symbol><symbol id="icon-re_order_dots_horizontal" viewBox="0 0 1024 1024"><path d="M298.666667 661.333333a64 64 0 1 1-128 0 64 64 0 0 1 128 0z m0-298.666666a64 64 0 1 1-128 0 64 64 0 0 1 128 0z m298.666666 298.666666a64 64 0 1 1-128 0 64 64 0 0 1 128 Line truncated
|
||||
@@ -5,6 +5,13 @@
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "5068538",
|
||||
"name": "发票",
|
||||
"font_class": "fapiao",
|
||||
"unicode": "ed35",
|
||||
"unicode_decimal": 60725
|
||||
},
|
||||
{
|
||||
"icon_id": "44033866",
|
||||
"name": "calendar_work_week",
|
||||
|
||||
Binary file not shown.
@@ -6,7 +6,9 @@
|
||||
<ImplicitUsings>true</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Resources\Fonts\iconfont.ttf" />
|
||||
</ItemGroup>
|
||||
@@ -31,4 +33,16 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="Resources\Fonts\FontGeometry.xaml">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>FontGeometry.tt</DependentUpon>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in new issue
Block a user