Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93b8b404f6 | ||
|
|
b842292817 | ||
|
|
233ec5d58d | ||
|
|
e12c419ef3 | ||
|
|
11599bc7d3 | ||
|
|
e4e8c34f8d | ||
|
|
39648cd9a9 | ||
|
|
f7082d8a7a | ||
|
|
b7abfb79c6 | ||
|
|
57c8b69252 | ||
|
|
4f1fa75ec8 | ||
|
|
a18191b1a6 | ||
|
|
4e449f31bd | ||
|
|
d77ec5cc2e | ||
|
|
66f1381950 | ||
|
|
dad8846752 | ||
|
|
be54dff06b | ||
|
|
f772c35ede | ||
|
|
993a91a545 | ||
|
|
446224f4cf | ||
|
|
ce828fe467 | ||
|
|
726eb6de16 |
No files matched your search
@@ -17,6 +17,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_SolutionItems", "_Solution
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
.gitignore = .gitignore
|
||||
Directory.Build.props = Directory.Build.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WxOrder.Framework", "WxOrder.Framework\WxOrder.Framework.csproj", "{3123963C-A0EC-4AD5-9916-DC063F73490B}"
|
||||
@@ -25,6 +26,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".test", ".test", "{14261B9B
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WXOrder.Test", "test\WXOrder.Test\WXOrder.Test.csproj", "{3790EC79-EA1D-4660-AB0D-EA0DE4FF77B7}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Trade.Client", "Source\Trade.Client\Trade.Client.csproj", "{BCABF48D-183D-A66F-33B6-B865CC0AD623}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MAX.Core", "Source\MAX.Core\MAX.Core.csproj", "{313C8137-00DA-4116-B3B4-68FC45CC0CE7}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Trade.Framework", "Source\Trade.Framework\Trade.Framework.csproj", "{0180FDFA-6984-462D-B406-65B460CDB47A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Main.Test", "Main.Test\Main.Test.csproj", "{890E871A-F6E7-42DA-85D5-3BF2F8ECAEDF}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MAX.Framework", "Source\MAX.Framework\MAX.Framework.csproj", "{EABFACFE-C74D-4111-9683-C6E7714F432D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -117,6 +128,86 @@ Global
|
||||
{3790EC79-EA1D-4660-AB0D-EA0DE4FF77B7}.Release|x64.Build.0 = Release|Any CPU
|
||||
{3790EC79-EA1D-4660-AB0D-EA0DE4FF77B7}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{3790EC79-EA1D-4660-AB0D-EA0DE4FF77B7}.Release|x86.Build.0 = Release|Any CPU
|
||||
{BCABF48D-183D-A66F-33B6-B865CC0AD623}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BCABF48D-183D-A66F-33B6-B865CC0AD623}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BCABF48D-183D-A66F-33B6-B865CC0AD623}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{BCABF48D-183D-A66F-33B6-B865CC0AD623}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{BCABF48D-183D-A66F-33B6-B865CC0AD623}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{BCABF48D-183D-A66F-33B6-B865CC0AD623}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{BCABF48D-183D-A66F-33B6-B865CC0AD623}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{BCABF48D-183D-A66F-33B6-B865CC0AD623}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{BCABF48D-183D-A66F-33B6-B865CC0AD623}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BCABF48D-183D-A66F-33B6-B865CC0AD623}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BCABF48D-183D-A66F-33B6-B865CC0AD623}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{BCABF48D-183D-A66F-33B6-B865CC0AD623}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{BCABF48D-183D-A66F-33B6-B865CC0AD623}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{BCABF48D-183D-A66F-33B6-B865CC0AD623}.Release|x64.Build.0 = Release|Any CPU
|
||||
{BCABF48D-183D-A66F-33B6-B865CC0AD623}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{BCABF48D-183D-A66F-33B6-B865CC0AD623}.Release|x86.Build.0 = Release|Any CPU
|
||||
{313C8137-00DA-4116-B3B4-68FC45CC0CE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{313C8137-00DA-4116-B3B4-68FC45CC0CE7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{313C8137-00DA-4116-B3B4-68FC45CC0CE7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{313C8137-00DA-4116-B3B4-68FC45CC0CE7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{313C8137-00DA-4116-B3B4-68FC45CC0CE7}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{313C8137-00DA-4116-B3B4-68FC45CC0CE7}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{313C8137-00DA-4116-B3B4-68FC45CC0CE7}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{313C8137-00DA-4116-B3B4-68FC45CC0CE7}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{313C8137-00DA-4116-B3B4-68FC45CC0CE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{313C8137-00DA-4116-B3B4-68FC45CC0CE7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{313C8137-00DA-4116-B3B4-68FC45CC0CE7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{313C8137-00DA-4116-B3B4-68FC45CC0CE7}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{313C8137-00DA-4116-B3B4-68FC45CC0CE7}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{313C8137-00DA-4116-B3B4-68FC45CC0CE7}.Release|x64.Build.0 = Release|Any CPU
|
||||
{313C8137-00DA-4116-B3B4-68FC45CC0CE7}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{313C8137-00DA-4116-B3B4-68FC45CC0CE7}.Release|x86.Build.0 = Release|Any CPU
|
||||
{0180FDFA-6984-462D-B406-65B460CDB47A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0180FDFA-6984-462D-B406-65B460CDB47A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0180FDFA-6984-462D-B406-65B460CDB47A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{0180FDFA-6984-462D-B406-65B460CDB47A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{0180FDFA-6984-462D-B406-65B460CDB47A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{0180FDFA-6984-462D-B406-65B460CDB47A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{0180FDFA-6984-462D-B406-65B460CDB47A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{0180FDFA-6984-462D-B406-65B460CDB47A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{0180FDFA-6984-462D-B406-65B460CDB47A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0180FDFA-6984-462D-B406-65B460CDB47A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0180FDFA-6984-462D-B406-65B460CDB47A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{0180FDFA-6984-462D-B406-65B460CDB47A}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{0180FDFA-6984-462D-B406-65B460CDB47A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{0180FDFA-6984-462D-B406-65B460CDB47A}.Release|x64.Build.0 = Release|Any CPU
|
||||
{0180FDFA-6984-462D-B406-65B460CDB47A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{0180FDFA-6984-462D-B406-65B460CDB47A}.Release|x86.Build.0 = Release|Any CPU
|
||||
{890E871A-F6E7-42DA-85D5-3BF2F8ECAEDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{890E871A-F6E7-42DA-85D5-3BF2F8ECAEDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{890E871A-F6E7-42DA-85D5-3BF2F8ECAEDF}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{890E871A-F6E7-42DA-85D5-3BF2F8ECAEDF}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{890E871A-F6E7-42DA-85D5-3BF2F8ECAEDF}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{890E871A-F6E7-42DA-85D5-3BF2F8ECAEDF}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{890E871A-F6E7-42DA-85D5-3BF2F8ECAEDF}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{890E871A-F6E7-42DA-85D5-3BF2F8ECAEDF}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{890E871A-F6E7-42DA-85D5-3BF2F8ECAEDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{890E871A-F6E7-42DA-85D5-3BF2F8ECAEDF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{890E871A-F6E7-42DA-85D5-3BF2F8ECAEDF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{890E871A-F6E7-42DA-85D5-3BF2F8ECAEDF}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{890E871A-F6E7-42DA-85D5-3BF2F8ECAEDF}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{890E871A-F6E7-42DA-85D5-3BF2F8ECAEDF}.Release|x64.Build.0 = Release|Any CPU
|
||||
{890E871A-F6E7-42DA-85D5-3BF2F8ECAEDF}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{890E871A-F6E7-42DA-85D5-3BF2F8ECAEDF}.Release|x86.Build.0 = Release|Any CPU
|
||||
{EABFACFE-C74D-4111-9683-C6E7714F432D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EABFACFE-C74D-4111-9683-C6E7714F432D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EABFACFE-C74D-4111-9683-C6E7714F432D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{EABFACFE-C74D-4111-9683-C6E7714F432D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{EABFACFE-C74D-4111-9683-C6E7714F432D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{EABFACFE-C74D-4111-9683-C6E7714F432D}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{EABFACFE-C74D-4111-9683-C6E7714F432D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{EABFACFE-C74D-4111-9683-C6E7714F432D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{EABFACFE-C74D-4111-9683-C6E7714F432D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EABFACFE-C74D-4111-9683-C6E7714F432D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EABFACFE-C74D-4111-9683-C6E7714F432D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{EABFACFE-C74D-4111-9683-C6E7714F432D}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{EABFACFE-C74D-4111-9683-C6E7714F432D}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{EABFACFE-C74D-4111-9683-C6E7714F432D}.Release|x64.Build.0 = Release|Any CPU
|
||||
{EABFACFE-C74D-4111-9683-C6E7714F432D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{EABFACFE-C74D-4111-9683-C6E7714F432D}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -126,6 +217,7 @@ Global
|
||||
{1AD45D66-6872-4522-ACF0-04E2C0514048} = {4A54DC2A-B47C-4ED1-A482-EE60161DB2C3}
|
||||
{5A410EB5-06AD-97BB-2314-9D7A808E12F7} = {1A7830FA-AC8F-40FB-A232-F0626812B8BE}
|
||||
{3790EC79-EA1D-4660-AB0D-EA0DE4FF77B7} = {14261B9B-E3EA-4BC0-B08D-4B3591AC13E5}
|
||||
{890E871A-F6E7-42DA-85D5-3BF2F8ECAEDF} = {14261B9B-E3EA-4BC0-B08D-4B3591AC13E5}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {38687A61-0347-49AC-98C7-E7F7148C0234}
|
||||
|
||||
@@ -20,6 +20,11 @@
|
||||
<converters:ListBoxIndexConverter x:Key="listBoxIndexConverter" />
|
||||
<converters:FlagColorConverter x:Key="flagColorConverter" />
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Basic.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/WxOrder.Framework;component/Resources/SkinLocal.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml" />
|
||||
|
||||
|
||||
<ResourceDictionary Source="Styles/BaseWindowStyle.xaml" />
|
||||
<ResourceDictionary Source="Styles/BaseDataGridStyle.xaml" />
|
||||
<ResourceDictionary Source="Styles/BaseButtonStyle.xaml" />
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
<Window x:Class="_29WxOrderWpf.Common.DialogWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:_29WxOrderWpf.Common"
|
||||
mc:Ignorable="d"
|
||||
Title="{Binding Title}"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
Background="#FFFFFF"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<Window.Resources>
|
||||
<Window
|
||||
x:Class="_29WxOrderWpf.Common.DialogWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:_29WxOrderWpf.Common"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Title="{Binding Title}"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
Background="#FFFFFF"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
mc:Ignorable="d">
|
||||
<Window.Resources>
|
||||
<Style x:Key="DialogWindowStyle" TargetType="Window">
|
||||
<Setter Property="Tag" Value="Default"/>
|
||||
<Setter Property="ResizeMode" Value="NoResize"/>
|
||||
<Setter Property="ShowInTaskbar" Value="False"/>
|
||||
<Setter Property="WindowChrome.GlassFrameThickness" Value="-1"/>
|
||||
<Setter Property="Tag" Value="Default" />
|
||||
<Setter Property="ResizeMode" Value="NoResize" />
|
||||
<Setter Property="ShowInTaskbar" Value="False" />
|
||||
<Setter Property="WindowChrome.GlassFrameThickness" Value="-1" />
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
<Grid>
|
||||
|
||||
</Grid>
|
||||
<Grid />
|
||||
</Window>
|
||||
@@ -37,7 +37,7 @@ using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using System.Xml.XPath;
|
||||
using TemuKJD.ToolForms;
|
||||
using _29WxOrderWpf.ViewModels;
|
||||
using Work;
|
||||
using Work._29Tools.BLL;
|
||||
using Work._29Tools.CefTools;
|
||||
@@ -641,7 +641,7 @@ namespace _29WxOrderWpf.Ctrls.BuyerCtrls
|
||||
if (data.Contains("\"error_code\":40001"))
|
||||
{
|
||||
currentReadBuyer = "";
|
||||
PddMobileLoginForm.CheckBuyerLogin(new List<BuyerLoginInfo> { buyer }, false);
|
||||
PddMobileLoginViewModel.CheckBuyerLogin(new List<BuyerLoginInfo> { buyer }, false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -957,7 +957,7 @@ namespace _29WxOrderWpf.Ctrls.BuyerCtrls
|
||||
//{"errorCode":72004,"errorMsg":"","error_code":72004,"error_msg":"","result":null}
|
||||
if (data.Contains("\"error_code\":40001") || data.Contains("\"error_code\":50000") || data.Contains("\"error_code\":72004"))
|
||||
{
|
||||
PddMobileLoginForm.CheckBuyerLogin(new List<BuyerLoginInfo> { buyer }, false);
|
||||
PddMobileLoginViewModel.CheckBuyerLogin(new List<BuyerLoginInfo> { buyer }, false);
|
||||
if (buyer.status == "掉线")
|
||||
currentReadBuyer = "";
|
||||
return;
|
||||
@@ -1364,7 +1364,7 @@ namespace _29WxOrderWpf.Ctrls.BuyerCtrls
|
||||
if (tokenSource.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
PddMobileLoginForm.CheckBuyerLogin(new List<BuyerLoginInfo>() { buyer });
|
||||
PddMobileLoginViewModel.CheckBuyerLogin(new List<BuyerLoginInfo>() { buyer });
|
||||
if (buyer.status != "在线")
|
||||
continue;
|
||||
|
||||
@@ -1387,7 +1387,7 @@ namespace _29WxOrderWpf.Ctrls.BuyerCtrls
|
||||
Thread.Sleep(100);
|
||||
Dispatcher.Invoke(new Action(() =>
|
||||
{
|
||||
if (buyer.wpf_browser != null)
|
||||
if (buyer.wpf_browser != null && buyer.wpf_browser.IsBrowserInitialized)
|
||||
{
|
||||
var frame = buyer.wpf_browser.GetBrowser().MainFrame;
|
||||
isInitial = buyer.wpf_browser != null && buyer.wpf_browser.IsBrowserInitialized && frame.IsValid;
|
||||
@@ -1446,7 +1446,7 @@ namespace _29WxOrderWpf.Ctrls.BuyerCtrls
|
||||
|
||||
if (lastCefReadOrderTime < DateTime.Now.AddSeconds(-30))
|
||||
{
|
||||
PddMobileLoginForm.CheckBuyerLogin(new List<BuyerLoginInfo>() { buyer });
|
||||
PddMobileLoginViewModel.CheckBuyerLogin(new List<BuyerLoginInfo>() { buyer });
|
||||
if (buyer.status != "在线")
|
||||
{
|
||||
BindBuyer(buyer, "店铺掉线");
|
||||
@@ -1522,7 +1522,7 @@ namespace _29WxOrderWpf.Ctrls.BuyerCtrls
|
||||
|
||||
if (lastCefReadOrderTime < DateTime.Now.AddSeconds(-30))
|
||||
{
|
||||
PddMobileLoginForm.CheckBuyerLogin(new List<BuyerLoginInfo>() { buyer });
|
||||
PddMobileLoginViewModel.CheckBuyerLogin(new List<BuyerLoginInfo>() { buyer });
|
||||
if (buyer.status != "在线")
|
||||
{
|
||||
BindBuyer(buyer, "店铺掉线");
|
||||
@@ -1534,7 +1534,7 @@ namespace _29WxOrderWpf.Ctrls.BuyerCtrls
|
||||
|
||||
if (times % 100 == 0)
|
||||
{
|
||||
PddMobileLoginForm.CheckBuyerLogin(new List<BuyerLoginInfo>() { buyer });
|
||||
PddMobileLoginViewModel.CheckBuyerLogin(new List<BuyerLoginInfo>() { buyer });
|
||||
}
|
||||
if (buyer.status != "在线")
|
||||
break;
|
||||
@@ -1604,7 +1604,7 @@ namespace _29WxOrderWpf.Ctrls.BuyerCtrls
|
||||
|
||||
if (lastCefReadOrderTime < DateTime.Now.AddSeconds(-30))
|
||||
{
|
||||
PddMobileLoginForm.CheckBuyerLogin(new List<BuyerLoginInfo>() { buyer });
|
||||
PddMobileLoginViewModel.CheckBuyerLogin(new List<BuyerLoginInfo>() { buyer });
|
||||
if (buyer.status != "在线")
|
||||
{
|
||||
BindBuyer(buyer, "店铺掉线");
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using _29WxOrderWpf.BaseWindows;
|
||||
using _29WxOrderWpf.Common;
|
||||
using _29WxOrderWpf.ViewModels;
|
||||
using _29WxOrderWpf.Views;
|
||||
using AllBase;
|
||||
using AllBase.Helper;
|
||||
using AllBase.Model.DBModel;
|
||||
@@ -24,146 +27,166 @@ using Work.BaseForms;
|
||||
|
||||
namespace _29WxOrderWpf.Ctrls.BuyerCtrls
|
||||
{
|
||||
/// <summary>
|
||||
/// PDDBuyerSelectCtrl.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class PDDBuyerSelectCtrl : UserControl
|
||||
{
|
||||
public PDDBuyerSelectCtrl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
/// <summary>
|
||||
/// PDDBuyerSelectCtrl.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class PDDBuyerSelectCtrl : UserControl
|
||||
{
|
||||
public PDDBuyerSelectCtrl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void UserControl_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
cmbPddBuyer.ItemsSource = GlobalConfig.PDDBuyerLoginList;
|
||||
}
|
||||
private void UserControl_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
cmbPddBuyer.ItemsSource = GlobalConfig.PDDBuyerLoginList;
|
||||
}
|
||||
|
||||
private void btnLoginPdd_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
BuyerLoginInfo buyer = null;
|
||||
if (cmbPddBuyer.SelectedItem != null)
|
||||
{
|
||||
buyer = cmbPddBuyer.SelectedItem as BuyerLoginInfo;
|
||||
}
|
||||
private void btnLoginPdd_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
BuyerLoginInfo buyer = null;
|
||||
if (cmbPddBuyer.SelectedItem != null)
|
||||
{
|
||||
buyer = cmbPddBuyer.SelectedItem as BuyerLoginInfo;
|
||||
}
|
||||
|
||||
PddMobileLoginForm pddBuyerForm = new PddMobileLoginForm(buyer);
|
||||
pddBuyerForm.openType = EnumOpenType.login;
|
||||
pddBuyerForm.InitUrl = "https://mobile.yangkeduo.com/login.html?from=https%3A%2F%2Fmobile.yangkeduo.com%2Fpersonal.html";
|
||||
pddBuyerForm.ShowDialog();
|
||||
MainWindow.Instance.GetPddBuyer();
|
||||
}
|
||||
PddMobileLoginView pddBuyerForm = new PddMobileLoginView(buyer);
|
||||
pddBuyerForm.ViewModel.OpenType = EnumOpenType.login;
|
||||
pddBuyerForm.ViewModel.InitUrl = "https://mobile.yangkeduo.com/login.html?from=https%3A%2F%2Fmobile.yangkeduo.com%2Fpersonal.html";
|
||||
//pddBuyerForm.ShowDialog();
|
||||
new DialogWindow()
|
||||
{
|
||||
Title = "买手登录",
|
||||
Content = pddBuyerForm,
|
||||
}.ShowDialog();
|
||||
|
||||
private void cmbPddBuyer_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.GetPosition(sender as IInputElement).X > cmbPddBuyer.ActualWidth - 18)
|
||||
return;
|
||||
MainWindow.Instance.GetPddBuyer();
|
||||
|
||||
BuyerLoginInfo buyer = null;
|
||||
if (cmbPddBuyer.SelectedItem != null)
|
||||
{
|
||||
buyer = cmbPddBuyer.SelectedItem as BuyerLoginInfo;
|
||||
}
|
||||
if (buyer == null)
|
||||
return;
|
||||
|
||||
PddMobileLoginForm.CheckBuyerLogin(new List<BuyerLoginInfo> { buyer }, false);
|
||||
if (buyer.status == "在线")
|
||||
{
|
||||
PddMobileLoginForm shopLoginForm = new PddMobileLoginForm(buyer);
|
||||
shopLoginForm.openType = EnumOpenType.manage;
|
||||
//shopLoginForm.WindowState = FormWindowState.Maximized;
|
||||
shopLoginForm.InitUrl = "https://mobile.yangkeduo.com";
|
||||
shopLoginForm.Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
PddMobileLoginForm shopLoginForm = new PddMobileLoginForm(buyer);
|
||||
shopLoginForm.openType = EnumOpenType.login;
|
||||
shopLoginForm.InitUrl = "https://mobile.yangkeduo.com/login.html?from=https%3A%2F%2Fmobile.yangkeduo.com%2Fpersonal.html";
|
||||
shopLoginForm.ShowDialog();
|
||||
MainWindow.Instance.GetPddBuyer();
|
||||
}
|
||||
}
|
||||
//PddMobileLoginForm pddBuyerForm = new PddMobileLoginForm(buyer);
|
||||
//pddBuyerForm.openType = TemuKJD.ToolForms.EnumOpenType.login;
|
||||
//pddBuyerForm.InitUrl = "https://mobile.yangkeduo.com/login.html?from=https%3A%2F%2Fmobile.yangkeduo.com%2Fpersonal.html";
|
||||
//pddBuyerForm.ShowDialog();
|
||||
//MainWindow.Instance.GetPddBuyer();
|
||||
}
|
||||
|
||||
private void cmbPddBuyer_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
BuyerLoginInfo buyer = null;
|
||||
if (cmbPddBuyer.SelectedItem != null)
|
||||
{
|
||||
buyer = cmbPddBuyer.SelectedItem as BuyerLoginInfo;
|
||||
}
|
||||
GlobalConfig.CurrentPddBuyer = buyer;
|
||||
}
|
||||
private void cmbPddBuyer_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.GetPosition(sender as IInputElement).X > cmbPddBuyer.ActualWidth - 18)
|
||||
return;
|
||||
|
||||
private void btnDeleteItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (BaseMsgWindow.ShowConfirm("确定删除吗?删除后不可恢复!").DialogResult != true)
|
||||
return;
|
||||
BuyerLoginInfo buyer = null;
|
||||
if (cmbPddBuyer.SelectedItem != null)
|
||||
{
|
||||
buyer = cmbPddBuyer.SelectedItem as BuyerLoginInfo;
|
||||
}
|
||||
if (buyer == null)
|
||||
return;
|
||||
|
||||
if (sender is Button btn)
|
||||
{
|
||||
if (btn.DataContext is BuyerLoginInfo buyer)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
var url = BaseClass.Build29PDDObjString("user/del_buyer");
|
||||
var data = UserLogin.GetSignaturex()
|
||||
+ "&buyer_id=" + buyer.buyer_id;
|
||||
var result = GetHtml.PostUtf8(url, data);
|
||||
if (result.Contains("\"code\":200"))
|
||||
{
|
||||
Dispatcher.Invoke(new Action(() =>
|
||||
{
|
||||
GlobalConfig.PDDBuyerLoginList.Remove(buyer);
|
||||
this.cmbPddBuyer.SelectedIndex = 0;
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
//var pddBuyer =
|
||||
bool islogin = false;
|
||||
PddMobileLoginViewModel.CheckBuyerLogin(new List<BuyerLoginInfo> { buyer }, false);
|
||||
PddMobileLoginView shopLoginForm = new PddMobileLoginView(buyer);
|
||||
if (buyer.status == "在线")
|
||||
{
|
||||
shopLoginForm.ViewModel.OpenType = EnumOpenType.manage;
|
||||
shopLoginForm.ViewModel.InitUrl = "https://mobile.yangkeduo.com";
|
||||
//shopLoginForm.Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
shopLoginForm.ViewModel.OpenType = EnumOpenType.login;
|
||||
shopLoginForm.ViewModel.InitUrl = "https://mobile.yangkeduo.com/login.html?from=https%3A%2F%2Fmobile.yangkeduo.com%2Fpersonal.html";
|
||||
//shopLoginForm.ShowDialog();
|
||||
islogin = true;
|
||||
|
||||
//var ids = dgvBuyer.Rows.Cast<DataGridViewRow>().Where(r => r.Cells["colSelect"].Value.ParseToBool() == true).Select(s => s.Cells["colBuyerId"].Value.ParseToString()).ToList();
|
||||
//var buyers = GlobalConfig.PDDBuyerLoginList.Where(b => ids.Contains(b.buyer_id)).ToList();
|
||||
//this.statusBar.pgbTaskProgress.Maximum = buyers.Count;
|
||||
//pbLoading.Visible = true;
|
||||
}
|
||||
new DialogWindow()
|
||||
{
|
||||
Title = "买手登录",
|
||||
Content = shopLoginForm,
|
||||
}.ShowDialog();
|
||||
if (islogin)
|
||||
MainWindow.Instance.GetPddBuyer();
|
||||
}
|
||||
|
||||
//Task.Run(() =>
|
||||
//{
|
||||
// int i = 0;
|
||||
// foreach (var buyer in buyers)
|
||||
// {
|
||||
// i++;
|
||||
// this.Invoke(new Action(() =>
|
||||
// {
|
||||
// this.statusBar.lblTaskStatus.Text = "正在删除...";
|
||||
// this.statusBar.pgbTaskProgress.Value = i;
|
||||
// this.statusBar.pgbTaskProgress.Text = i + "/" + buyers.Count;
|
||||
// }));
|
||||
private void cmbPddBuyer_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
BuyerLoginInfo buyer = null;
|
||||
if (cmbPddBuyer.SelectedItem != null)
|
||||
{
|
||||
buyer = cmbPddBuyer.SelectedItem as BuyerLoginInfo;
|
||||
}
|
||||
GlobalConfig.CurrentPddBuyer = buyer;
|
||||
}
|
||||
|
||||
private void btnDeleteItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (BaseMsgWindow.ShowConfirm("确定删除吗?删除后不可恢复!").DialogResult != true)
|
||||
return;
|
||||
|
||||
if (sender is Button btn)
|
||||
{
|
||||
if (btn.DataContext is BuyerLoginInfo buyer)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
var url = BaseClass.Build29PDDObjString("user/del_buyer");
|
||||
var data = UserLogin.GetSignaturex()
|
||||
+ "&buyer_id=" + buyer.buyer_id;
|
||||
var result = GetHtml.PostUtf8(url, data);
|
||||
if (result.Contains("\"code\":200"))
|
||||
{
|
||||
Dispatcher.Invoke(new Action(() =>
|
||||
{
|
||||
GlobalConfig.PDDBuyerLoginList.Remove(buyer);
|
||||
this.cmbPddBuyer.SelectedIndex = 0;
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
//var pddBuyer =
|
||||
|
||||
//var ids = dgvBuyer.Rows.Cast<DataGridViewRow>().Where(r => r.Cells["colSelect"].Value.ParseToBool() == true).Select(s => s.Cells["colBuyerId"].Value.ParseToString()).ToList();
|
||||
//var buyers = GlobalConfig.PDDBuyerLoginList.Where(b => ids.Contains(b.buyer_id)).ToList();
|
||||
//this.statusBar.pgbTaskProgress.Maximum = buyers.Count;
|
||||
//pbLoading.Visible = true;
|
||||
|
||||
//Task.Run(() =>
|
||||
//{
|
||||
// int i = 0;
|
||||
// foreach (var buyer in buyers)
|
||||
// {
|
||||
// i++;
|
||||
// this.Invoke(new Action(() =>
|
||||
// {
|
||||
// this.statusBar.lblTaskStatus.Text = "正在删除...";
|
||||
// this.statusBar.pgbTaskProgress.Value = i;
|
||||
// this.statusBar.pgbTaskProgress.Text = i + "/" + buyers.Count;
|
||||
// }));
|
||||
|
||||
|
||||
|
||||
// Thread.Sleep(100);
|
||||
// }
|
||||
// this.Invoke(new Action(() =>
|
||||
// {
|
||||
// this.statusBar.lblTaskStatus.Text = "删除完成";
|
||||
// this.statusBar.pgbTaskProgress.Value = buyers.Count;
|
||||
// this.statusBar.pgbTaskProgress.Text = buyers.Count + "/" + buyers.Count;
|
||||
// }));
|
||||
// GetPddBuyer();
|
||||
//});
|
||||
}
|
||||
// Thread.Sleep(100);
|
||||
// }
|
||||
// this.Invoke(new Action(() =>
|
||||
// {
|
||||
// this.statusBar.lblTaskStatus.Text = "删除完成";
|
||||
// this.statusBar.pgbTaskProgress.Value = buyers.Count;
|
||||
// this.statusBar.pgbTaskProgress.Text = buyers.Count + "/" + buyers.Count;
|
||||
// }));
|
||||
// GetPddBuyer();
|
||||
//});
|
||||
}
|
||||
|
||||
private void Image_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (MainWindow.Instance.gridPddBuyerOrderReadCtrl.Visibility != Visibility.Visible)
|
||||
MainWindow.Instance.gridPddBuyerOrderReadCtrl.Visibility = Visibility.Visible;
|
||||
if (Panel.GetZIndex(MainWindow.Instance.gridPddBuyerOrderReadCtrl) < 1)
|
||||
Panel.SetZIndex(MainWindow.Instance.gridPddBuyerOrderReadCtrl, 1);
|
||||
else
|
||||
Panel.SetZIndex(MainWindow.Instance.gridPddBuyerOrderReadCtrl, -1);
|
||||
}
|
||||
}
|
||||
private void Image_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (MainWindow.Instance.gridPddBuyerOrderReadCtrl.Visibility != Visibility.Visible)
|
||||
MainWindow.Instance.gridPddBuyerOrderReadCtrl.Visibility = Visibility.Visible;
|
||||
if (Panel.GetZIndex(MainWindow.Instance.gridPddBuyerOrderReadCtrl) < 1)
|
||||
Panel.SetZIndex(MainWindow.Instance.gridPddBuyerOrderReadCtrl, 1);
|
||||
else
|
||||
Panel.SetZIndex(MainWindow.Instance.gridPddBuyerOrderReadCtrl, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<UserControl
|
||||
x:Class="_29WxOrderWpf.Ctrls.BuyerCtrls.SkuSelectListRowCtrl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:_29WxOrderWpf.Ctrls.BuyerCtrls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
d:DesignHeight="150"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
<Grid>
|
||||
<Border
|
||||
Margin="4"
|
||||
BorderBrush="LightSkyBlue"
|
||||
BorderThickness="1"
|
||||
CornerRadius="10">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image
|
||||
x:Name="imgSku"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Width="100"
|
||||
Height="100"
|
||||
Margin="8"
|
||||
VerticalAlignment="Top"
|
||||
Cursor="Hand"
|
||||
MouseDown="imgSku_MouseDown"
|
||||
MouseEnter="imgSku_MouseEnter"
|
||||
MouseLeave="imgSku_MouseLeave" />
|
||||
<Grid
|
||||
Grid.Column="1"
|
||||
Margin="0,8"
|
||||
VerticalAlignment="Center">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<WrapPanel Visibility="{Binding SpecNameStr, Converter={StaticResource HasTextVisibility}}">
|
||||
<TextBox
|
||||
VerticalAlignment="Top"
|
||||
d:Opacity="0.5"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
IsReadOnly="True"
|
||||
Text="商品标题:"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBox
|
||||
VerticalAlignment="Top"
|
||||
d:Opacity="0.5"
|
||||
d:Text="商品标题"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding GoodsNameStr}"
|
||||
TextWrapping="Wrap" />
|
||||
</WrapPanel>
|
||||
<WrapPanel Grid.Row="1" Visibility="{Binding SpecNameStr, Converter={StaticResource HasTextVisibility}}">
|
||||
<TextBox
|
||||
VerticalAlignment="Top"
|
||||
d:Opacity="0.5"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
IsReadOnly="True"
|
||||
Text="商品规格:"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBox
|
||||
Grid.Row="0"
|
||||
VerticalAlignment="Top"
|
||||
d:Opacity="0.5"
|
||||
d:Text="SKU规格"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
FontWeight="Bold"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding SpecNameStr}"
|
||||
TextWrapping="Wrap" />
|
||||
</WrapPanel>
|
||||
|
||||
<WrapPanel Grid.Row="2" Visibility="{Binding groupPrice, Converter={StaticResource HasTextVisibility}}">
|
||||
<TextBox
|
||||
VerticalAlignment="Top"
|
||||
d:Opacity="0.5"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
IsReadOnly="True"
|
||||
Text="规格单价:"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBox
|
||||
Grid.Row="0"
|
||||
VerticalAlignment="Top"
|
||||
d:Opacity="0.5"
|
||||
d:Text="0.00"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding groupPrice}"
|
||||
TextWrapping="Wrap" />
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
<Button
|
||||
x:Name="btnAdd"
|
||||
Grid.Column="100"
|
||||
MinWidth="48"
|
||||
MinHeight="28"
|
||||
Margin="8"
|
||||
Padding="4"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Click="btnAdd_Click"
|
||||
Content="确定关联"
|
||||
Style="{StaticResource BasePageButtonStyle}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,81 @@
|
||||
using _29WxOrderWpf.ViewModels;
|
||||
using AllBase;
|
||||
using AllBase.Model.DBModel;
|
||||
using AllBase.Model.Result;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
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;
|
||||
using Work._29Tools.Forms.ToolForms;
|
||||
|
||||
namespace _29WxOrderWpf.Ctrls.BuyerCtrls;
|
||||
|
||||
/// <summary>
|
||||
/// SkuSelectListRowCtrl.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class SkuSelectListRowCtrl : UserControl
|
||||
{
|
||||
public SkuSelectListRowCtrl()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.DataContextChanged += SkuSelectListRowCtrl_DataContextChanged;
|
||||
}
|
||||
|
||||
private void SkuSelectListRowCtrl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (this.DataContext is frontSkuInfo3 frontSku)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
// 异步加载图片防止卡顿
|
||||
// 缩略图在有的电脑上无法显示
|
||||
//BitmapImage bImg = ImageUrlToBitmapImageConverter.LoadImageFromUrl(order_item.thumb_img + "?imageView2/1/w/100/format/webp|imageMogr2/auto-orient=");
|
||||
BitmapImage bImg = AllBase.ImageBuild.LoadBitmipImageFromUrl(frontSku.thumbUrl);
|
||||
Thread.Sleep(Randomx.GetId(200));
|
||||
Dispatcher.Invoke(new Action(() =>
|
||||
{
|
||||
imgSku.Source = bImg;
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void imgSku_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (this.DataContext is frontSkuInfo3 frontSku)
|
||||
{
|
||||
ShowImgForm imgFrom = new ShowImgForm(frontSku.thumbUrl);
|
||||
imgFrom.TopMost = true;
|
||||
imgFrom.Show();
|
||||
}
|
||||
}
|
||||
|
||||
private void imgSku_MouseEnter(object sender, MouseEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void imgSku_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private async void btnAdd_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (this.DataContext is frontSkuInfo3 frontSku)
|
||||
{
|
||||
await BuyMapSkuViewModel.BuyMapSkuInstance.SetOrderMapSku(frontSku);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -580,24 +580,33 @@
|
||||
BorderThickness="0,0,0,1">
|
||||
<WrapPanel>
|
||||
<CheckBox
|
||||
x:Name="chkMapProdOnly"
|
||||
x:Name="chkNoMapProd"
|
||||
Margin="8,0,4,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
BorderBrush="{StaticResource mainColorBrush}"
|
||||
Click="chkMapProdOnly_Click"
|
||||
Content="已关联拍单"
|
||||
FontSize="12" />
|
||||
<CheckBox
|
||||
x:Name="chkNoMapProdOnly"
|
||||
Margin="8,0,4,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
BorderBrush="{StaticResource mainColorBrush}"
|
||||
Click="chkNoMapProdOnly_Click"
|
||||
Click="chkMapProd_Click"
|
||||
Content="未关联拍单"
|
||||
FontSize="12" />
|
||||
|
||||
<CheckBox
|
||||
x:Name="chkMapProd"
|
||||
Margin="8,0,4,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
BorderBrush="{StaticResource mainColorBrush}"
|
||||
Click="chkMapProd_Click"
|
||||
Content="已关联商品"
|
||||
FontSize="12" />
|
||||
<CheckBox
|
||||
x:Name="chkMapSku"
|
||||
Margin="8,0,4,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
BorderBrush="{StaticResource mainColorBrush}"
|
||||
Click="chkMapProd_Click"
|
||||
Content="已关联拍单"
|
||||
FontSize="12" />
|
||||
|
||||
<Button
|
||||
Width="Auto"
|
||||
|
||||
@@ -42,7 +42,7 @@ using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using TemuKJD.ToolForms;
|
||||
using _29WxOrderWpf.ViewModels;
|
||||
using Work;
|
||||
using Work._29Tools.BLL;
|
||||
using Work.BuyUsers;
|
||||
@@ -302,24 +302,9 @@ namespace _29WxOrderWpf.Ctrls.OrderCtrls
|
||||
var pddBuyerAfterSales = GetPddBuyerAfterSales(order_ids);
|
||||
|
||||
// 读取关联拍单商品
|
||||
var urlMap = BaseClass.Build29WxObjString("order/wx_get_buy_prod_map");
|
||||
var dataMap = UserLogin.GetSignaturex()
|
||||
+ "&spu_codes=" + string.Join(",", orderItems.Select(oi => GetHtml.UrlEncodeUtf8(oi.out_product_id))) + "," + string.Join(",", orderItems.Select(oi => oi.product_id));
|
||||
dataMap += "&main_user_name=" + BaseClass.MainUserName;
|
||||
var resultMap = GetHtml.PostUtf8(urlMap, dataMap);
|
||||
if (string.IsNullOrEmpty(resultMap) || !resultMap.Contains("\"code\":200"))
|
||||
resultMap = GetHtml.PostUtf8(urlMap, dataMap);
|
||||
if (string.IsNullOrEmpty(resultMap) || !resultMap.Contains("\"code\":200"))
|
||||
{
|
||||
Dispatcher.Invoke(new Action(() =>
|
||||
{
|
||||
BaseMsgWindow.ShowError("订单读取失败 " + BaseRegex.GetStringJson(ref resultMap, "msg"));
|
||||
}));
|
||||
break;
|
||||
}
|
||||
List<WXBuyMapInfo> byuMapList = new List<WXBuyMapInfo>();
|
||||
try { byuMapList = JsonHelper.DeserializeJsonToList<WXBuyMapInfo>(JObject.Parse(resultMap)["data"].ParseToString()); } catch { }
|
||||
var buyMapList = GetBuyProdMap(orderItems);
|
||||
|
||||
var buyMapSkusList = GetBuyProdMapSkus(orderItems);
|
||||
|
||||
// 装载订单
|
||||
foreach (JObject jObj in jArr)
|
||||
@@ -347,14 +332,15 @@ namespace _29WxOrderWpf.Ctrls.OrderCtrls
|
||||
// 关联拍单关系 // 危险物品禁止拍单,仅关联安全商品
|
||||
//if (!GlobalConfig.lstDangerItemWord.Any(d => (item.title).ParseToString().Contains(d)))
|
||||
{
|
||||
item.BuyMap = byuMapList.FirstOrDefault(b => b.sku_code == item.out_sku_id && b.spu_code == item.out_product_id);
|
||||
item.BuyMap = buyMapList.FirstOrDefault(b => b.sku_code == item.out_sku_id && b.spu_code == item.out_product_id);
|
||||
if (item.BuyMap == null)
|
||||
item.BuyMap = byuMapList.FirstOrDefault(b => b.sku_code == item.sku_attr_str && b.spu_code == item.out_product_id);
|
||||
item.BuyMap = buyMapList.FirstOrDefault(b => b.sku_code == item.sku_attr_str && b.spu_code == item.out_product_id);
|
||||
if (item.BuyMap == null)
|
||||
item.BuyMap = byuMapList.FirstOrDefault(b => b.sku_code == item.out_sku_id && b.spu_code == item.product_id);
|
||||
item.BuyMap = buyMapList.FirstOrDefault(b => b.sku_code == item.out_sku_id && b.spu_code == item.product_id);
|
||||
if (item.BuyMap == null)
|
||||
item.BuyMap = byuMapList.FirstOrDefault(b => b.sku_code == item.sku_attr_str && b.spu_code == item.product_id);
|
||||
item.BuyMap = buyMapList.FirstOrDefault(b => b.sku_code == item.sku_attr_str && b.spu_code == item.product_id);
|
||||
}
|
||||
item.BuyMapSkus = buyMapSkusList.Where(s => s.spu_code == item.out_product_id || s.spu_code == item.product_id).OrderByDescending(s => s.mdy_time).ToList();
|
||||
|
||||
// 中转仓订单
|
||||
if (item.ItemOrder.orderItems.Count > 1)
|
||||
@@ -696,6 +682,87 @@ namespace _29WxOrderWpf.Ctrls.OrderCtrls
|
||||
return new List<order_pdd_buyer_after_sale>();
|
||||
}
|
||||
|
||||
public static List<WXBuyMapInfo> GetBuyProdMap(List<oms_wei_order_item_info> orderItems)
|
||||
{
|
||||
//foreach (var item in orderItems.ToList())
|
||||
//{
|
||||
// var buyMap = GlobalConfig.BuyMapList.FirstOrDefault(b => b.sku_code == item.out_sku_id && b.spu_code == item.out_product_id);
|
||||
// if (buyMap == null)
|
||||
// buyMap = GlobalConfig.BuyMapList.FirstOrDefault(b => b.sku_code == item.sku_attr_str && b.spu_code == item.out_product_id);
|
||||
// if (buyMap == null)
|
||||
// buyMap = GlobalConfig.BuyMapList.FirstOrDefault(b => b.sku_code == item.out_sku_id && b.spu_code == item.product_id);
|
||||
// if (buyMap == null)
|
||||
// buyMap = GlobalConfig.BuyMapList.FirstOrDefault(b => b.sku_code == item.sku_attr_str && b.spu_code == item.product_id);
|
||||
|
||||
// if (buyMap != null)
|
||||
// orderItems.Remove(item);
|
||||
//}
|
||||
if (orderItems == null || orderItems.Count == 0)
|
||||
{
|
||||
return GlobalConfig.BuyMapList.Where(m => orderItems.Any(oi => m.spu_code == oi.out_product_id || m.spu_code == oi.product_id)).ToList();
|
||||
}
|
||||
|
||||
var urlMap = BaseClass.Build29WxObjString("order/wx_get_buy_prod_map");
|
||||
var dataMap = UserLogin.GetSignaturex()
|
||||
+ "&spu_codes=" + string.Join(",", orderItems.Select(oi => GetHtml.UrlEncodeUtf8(oi.out_product_id))) + "," + string.Join(",", orderItems.Select(oi => oi.product_id));
|
||||
dataMap += "&main_user_name=" + BaseClass.MainUserName;
|
||||
var resultMap = GetHtml.PostUtf8(urlMap, dataMap);
|
||||
if (string.IsNullOrEmpty(resultMap) || !resultMap.Contains("\"code\":200"))
|
||||
resultMap = GetHtml.PostUtf8(urlMap, dataMap);
|
||||
if (string.IsNullOrEmpty(resultMap) || !resultMap.Contains("\"code\":200"))
|
||||
{
|
||||
BaseMsgWindow.ShowAnimateMessage("订单关联读取失败 " + BaseRegex.GetStringJson(ref resultMap, "msg"), "Error", 5);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
var buyMapList = JsonHelper.DeserializeJsonToList<WXBuyMapInfo>(JObject.Parse(resultMap)["data"].ParseToString());
|
||||
buyMapList = buyMapList.Where(b => !GlobalConfig.BuyMapList.Any(m => m.sku_code == b.sku_code && m.spu_code == b.spu_code)).ToList();
|
||||
GlobalConfig.BuyMapList.AddRange(buyMapList);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
return GlobalConfig.BuyMapList.Where(m => orderItems.Any(oi => m.spu_code == oi.out_product_id || m.spu_code == oi.product_id)).ToList();
|
||||
}
|
||||
|
||||
public static List<WXBuyMapSkusInfo> GetBuyProdMapSkus(List<oms_wei_order_item_info> orderItems)
|
||||
{
|
||||
//orderItems.RemoveAll(oi => GlobalConfig.BuyMapSkusList.Any(m => m.spu_code == oi.out_product_id || m.spu_code == oi.product_id));
|
||||
if (orderItems == null || orderItems.Count == 0)
|
||||
{
|
||||
return GlobalConfig.BuyMapSkusList.Where(m => orderItems.Any(oi => m.spu_code == oi.out_product_id || m.spu_code == oi.product_id)).ToList();
|
||||
}
|
||||
|
||||
var url = BaseClass.Build29WxObjString("order/wx_get_buy_pord_map_skus");
|
||||
var dataMap = UserLogin.GetSignaturex()
|
||||
+ "&spu_codes=" + string.Join(",", orderItems.Select(oi => GetHtml.UrlEncodeUtf8(oi.out_product_id))) + "," + string.Join(",", orderItems.Select(oi => oi.product_id));
|
||||
dataMap += "&main_user_name=" + BaseClass.MainUserName;
|
||||
var resultSkusMap = GetHtml.PostUtf8(url, dataMap);
|
||||
if (string.IsNullOrEmpty(resultSkusMap) || !resultSkusMap.Contains("\"code\":200"))
|
||||
{
|
||||
resultSkusMap = GetHtml.PostUtf8(url, dataMap);
|
||||
}
|
||||
if (string.IsNullOrEmpty(resultSkusMap) || !resultSkusMap.Contains("\"code\":200"))
|
||||
{
|
||||
BaseMsgWindow.ShowAnimateMessage("订单关联列表读取失败 " + BaseRegex.GetStringJson(ref resultSkusMap, "msg"), "Error", 5);
|
||||
return new List<WXBuyMapSkusInfo>();
|
||||
}
|
||||
try
|
||||
{
|
||||
var buyMapSkusList = JsonHelper.DeserializeJsonToList<WXBuyMapSkusInfo>(JObject.Parse(resultSkusMap)["data"].ParseToString());
|
||||
buyMapSkusList = buyMapSkusList.Where(b => !GlobalConfig.BuyMapSkusList.Any(m => m.map_goods_id == b.map_goods_id)).ToList();
|
||||
GlobalConfig.BuyMapSkusList.AddRange(buyMapSkusList);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new List<WXBuyMapSkusInfo>();
|
||||
}
|
||||
return GlobalConfig.BuyMapSkusList.Where(m => orderItems.Any(oi => m.spu_code == oi.out_product_id || m.spu_code == oi.product_id)).ToList();
|
||||
}
|
||||
|
||||
public void GetShopWaybillExceptionOrder()
|
||||
{
|
||||
foreach (var shop in GlobalConfig.ShopLoginList)
|
||||
@@ -1445,9 +1512,11 @@ namespace _29WxOrderWpf.Ctrls.OrderCtrls
|
||||
{
|
||||
if (!order.IsWaitBuy)
|
||||
return true;
|
||||
if (chkMapProdOnly.IsChecked == true && order.orderItems.All(oi => oi.BuyMap == null))
|
||||
if (chkMapSku.IsChecked == true && order.orderItems.All(oi => oi.BuyMap == null && (oi.BuyMapSkus == null || oi.BuyMapSkus.Count == 0)))
|
||||
return true;
|
||||
if (chkNoMapProdOnly.IsChecked == true && order.orderItems.All(oi => oi.BuyMap != null))
|
||||
if (chkMapProd.IsChecked == true && order.orderItems.All(oi => oi.BuyMapSkus == null || oi.BuyMapSkus.Count == 0))
|
||||
return true;
|
||||
if (chkNoMapProd.IsChecked == true && order.orderItems.All(oi => oi.BuyMap != null))
|
||||
return true;
|
||||
}
|
||||
if (filterHeadStr.StartsWith("待发货"))
|
||||
@@ -1753,17 +1822,15 @@ namespace _29WxOrderWpf.Ctrls.OrderCtrls
|
||||
GlobalConfig.OrderTabHeaderStr = ((tabOrderType.SelectedItem as TabItem)?.Header).ParseToString();
|
||||
FilterOrders();
|
||||
}
|
||||
|
||||
private void chkMapProdOnly_Click(object sender, RoutedEventArgs e)
|
||||
|
||||
private void chkMapProd_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (chkMapProdOnly.IsChecked == true)
|
||||
chkNoMapProdOnly.IsChecked = false;
|
||||
FilterOrders();
|
||||
}
|
||||
private void chkNoMapProdOnly_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (chkNoMapProdOnly.IsChecked == true)
|
||||
chkMapProdOnly.IsChecked = false;
|
||||
if (sender is CheckBox chk)
|
||||
{
|
||||
var isCheck = !chk.IsChecked.ParseToBool();
|
||||
chkNoMapProd.IsChecked = chkMapProd.IsChecked = chkMapSku.IsChecked = false;
|
||||
chk.IsChecked = !isCheck;
|
||||
}
|
||||
FilterOrders();
|
||||
}
|
||||
|
||||
@@ -2136,7 +2203,7 @@ namespace _29WxOrderWpf.Ctrls.OrderCtrls
|
||||
// 检测一下买手号
|
||||
if (buyer.status == "在线")
|
||||
{
|
||||
PddMobileLoginForm.CheckBuyerLogin(buyer);
|
||||
PddMobileLoginViewModel.CheckBuyerLogin(buyer);
|
||||
}
|
||||
if (buyer.status != "在线")
|
||||
{
|
||||
|
||||
@@ -1,42 +1,52 @@
|
||||
<UserControl x:Class="_29WxOrderWpf.Ctrls.OrderCtrls.OrderListRowCtrl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:_29WxOrderWpf.Ctrls.OrderCtrls"
|
||||
xmlns:uc="clr-namespace:_29WxOrderWpf.UserControls"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="230" d:DesignWidth="1200">
|
||||
<Border Margin="0,12,8,0" HorizontalAlignment="Stretch" VerticalAlignment="Center" BorderThickness="1"
|
||||
BorderBrush="{StaticResource ListRowBorderColorBrush}" CornerRadius="10">
|
||||
<Border.Style>
|
||||
<!-- 鼠标进入或悬停Border时切换颜色(悬停时Border的填充控件必须有填充Border,所在层级不限,仅填充透明Grid则触发器无效) -->
|
||||
<Style TargetType="Border">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource ListRowHoverBackColorBrush}"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Center">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<UserControl
|
||||
x:Class="_29WxOrderWpf.Ctrls.OrderCtrls.OrderListRowCtrl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:_29WxOrderWpf.Ctrls.OrderCtrls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:uc="clr-namespace:_29WxOrderWpf.UserControls"
|
||||
d:DesignHeight="230"
|
||||
d:DesignWidth="1200"
|
||||
mc:Ignorable="d">
|
||||
<Border
|
||||
Margin="0,12,8,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
BorderBrush="{StaticResource ListRowBorderColorBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="10">
|
||||
<Border.Style>
|
||||
<!-- 鼠标进入或悬停Border时切换颜色(悬停时Border的填充控件必须有填充Border,所在层级不限,仅填充透明Grid则触发器无效) -->
|
||||
<Style TargetType="Border">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource ListRowHoverBackColorBrush}" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Center">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!--子控件是普通控件而非模板页控件,无需指定DataContext="{Binding}"-->
|
||||
<local:OrderListRowHeaderCtrl></local:OrderListRowHeaderCtrl>
|
||||
<!--<local:OrderListRowHeaderCtrl DataContext="{Binding}"></local:OrderListRowHeaderCtrl>-->
|
||||
<Border Grid.Row="1" Background="Transparent" CornerRadius="0,0,12,12" >
|
||||
<Grid x:Name="gridChildOrder">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="6"></RowDefinition>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
<RowDefinition Height="6"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
<!-- 子控件是普通控件而非模板页控件,无需指定DataContext="{Binding}" -->
|
||||
<local:OrderListRowHeaderCtrl />
|
||||
<!--<local:OrderListRowHeaderCtrl DataContext="{Binding}"></local:OrderListRowHeaderCtrl>-->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Background="Transparent"
|
||||
CornerRadius="0,0,12,12">
|
||||
<Grid x:Name="gridChildOrder">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="6" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="6" />
|
||||
</Grid.RowDefinitions>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -30,6 +30,7 @@ using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Threading;
|
||||
using _29WxOrderWpf.ViewModels;
|
||||
using TemuKJD.ToolForms;
|
||||
using Work;
|
||||
using Work._29Tools.BLL;
|
||||
@@ -49,12 +50,13 @@ namespace _29WxOrderWpf.Ctrls.OrderCtrls
|
||||
public OrderListRowItemCtrl()
|
||||
{
|
||||
InitializeComponent();
|
||||
txtTransitOrderBuyerStr.AddHandler(TextBox.MouseUpEvent, new MouseButtonEventHandler(txtTransitOrderNumStr_MouseLeftButtonUp), true);
|
||||
txtTransitOrderNumStr.AddHandler(TextBox.MouseUpEvent, new MouseButtonEventHandler(txtTransitOrderNumStr_MouseLeftButtonUp), true);
|
||||
txtTransitOrderBuyerStr.AddHandler(TextBox.MouseUpEvent, new MouseButtonEventHandler(TransitOrderBuye_Click), true);
|
||||
txtTransitOrderNumStr.AddHandler(TextBox.MouseUpEvent, new MouseButtonEventHandler(TransitOrderBuye_Click), true);
|
||||
txtDirectBuyer.AddHandler(TextBox.MouseUpEvent, new MouseButtonEventHandler(txtDirectOrderNumStr_MouseLeftButtonUp), true);
|
||||
txtDirectBuyerOrderNo.AddHandler(TextBox.MouseUpEvent, new MouseButtonEventHandler(txtDirectOrderNumStr_MouseLeftButtonUp), true);
|
||||
txtRefundId.AddHandler(TextBox.MouseUpEvent, new MouseButtonEventHandler(txtRefundId_MouseLeftButtonUp), true);
|
||||
|
||||
|
||||
txtWarehouseStatus.AddHandler(TextBox.MouseEnterEvent, new MouseEventHandler(txtWarehouseStatus_MouseEnter), true);
|
||||
}
|
||||
|
||||
@@ -381,7 +383,7 @@ namespace _29WxOrderWpf.Ctrls.OrderCtrls
|
||||
// 拍单前检测买手号是否在线
|
||||
if (GlobalConfig.CurrentPddBuyer.status == "在线")
|
||||
{
|
||||
PddMobileLoginForm.CheckBuyerLogin(GlobalConfig.CurrentPddBuyer);
|
||||
PddMobileLoginViewModel.CheckBuyerLogin(GlobalConfig.CurrentPddBuyer);
|
||||
}
|
||||
if (GlobalConfig.CurrentPddBuyer.status != "在线")
|
||||
{
|
||||
@@ -715,11 +717,10 @@ namespace _29WxOrderWpf.Ctrls.OrderCtrls
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void txtTransitOrderNumStr_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
private void TransitOrderBuye_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var buyerName = txtTransitOrderBuyerStr.Text.Split(':').LastOrDefault();
|
||||
var outOrderId = txtTransitOrderNumStr.Text.Split(':').LastOrDefault();
|
||||
var buyerName = txtTransitOrderBuyerStr.Text;
|
||||
var outOrderId = txtTransitOrderNumStr.Text;
|
||||
if (string.IsNullOrEmpty(buyerName) || string.IsNullOrEmpty(outOrderId))
|
||||
{
|
||||
return;
|
||||
@@ -751,7 +752,7 @@ namespace _29WxOrderWpf.Ctrls.OrderCtrls
|
||||
// 拍单的时候检测一下买手号
|
||||
if (buyer.status == "在线")
|
||||
{
|
||||
PddMobileLoginForm.CheckBuyerLogin(buyer);
|
||||
PddMobileLoginViewModel.CheckBuyerLogin(buyer);
|
||||
}
|
||||
if (buyer.status != "在线")
|
||||
{
|
||||
@@ -811,7 +812,7 @@ namespace _29WxOrderWpf.Ctrls.OrderCtrls
|
||||
// 拍单的时候检测一下买手号
|
||||
if (buyer.status == "在线")
|
||||
{
|
||||
PddMobileLoginForm.CheckBuyerLogin(buyer);
|
||||
PddMobileLoginViewModel.CheckBuyerLogin(buyer);
|
||||
}
|
||||
if (buyer.status != "在线")
|
||||
{
|
||||
@@ -946,7 +947,7 @@ namespace _29WxOrderWpf.Ctrls.OrderCtrls
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
PddMobileLoginForm.CheckBuyerLogin(GlobalConfig.CurrentPddBuyer);
|
||||
PddMobileLoginViewModel.CheckBuyerLogin(GlobalConfig.CurrentPddBuyer);
|
||||
});
|
||||
}
|
||||
if (GlobalConfig.CurrentPddBuyer.status != "在线")
|
||||
@@ -1265,7 +1266,7 @@ namespace _29WxOrderWpf.Ctrls.OrderCtrls
|
||||
// 检测买手号是否掉线
|
||||
if (GlobalConfig.CurrentPddBuyer.status == "在线")
|
||||
{
|
||||
PddMobileLoginForm.CheckBuyerLogin(GlobalConfig.CurrentPddBuyer);
|
||||
PddMobileLoginViewModel.CheckBuyerLogin(GlobalConfig.CurrentPddBuyer);
|
||||
}
|
||||
if (GlobalConfig.CurrentPddBuyer.status != "在线")
|
||||
{
|
||||
@@ -1478,7 +1479,7 @@ namespace _29WxOrderWpf.Ctrls.OrderCtrls
|
||||
// 检测一下买手号
|
||||
if (buyer.status == "在线")
|
||||
{
|
||||
PddMobileLoginForm.CheckBuyerLogin(buyer);
|
||||
PddMobileLoginViewModel.CheckBuyerLogin(buyer);
|
||||
}
|
||||
if (buyer.status != "在线")
|
||||
{
|
||||
@@ -1584,6 +1585,18 @@ namespace _29WxOrderWpf.Ctrls.OrderCtrls
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void btnMapSkus_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
|
||||
if (DataContext is oms_wei_order_item_info orderItem)
|
||||
{
|
||||
BuyMapSkuSelectWindow window = new BuyMapSkuSelectWindow(orderItem);
|
||||
window.Show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,19 +81,15 @@
|
||||
|
||||
</Border>
|
||||
<Button
|
||||
Height="16"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Command="{Binding ShowOrderListCommand}"
|
||||
CommandParameter="ValidOrderCount"
|
||||
Content="明细"
|
||||
FontSize="12"
|
||||
Foreground="{StaticResource pageColorBrush}"
|
||||
Style="{StaticResource IconButtonStyle}">
|
||||
<TextBlock
|
||||
FontSize="24"
|
||||
Style="{StaticResource TextBlockIcon}"
|
||||
Text="{StaticResource txt-more_horizontal}" />
|
||||
</Button>
|
||||
Style="{StaticResource IconButtonStyle}" />
|
||||
</Grid>
|
||||
<!-- 采购金额 -->
|
||||
<Border Style="{StaticResource CardBorderStyle}">
|
||||
@@ -242,19 +238,15 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Button
|
||||
Height="16"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Command="{Binding ShowOrderListCommand}"
|
||||
CommandParameter="RefundOrderCount"
|
||||
Content="明细"
|
||||
FontSize="12"
|
||||
Foreground="{StaticResource pageColorBrush}"
|
||||
Style="{StaticResource IconButtonStyle}">
|
||||
<TextBlock
|
||||
FontSize="24"
|
||||
Style="{StaticResource TextBlockIcon}"
|
||||
Text="{StaticResource txt-more_horizontal}" />
|
||||
</Button>
|
||||
Style="{StaticResource IconButtonStyle}" />
|
||||
</Grid>
|
||||
|
||||
<!-- 采购退款成功金额 -->
|
||||
@@ -321,19 +313,15 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Button
|
||||
Height="16"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Command="{Binding ShowOrderListCommand}"
|
||||
CommandParameter="BuyerRefundRemainingAmount"
|
||||
Content="明细"
|
||||
FontSize="12"
|
||||
Foreground="{StaticResource pageColorBrush}"
|
||||
Style="{StaticResource IconButtonStyle}">
|
||||
<TextBlock
|
||||
FontSize="24"
|
||||
Style="{StaticResource TextBlockIcon}"
|
||||
Text="{StaticResource txt-more_horizontal}" />
|
||||
</Button>
|
||||
Style="{StaticResource IconButtonStyle}" />
|
||||
</Grid>
|
||||
|
||||
<!-- 退款率 -->
|
||||
|
||||
@@ -20,26 +20,13 @@
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<DataGrid
|
||||
<DataGrid Margin="5"
|
||||
d:ItemsSource="{d:SampleData ItemCount=10}"
|
||||
AlternatingRowBackground="#FAFAFA"
|
||||
AutoGenerateColumns="False"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
CanUserAddRows="False"
|
||||
CanUserDeleteRows="False"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeRows="False"
|
||||
ColumnHeaderStyle="{StaticResource BaseDataGridColumnHeaderStyle}"
|
||||
GridLinesVisibility="Horizontal"
|
||||
HeadersVisibility="Column"
|
||||
HorizontalGridLinesBrush="#EEEEEE"
|
||||
IsReadOnly="True"
|
||||
ItemsSource="{Binding Data}"
|
||||
RowHeight="44"
|
||||
SelectionMode="Single"
|
||||
SelectionUnit="FullRow"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
Style="{StaticResource DefaultDataGridStyle}"
|
||||
VerticalScrollBarVisibility="Visible">
|
||||
<DataGrid.Columns>
|
||||
<!-- 店名 -->
|
||||
<DataGridTextColumn
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using _29WxOrderWpf.Ctrls.Statistics.Models;
|
||||
using _29WxOrderWpf.BaseWindows;
|
||||
using _29WxOrderWpf.Ctrls.Statistics.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -36,7 +37,16 @@ public partial class ShowOrderDialogView : UserControl
|
||||
&& element.DataContext is ShopRevenueStatisticsRow row
|
||||
&& !string.IsNullOrEmpty(row.OrderId))
|
||||
{
|
||||
Clipboard.SetText(row.OrderId);
|
||||
Window window = Window.GetWindow(this);
|
||||
try
|
||||
{
|
||||
Clipboard.SetDataObject(row.OrderId);
|
||||
BaseMsgWindow.ShowAnimateMessage("复制成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
BaseMsgWindow.ShowError("复制失败 " + ex.Message,window: window);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+200
-68
@@ -1,72 +1,204 @@
|
||||
<basewindows:BaseWindow xmlns:basewindows="clr-namespace:_29WxOrderWpf.BaseWindows" x:Class="_29WxOrderWpf.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:_29WxOrderWpf"
|
||||
xmlns:orderctrls="clr-namespace:_29WxOrderWpf.Ctrls.OrderCtrls"
|
||||
xmlns:shopctrls="clr-namespace:_29WxOrderWpf.Ctrls.ShopCtrls"
|
||||
xmlns:userctrls="clr-namespace:_29WxOrderWpf.UserControls" xmlns:buyerctrls="clr-namespace:_29WxOrderWpf.Ctrls.BuyerCtrls"
|
||||
mc:Ignorable="d" Style="{StaticResource BaseNoneBorderWindowStyle}"
|
||||
x:Name="mainWindow" Title="MainWindow" Height="900" Width="1600" Loaded="Window_Loaded" Closing="BaseWindow_Closing">
|
||||
<basewindows:BaseWindow
|
||||
x:Class="_29WxOrderWpf.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:basewindows="clr-namespace:_29WxOrderWpf.BaseWindows"
|
||||
xmlns:buyerctrls="clr-namespace:_29WxOrderWpf.Ctrls.BuyerCtrls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:_29WxOrderWpf"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:orderctrls="clr-namespace:_29WxOrderWpf.Ctrls.OrderCtrls"
|
||||
xmlns:shopctrls="clr-namespace:_29WxOrderWpf.Ctrls.ShopCtrls"
|
||||
xmlns:userctrls="clr-namespace:_29WxOrderWpf.UserControls"
|
||||
x:Name="mainWindow"
|
||||
Title="MainWindow"
|
||||
Width="1600"
|
||||
Height="900"
|
||||
Closing="BaseWindow_Closing"
|
||||
Loaded="Window_Loaded"
|
||||
Style="{StaticResource BaseNoneBorderWindowStyle}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid Background="Transparent" x:Name="mainGrid" Margin="{Binding ElementName=mainWindow, Path=Padding}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="60"></RowDefinition>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid x:Name="gridTitle">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"></ColumnDefinition>
|
||||
<ColumnDefinition Width="auto"></ColumnDefinition>
|
||||
<ColumnDefinition></ColumnDefinition>
|
||||
<ColumnDefinition></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image Source="{StaticResource main29WeixinIcon}" Height="50" Width="50" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="4"></Image>
|
||||
<Label x:Name="lblAppName" FontSize="20" Margin="0,0,0,0" Foreground="{StaticResource mainColorLightBrush}" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" VerticalContentAlignment="Center" Content="拍单软件" Grid.Column="1"></Label>
|
||||
<Grid
|
||||
x:Name="mainGrid"
|
||||
Margin="{Binding ElementName=mainWindow, Path=Padding}"
|
||||
Background="Transparent">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="60" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid
|
||||
x:Name="gridTitle"
|
||||
Background="Transparent"
|
||||
MouseLeftButtonDown="gridTitle_MouseLeftButtonDown">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image
|
||||
Width="50"
|
||||
Height="50"
|
||||
Margin="4"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Source="{StaticResource main29WeixinIcon}" />
|
||||
<Label
|
||||
x:Name="lblAppName"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Content="拍单软件"
|
||||
FontSize="20"
|
||||
Foreground="{StaticResource mainColorLightBrush}" />
|
||||
|
||||
<WrapPanel Margin="0,4,80,0" Grid.Column="12" HorizontalAlignment="Right" VerticalAlignment="Top">
|
||||
<Label x:Name="lblDebug" FontSize="12" Margin="2,4,0,0" Foreground="{StaticResource mainColorLightBrush}" HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top" VerticalContentAlignment="Center" Content="[release]" Opacity="0.7" Grid.Column="4"></Label>
|
||||
<Label x:Name="lblVersion" FontSize="12" Margin="2,4,0,0" Foreground="{StaticResource mainColorLightBrush}" HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top" VerticalContentAlignment="Center" Content="版本信息" Opacity="0.7" Grid.Column="4"></Label>
|
||||
|
||||
</WrapPanel>
|
||||
|
||||
<Label x:Name="lblBalance" FontSize="12" Margin="0,25,80,0" HorizontalAlignment="Right" Foreground="#60ff66" MouseLeftButtonUp="lblBalance_MouseLeftButtonUp"
|
||||
VerticalAlignment="Top" VerticalContentAlignment="Center" Content="{Binding Balance}" Opacity="0.7" Grid.Column="4" Cursor="Hand"></Label>
|
||||
</Grid>
|
||||
<Grid x:Name="gridContainer" Grid.Row="1" Background="White" Margin="2,0,2,2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"></ColumnDefinition>
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
<orderctrls:OrderListCtrl x:Name="orderListCtrl" Grid.Column="1" Visibility="Collapsed"></orderctrls:OrderListCtrl>
|
||||
<Grid x:Name="gridPddBuyerOrderReadCtrl" Visibility="Hidden" Grid.ColumnSpan="2" Background="#7F000000" MouseLeftButtonUp="gridPddBuyerOrderReadCtrl_MouseLeftButtonUp">
|
||||
<Border CornerRadius="10" HorizontalAlignment="Right" Height="auto" Background="White" Margin="0,50,0,0">
|
||||
<buyerctrls:PDDBuyerOrderReadCtrl x:Name="pddBuyerOrderReadCtrl" Grid.Column="1" Visibility="Collapsed" Panel.ZIndex="-1" Width="1200"
|
||||
HorizontalAlignment="Right"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
<buyerctrls:PDDBuyerSelectCtrl x:Name="pddBuyerSelectCtrl" Visibility="Collapsed" Width="280" Grid.Column="1" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="8"/>
|
||||
<Grid x:Name="gridUser" Visibility="Hidden" Grid.ColumnSpan="2" Background="#7F000000">
|
||||
<Border CornerRadius="4" Width="500" Height="600" Background="White">
|
||||
<shopctrls:ShopListCtrl x:Name="shopListCtrl" Visibility="{Binding ElementName=gridUser,Mode=TwoWay,Path=Visibility}" Margin="2"
|
||||
Grid.ColumnSpan="2"></shopctrls:ShopListCtrl>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<userctrls:UcWindowBoxes x:Name="ucWindowBox" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="4" MaxBoxVisibility="Visible"/>
|
||||
<WrapPanel
|
||||
Grid.Column="12"
|
||||
Margin="0,4,80,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top">
|
||||
<Label
|
||||
x:Name="lblDebug"
|
||||
Grid.Column="4"
|
||||
Margin="2,4,0,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
VerticalContentAlignment="Center"
|
||||
Content="[release]"
|
||||
FontSize="12"
|
||||
Foreground="{StaticResource mainColorLightBrush}"
|
||||
Opacity="0.7" />
|
||||
<Label
|
||||
x:Name="lblVersion"
|
||||
Grid.Column="4"
|
||||
Margin="2,4,0,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
VerticalContentAlignment="Center"
|
||||
Content="版本信息"
|
||||
FontSize="12"
|
||||
Foreground="{StaticResource mainColorLightBrush}"
|
||||
Opacity="0.7" />
|
||||
|
||||
<Border x:Name="borderMessage" Grid.Row="1" Width="auto" Height="auto" HorizontalAlignment="Center" Margin="10,50" VerticalAlignment="Top" BorderThickness="1" Opacity="0"
|
||||
BorderBrush="{StaticResource pageColorBrush}" CornerRadius="6" Background="White" IsHitTestVisible="False"
|
||||
MinWidth="120" MinHeight="2" MaxWidth="1200" Panel.ZIndex="1000000">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect ShadowDepth="5" Color="#FF98638f" BlurRadius="40" Opacity="0.2"/>
|
||||
</Border.Effect>
|
||||
<TextBlock Text="这里是默认提示信息" Margin="10" VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="tbMessage" TextWrapping="Wrap"></TextBlock>
|
||||
</Border>
|
||||
<!--<Image x:Name="imageBig" HorizontalAlignment="Left" Height="800" Width="800" VerticalAlignment="Center" Grid.Row="1" Margin="200,0,0,0"/>-->
|
||||
</Grid>
|
||||
</WrapPanel>
|
||||
|
||||
<Label
|
||||
x:Name="lblBalance"
|
||||
Grid.Column="4"
|
||||
Margin="0,25,80,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
VerticalContentAlignment="Center"
|
||||
Content="{Binding Balance}"
|
||||
Cursor="Hand"
|
||||
FontSize="12"
|
||||
Foreground="#60ff66"
|
||||
MouseLeftButtonUp="lblBalance_MouseLeftButtonUp"
|
||||
Opacity="0.7" />
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="gridContainer"
|
||||
Grid.Row="1"
|
||||
Margin="2,0,2,2"
|
||||
Background="White">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<orderctrls:OrderListCtrl
|
||||
x:Name="orderListCtrl"
|
||||
Grid.Column="1"
|
||||
Visibility="Collapsed" />
|
||||
<Grid
|
||||
x:Name="gridPddBuyerOrderReadCtrl"
|
||||
Grid.ColumnSpan="2"
|
||||
Background="#7F000000"
|
||||
MouseLeftButtonUp="gridPddBuyerOrderReadCtrl_MouseLeftButtonUp"
|
||||
Visibility="Hidden">
|
||||
<Border
|
||||
Height="auto"
|
||||
Margin="0,50,0,0"
|
||||
HorizontalAlignment="Right"
|
||||
Background="White"
|
||||
CornerRadius="10">
|
||||
<buyerctrls:PDDBuyerOrderReadCtrl
|
||||
x:Name="pddBuyerOrderReadCtrl"
|
||||
Grid.Column="1"
|
||||
Width="1200"
|
||||
HorizontalAlignment="Right"
|
||||
Panel.ZIndex="-1"
|
||||
Visibility="Collapsed" />
|
||||
</Border>
|
||||
</Grid>
|
||||
<buyerctrls:PDDBuyerSelectCtrl
|
||||
x:Name="pddBuyerSelectCtrl"
|
||||
Grid.Column="1"
|
||||
Width="280"
|
||||
Margin="8"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Visibility="Collapsed" />
|
||||
<Grid
|
||||
x:Name="gridUser"
|
||||
Grid.ColumnSpan="2"
|
||||
Background="#7F000000"
|
||||
Visibility="Hidden">
|
||||
<Border
|
||||
Width="500"
|
||||
Height="600"
|
||||
Background="White"
|
||||
CornerRadius="4">
|
||||
<shopctrls:ShopListCtrl
|
||||
x:Name="shopListCtrl"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="2"
|
||||
Visibility="{Binding ElementName=gridUser, Mode=TwoWay, Path=Visibility}" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<userctrls:UcWindowBoxes
|
||||
x:Name="ucWindowBox"
|
||||
Margin="4"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
MaxBoxVisibility="Visible" />
|
||||
|
||||
<Border
|
||||
x:Name="borderMessage"
|
||||
Grid.Row="1"
|
||||
Width="auto"
|
||||
Height="auto"
|
||||
MinWidth="120"
|
||||
MinHeight="2"
|
||||
MaxWidth="1200"
|
||||
Margin="10,50"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
Panel.ZIndex="1000000"
|
||||
Background="White"
|
||||
BorderBrush="{StaticResource pageColorBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="6"
|
||||
IsHitTestVisible="False"
|
||||
Opacity="0">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect
|
||||
BlurRadius="40"
|
||||
Opacity="0.2"
|
||||
ShadowDepth="5"
|
||||
Color="#FF98638f" />
|
||||
</Border.Effect>
|
||||
<TextBlock
|
||||
x:Name="tbMessage"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="这里是默认提示信息"
|
||||
TextWrapping="Wrap" />
|
||||
</Border>
|
||||
<!--<Image x:Name="imageBig" HorizontalAlignment="Left" Height="800" Width="800" VerticalAlignment="Center" Grid.Row="1" Margin="200,0,0,0"/>-->
|
||||
</Grid>
|
||||
</basewindows:BaseWindow>
|
||||
@@ -450,5 +450,20 @@ namespace _29WxOrderWpf
|
||||
//else
|
||||
// Panel.SetZIndex(MainWindow.Instance.gridPddBuyerOrderReadCtrl, -1);
|
||||
}
|
||||
}
|
||||
|
||||
private void gridTitle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ClickCount == 2)
|
||||
{
|
||||
// 双击切换窗口状态
|
||||
this.WindowState = this.WindowState == WindowState.Maximized
|
||||
? WindowState.Normal
|
||||
: WindowState.Maximized;
|
||||
}
|
||||
else if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
this.DragMove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
using _29WxOrderWpf.BaseWindows;
|
||||
using _29WxOrderWpf.Commands;
|
||||
using AllBase;
|
||||
using AllBase.Helper;
|
||||
using AllBase.Model.DBModel;
|
||||
using AllBase.Model.MvvmBaseClasses;
|
||||
using AllBase.Model.Result;
|
||||
using NaiBa.Util.Extension;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NPOI.HSSF.Record;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Work;
|
||||
using Work.BaseForms;
|
||||
using Work.SearchSame;
|
||||
using static System.Windows.Forms.VisualStyles.VisualStyleElement.StartPanel;
|
||||
|
||||
namespace _29WxOrderWpf.ViewModels;
|
||||
|
||||
public class BuyMapSkuViewModel : PropertyNotifyObject
|
||||
{
|
||||
public static BuyMapSkuViewModel BuyMapSkuInstance { get; set; }
|
||||
/// <summary>
|
||||
/// 请求关闭窗口
|
||||
/// </summary>
|
||||
public event Action RequestClose;
|
||||
private volatile bool _isWindowLoaded = false;
|
||||
public oms_wei_order_item_info OrderItem
|
||||
{
|
||||
get { return this.GetValue(x => x.OrderItem); }
|
||||
set { this.SetValue(x => x.OrderItem, value); }
|
||||
}
|
||||
public ObservableCollection<WXBuyMapSkusInfo> BuyMapSkus
|
||||
{
|
||||
get { return this.GetValue(x => x.BuyMapSkus); }
|
||||
set { this.SetValue(x => x.BuyMapSkus, value); }
|
||||
}
|
||||
public ObservableCollection<frontSkuInfo3> FrontSkuInfos
|
||||
{
|
||||
get { return this.GetValue(x => x.FrontSkuInfos); }
|
||||
set { this.SetValue(x => x.FrontSkuInfos, value); }
|
||||
}
|
||||
public string ShopGoodsName
|
||||
{
|
||||
get { return this.GetValue(x => x.ShopGoodsName); }
|
||||
set { this.SetValue(x => x.ShopGoodsName, value); }
|
||||
}
|
||||
public string ShopSkuName
|
||||
{
|
||||
get { return this.GetValue(x => x.ShopSkuName); }
|
||||
set { this.SetValue(x => x.ShopSkuName, value); }
|
||||
}
|
||||
public string ShopSkuPrice
|
||||
{
|
||||
get { return this.GetValue(x => x.ShopSkuPrice); }
|
||||
set { this.SetValue(x => x.ShopSkuPrice, value); }
|
||||
}
|
||||
//pddFrontGoods3Lite
|
||||
|
||||
public BuyMapSkuViewModel(oms_wei_order_item_info orderItem)
|
||||
{
|
||||
if (BuyMapSkuInstance != null)
|
||||
BuyMapSkuInstance.RequestClose.Invoke();
|
||||
|
||||
BuyMapSkuInstance = this;
|
||||
|
||||
OrderItem = orderItem;
|
||||
if (orderItem != null)
|
||||
{
|
||||
this.ShopGoodsName = orderItem.title;
|
||||
this.ShopSkuName = orderItem.sku_attr_str;
|
||||
this.ShopSkuPrice = (orderItem.real_price * 0.01m).ParseToString();
|
||||
}
|
||||
if (orderItem?.BuyMapSkus != null && orderItem?.BuyMapSkus.Count > 0)
|
||||
{
|
||||
BuyMapSkus = new ObservableCollection<WXBuyMapSkusInfo>(orderItem.BuyMapSkus);
|
||||
SetCurrentFrontSkuInfo3(BuyMapSkus?.FirstOrDefault());
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCurrentFrontSkuInfo3(WXBuyMapSkusInfo skuInfo)
|
||||
{
|
||||
foreach (var info in BuyMapSkus)
|
||||
{
|
||||
info.IsChecked = false;
|
||||
}
|
||||
skuInfo.IsChecked = true;
|
||||
FrontSkuInfos = new ObservableCollection<frontSkuInfo3>(skuInfo?.pdd_front_goods3_Lite.skus);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗口加载完成时调用
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
_isWindowLoaded = true;
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
// todo get skus
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗口关闭时调用,停止后台任务
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
_isWindowLoaded = false;
|
||||
}
|
||||
|
||||
public async Task SetOrderMapSku(frontSkuInfo3 upSku)
|
||||
{
|
||||
var mapSkuProd = OrderItem.BuyMapSkus.FirstOrDefault(s => s.map_goods_id == upSku.goodsId.ParseToString());
|
||||
if (mapSkuProd == null)
|
||||
return;
|
||||
|
||||
var mapSku = mapSkuProd.pdd_front_goods3_Lite.skus.FirstOrDefault(s => string.Join(",", s.specs.Select(sp => sp.spec_value)) == upSku.SpecNameStr);
|
||||
if (mapSku == null)
|
||||
return;
|
||||
|
||||
var mapGoodsName = mapSkuProd.map_goods_name;
|
||||
var mapSkuName = string.Join(",", mapSku.specs.Select(sp => sp.spec_value));
|
||||
var mapGoodsId = mapSkuProd.map_goods_id;
|
||||
var mapSkuId = mapSku.skuId.ParseToString();
|
||||
var sendProvince = mapSkuProd.map_send_address;
|
||||
|
||||
string cmbTransitWarehouse = "";
|
||||
string txtBuyTransitWarehouse = "";
|
||||
var transitWarehouse = TransitWarehouseData.TransitWarehouseList.FirstOrDefault(t => t.TransId == mapSkuProd.transit_warehouse_id);
|
||||
if (transitWarehouse != null)
|
||||
{
|
||||
cmbTransitWarehouse = transitWarehouse?.TransName + " " + transitWarehouse?.TransDetailAddress;
|
||||
txtBuyTransitWarehouse = transitWarehouse?.TransDetailAddress;
|
||||
}
|
||||
|
||||
var spuCode = OrderItem.out_product_id.IsNullOrEmpty() ? OrderItem.product_id : OrderItem.out_product_id;
|
||||
var skuCode = OrderItem.out_sku_id.IsNullOrEmpty() ? OrderItem.sku_attr_str : OrderItem.out_sku_id;
|
||||
if (spuCode.IsNullOrEmpty())
|
||||
spuCode = OrderItem.product_id;
|
||||
if (await SaveSkuMap(spuCode, skuCode, mapGoodsName, mapSkuName, mapGoodsId, mapSkuId, transitWarehouse, sendProvince))
|
||||
RequestClose?.Invoke();
|
||||
}
|
||||
|
||||
public async Task<bool> SaveSkuMap(string spuCode, string skuCode, string mapGoodsName,
|
||||
string mapSkuName, string mapGoodsId, string mapSkuId, TransitWarehouseParam transitWarehouse, string sendProvince)
|
||||
{
|
||||
if (transitWarehouse == null)
|
||||
{
|
||||
BaseMsgForm.ShowError("仓库不能为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
string url;
|
||||
string upSpuCode = spuCode;
|
||||
string upSkuCode = skuCode;
|
||||
var data = UserLogin.GetSignaturex()
|
||||
+ "&spu_code=" + GetHtml.UrlEncodeUtf8(upSpuCode)
|
||||
+ "&sku_code=" + GetHtml.UrlEncodeUtf8(upSkuCode)
|
||||
+ "&main_user_name=" + BaseClass.MainUserName
|
||||
+ "&map_goods_name=" + mapGoodsName
|
||||
+ "&map_spec_name=" + mapSkuName
|
||||
+ "&map_goods_id=" + mapGoodsId
|
||||
+ "&map_sku_id=" + mapSkuId
|
||||
+ "&map_send_address=" + sendProvince
|
||||
+ "&transit_warehouse_id=" + transitWarehouse?.TransId;
|
||||
|
||||
url = BaseClass.Build29WxObjString("order/wx_add_mdy_buy_prod_map");
|
||||
var result = await GetHtml.PostUtf8Async(url, data);
|
||||
if (!result.Contains("\"code\":200"))
|
||||
result = await GetHtml.PostUtf8Async(url, data);
|
||||
if (!result.Contains("\"code\":200"))
|
||||
{
|
||||
BaseMsgForm.ShowError("保存失败" + BaseRegex.GetStringJson(ref result, "msg") + ",请稍后重试");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var urlMap = BaseClass.Build29WxObjString("order/wx_get_buy_prod_map");
|
||||
var dataMap = UserLogin.GetSignaturex()
|
||||
+ "&spu_codes=" + upSpuCode;
|
||||
//dataMap += "&sku_codes=" + upSkuCode;
|
||||
dataMap += "&main_user_name=" + BaseClass.MainUserName;
|
||||
var resultMap = await GetHtml.PostUtf8Async(urlMap, dataMap);
|
||||
if (string.IsNullOrEmpty(resultMap) || !resultMap.Contains("\"code\":200"))
|
||||
resultMap = await GetHtml.PostUtf8Async(urlMap, dataMap);
|
||||
if (string.IsNullOrEmpty(resultMap) || !resultMap.Contains("\"code\":200"))
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
List<WXBuyMapInfo> buyMapList = new List<WXBuyMapInfo>();
|
||||
try
|
||||
{
|
||||
buyMapList = JsonHelper.DeserializeJsonToList<WXBuyMapInfo>(JObject.Parse(resultMap)["data"].ParseToString());
|
||||
|
||||
GlobalConfig.BuyMapList.RemoveAll(b => buyMapList.Any(map => b.spu_code == map.spu_code && b.sku_code == map.sku_code));
|
||||
GlobalConfig.BuyMapList.AddRange(buyMapList);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
var map = buyMapList.FirstOrDefault(b => b.spu_code == upSpuCode && b.sku_code == upSkuCode);
|
||||
if (map != null)
|
||||
{
|
||||
foreach (var order in GlobalConfig.lstOrder)
|
||||
{
|
||||
if (order.IsWaitBuy)
|
||||
{
|
||||
foreach (var item in order.orderItems)
|
||||
{
|
||||
if ((item.out_product_id == map.spu_code && item.out_sku_id == map.sku_code) ||
|
||||
(item.out_product_id == map.spu_code && item.sku_attr_str == map.sku_code) ||
|
||||
(item.product_id == map.spu_code && item.out_sku_id == map.sku_code) ||
|
||||
(item.product_id == map.spu_code && item.sku_attr_str == map.sku_code))
|
||||
{
|
||||
item.BuyMap = map;
|
||||
|
||||
item.SetOrderItemTextProperty();
|
||||
// todo 此处刷新一条可能不够
|
||||
MainWindow.Instance.orderListCtrl.FilterOneOrder(order, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BaseMsgWindow.ShowInfo("保存成功!");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,519 @@
|
||||
using _29WxOrderWpf.CefHelper;
|
||||
using _29WxOrderWpf.Commands;
|
||||
using AllBase;
|
||||
using AllBase.Helper;
|
||||
using AllBase.Model.DBModel;
|
||||
using AllBase.Model.MvvmBaseClasses;
|
||||
using CefSharp;
|
||||
using CefSharp.Handler;
|
||||
using CefSharp.Wpf;
|
||||
using NaiBa.Util.Extension;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using TemuKJD.ToolForms;
|
||||
using Work;
|
||||
using Work._29Tools.BLL;
|
||||
using Work._29Tools.CefTools;
|
||||
|
||||
namespace _29WxOrderWpf.ViewModels
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 拼多多买手移动版登录窗口 视图模型(WPF 版 PddMobileLoginForm)
|
||||
/// </summary>
|
||||
public class PddMobileLoginViewModel : PropertyNotifyObject
|
||||
{
|
||||
// 拼多多买手退货退款售后地址
|
||||
// https://mobile.yangkeduo.com/complaint.html?order_sn=260718-101481213440980&after_sales_reason=0&after_sales_type=2&after_sales_code=1002
|
||||
|
||||
public static PddMobileLoginViewModel PddMobileLoginInstance { get; set; } = null;
|
||||
|
||||
/// <summary>浏览器创建完成后触发(视图在此把浏览器加入可视树)</summary>
|
||||
public event Action<ChromiumWebBrowser> BrowserCreated;
|
||||
|
||||
/// <summary>请求关闭窗口</summary>
|
||||
public event Action RequestClose;
|
||||
|
||||
public int CloseAfterSecond { get; set; }
|
||||
|
||||
private EnumOpenType _openType = EnumOpenType.manage;
|
||||
public EnumOpenType OpenType
|
||||
{
|
||||
get { return _openType; }
|
||||
set
|
||||
{
|
||||
_openType = value;
|
||||
OnPropertyChanged(nameof(OpenType));
|
||||
OnPropertyChanged(nameof(IsLoginMode));
|
||||
}
|
||||
}
|
||||
|
||||
public string InitUrl { get; set; } = "";
|
||||
|
||||
private string shopCookieHead = "yangkeduo.com";
|
||||
private string shopLoginUrl = "https://mobile.yangkeduo.com/login.html?from=https%3A%2F%2Fmobile.yangkeduo.com%2Fpersonal.html";
|
||||
private string shopMainUrl = "https://mobile.yangkeduo.com/index.html";
|
||||
|
||||
public ChromiumWebBrowser CefWebBrowser { get; private set; }
|
||||
private BuyerLoginInfo buyer = null;
|
||||
private List<ShopCompany> lstShopCompany = new List<ShopCompany>();
|
||||
private string currentUrl = "";
|
||||
private volatile bool _isWindowLoaded = false;
|
||||
|
||||
#region 界面绑定属性
|
||||
|
||||
/// <summary>用户名</summary>
|
||||
public string UserName
|
||||
{
|
||||
get { return this.GetValue(x => x.UserName); }
|
||||
set { this.SetValue(x => x.UserName, value); }
|
||||
}
|
||||
|
||||
/// <summary>密码</summary>
|
||||
public string PassWord
|
||||
{
|
||||
get { return this.GetValue(x => x.PassWord); }
|
||||
set { this.SetValue(x => x.PassWord, value); }
|
||||
}
|
||||
|
||||
/// <summary>地址栏</summary>
|
||||
public string Address
|
||||
{
|
||||
get { return this.GetValue(x => x.Address); }
|
||||
set { this.SetValue(x => x.Address, value); }
|
||||
}
|
||||
|
||||
/// <summary>登录成功后关闭窗口</summary>
|
||||
public bool IsCloseAfterLogin
|
||||
{
|
||||
get { return this.GetValue(x => x.IsCloseAfterLogin); }
|
||||
set { this.SetValue(x => x.IsCloseAfterLogin, value); }
|
||||
}
|
||||
|
||||
/// <summary>登录模式(显示扫码提示和导入PDDAccessToken面板)</summary>
|
||||
public bool IsLoginMode
|
||||
{
|
||||
get { return this.GetValue(x => x.IsLoginMode); }
|
||||
set { this.SetValue(x => x.IsLoginMode, value); }
|
||||
}
|
||||
|
||||
/// <summary>导入的PDDAccessToken</summary>
|
||||
public string AccessToken
|
||||
{
|
||||
get { return this.GetValue(x => x.AccessToken); }
|
||||
set { this.SetValue(x => x.AccessToken, value); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
|
||||
public ICommand ReloadCommand { get; }
|
||||
public ICommand LoginAgainCommand { get; }
|
||||
public ICommand StopCommand { get; }
|
||||
public ICommand CopyUserNameCommand { get; }
|
||||
public ICommand CopyPasswordCommand { get; }
|
||||
public ICommand SaveCookieCommand { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
public PddMobileLoginViewModel(BuyerLoginInfo buyerInfo)
|
||||
{
|
||||
PddMobileLoginInstance = this;
|
||||
|
||||
buyer = buyerInfo;
|
||||
UserName = buyerInfo?.buyer_name;
|
||||
PassWord = ""; //DataOpenSSL.DecryptString(buyerInfo.Password);
|
||||
IsCloseAfterLogin = true;
|
||||
IsLoginMode = OpenType == EnumOpenType.login;
|
||||
|
||||
ReloadCommand = new DelegateCommand(Reload);
|
||||
LoginAgainCommand = new DelegateCommand(LoginAgain);
|
||||
StopCommand = new DelegateCommand(StopButton);
|
||||
CopyUserNameCommand = new DelegateCommand(CopyUserName);
|
||||
CopyPasswordCommand = new DelegateCommand(CopyPassword);
|
||||
SaveCookieCommand = new DelegateCommand(SaveCookie);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗口加载完成时调用
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
IsLoginMode = OpenType == EnumOpenType.login;
|
||||
_isWindowLoaded = true;
|
||||
|
||||
if (CloseAfterSecond > 0)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
Thread.Sleep(1000 * CloseAfterSecond);
|
||||
if (_isWindowLoaded)
|
||||
{
|
||||
Application.Current?.Dispatcher.Invoke(new Action(() =>
|
||||
{
|
||||
if (_isWindowLoaded)
|
||||
RequestClose?.Invoke();
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
Task.Run(() =>
|
||||
{
|
||||
|
||||
AutoLogin();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗口关闭时调用,停止后台任务
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
_isWindowLoaded = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自动登录店铺后台
|
||||
/// </summary>
|
||||
private async void AutoLogin()
|
||||
{
|
||||
if (OpenType != EnumOpenType.login && buyer == null)
|
||||
return;
|
||||
_ = Application.Current.Dispatcher.InvokeAsync(async () =>
|
||||
{
|
||||
if (CefWebBrowser == null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(InitUrl))
|
||||
CefWebBrowser = WPFCEFBrowser.CreateChromiumWebBrowser(InitUrl, false);
|
||||
else
|
||||
CefWebBrowser = WPFCEFBrowser.CreateChromiumWebBrowser(shopMainUrl, false);
|
||||
|
||||
LocalResourceRequestHandler resourceRequestHandler = new LocalResourceRequestHandler();
|
||||
resourceRequestHandler.ResourceLoadCompleteEvent += RequestHandler_ResourceLoadComplete;
|
||||
resourceRequestHandler.BeforeResourceLoadEvent += RequestHandler_BeforeResourceLoad;
|
||||
CefWebBrowser.RequestHandler = new LocalRequestHandler(resourceRequestHandler);
|
||||
var lifeSapnHandler = new LocalLifeSpanHandler(PopWindowType.SelfWindow);
|
||||
CefWebBrowser.LifeSpanHandler = lifeSapnHandler;
|
||||
CefWebBrowser.KeyboardHandler = new KeyboardHandler();
|
||||
CefWebBrowser.FrameLoadEnd += CefWebBrowser_FrameLoadEnd;
|
||||
CefWebBrowser.AddressChanged += CefWebBrowser_AddressChanged;
|
||||
CefWebBrowser.LoadError += CefWebBrowser_LoadError;
|
||||
|
||||
// 通知视图把浏览器加入可视树(必须在等待浏览器初始化之前完成)
|
||||
BrowserCreated?.Invoke(CefWebBrowser);
|
||||
|
||||
List<Cookie> cookieLst = new List<Cookie>();
|
||||
try
|
||||
{
|
||||
if (buyer != null)
|
||||
{
|
||||
cookieLst = JsonHelper.DeserializeJsonToList<CefSharp.Cookie>(buyer?.cookie);
|
||||
if (cookieLst == null)
|
||||
cookieLst = new List<Cookie>();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// cef138必须init后方可设置或删除cookie
|
||||
|
||||
while (!CefWebBrowser.IsBrowserInitialized || !CefWebBrowser.GetBrowser().MainFrame.IsValid)
|
||||
await Task.Delay(20);
|
||||
WPFCEFBrowser.DeleteCookie(CefWebBrowser.GetCookieManager(null));
|
||||
|
||||
//cef 138 如果没有设置RequestContext 可能为空,直接getCookieManager
|
||||
var t = WPFCEFBrowser.SetCookieAsync(JsonHelper.SerializeObject(cookieLst), CefWebBrowser.GetCookieManager(null));
|
||||
foreach (var cook in cookieLst)
|
||||
{
|
||||
//var t = CEFBrowser.SetCookieAsync(cook.Domain.Trim('.'), cook.Name + "=" + cook.Value + ";", CefWebBrowser.GetCookieManager(null));
|
||||
//t.Wait(5000);
|
||||
}
|
||||
|
||||
//根据登录的具体店铺设置local storage
|
||||
if (!string.IsNullOrEmpty(buyer?.buyer_id))
|
||||
{
|
||||
//var storageJsCode = "window.localStorage.setItem('" + "mall-info-id" + "', '" + shop.ShopId + "');\r\n";
|
||||
//storageJsCode += "window.localStorage.setItem('" + "agentseller-mall-info-id" + "', '" + shop.ShopId + "');\r\n";
|
||||
//while (!CefWebBrowser.IsBrowserInitialized || !CefWebBrowser.GetBrowser().MainFrame.IsValid)
|
||||
// Thread.Sleep(20);
|
||||
//CefWebBrowser.GetBrowser().MainFrame.ExecuteJavaScriptAsync(storageJsCode);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(InitUrl))
|
||||
CefWebBrowser.Load(InitUrl);
|
||||
else
|
||||
CefWebBrowser.Load(shopMainUrl);
|
||||
|
||||
//不是登录的话就此返回
|
||||
if (OpenType != EnumOpenType.login)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
if (CefWebBrowser.IsLoading)
|
||||
await Task.Delay(100);
|
||||
//Thread.Sleep(100);
|
||||
}
|
||||
await Task.Delay(Randomx.GetId(1500, 2000));
|
||||
if (!_isWindowLoaded)
|
||||
return;
|
||||
|
||||
while (!CefWebBrowser.Address?.StartsWith(shopLoginUrl) ?? false)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
if (!_isWindowLoaded)
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private void CefWebBrowser_LoadError(object sender, CefSharp.LoadErrorEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
private void CefWebBrowser_AddressChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (Application.Current?.Dispatcher != null)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(new Action(() =>
|
||||
{
|
||||
currentUrl = e.NewValue.ParseToString();
|
||||
Address = e.NewValue.ParseToString();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private void CefWebBrowser_FrameLoadEnd(object sender, CefSharp.FrameLoadEndEventArgs e)
|
||||
{
|
||||
//js绑定C#对象 html页面内声明绑定的C#对象
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("(function(){");
|
||||
sb.Append("CefSharp.BindObjectAsync('shopObj');");
|
||||
sb.Append("})();");
|
||||
CefWebBrowser.GetBrowser().MainFrame.EvaluateScriptAsync(sb.ToString());
|
||||
}
|
||||
|
||||
private bool RequestHandler_BeforeResourceLoad(object sender, string url, string postData, IRequest request)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private void RequestHandler_ResourceLoadComplete(object sender, string url, string postData, string responseData, IRequest request)
|
||||
{
|
||||
if (url.Contains("https://mobile.yangkeduo.com/index.html") && OpenType == EnumOpenType.login)
|
||||
{
|
||||
Task.Run(() => { CefWebBrowser.Load("https://mobile.yangkeduo.com/personal.html"); });
|
||||
}
|
||||
|
||||
if (url.Contains("https://mobile.yangkeduo.com/personal.html"))
|
||||
{
|
||||
if (OpenType == EnumOpenType.login)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
var data = responseData;
|
||||
if (data.Contains("\"success\":true") || data.Contains("nickname"))
|
||||
{
|
||||
var uid = BaseRegex.GetStringJson(ref data, "uid");
|
||||
var nickname = BaseRegex.GetStringJson(ref data, "nickname");
|
||||
var username = nickname;
|
||||
|
||||
if (!string.IsNullOrEmpty(nickname) && !string.IsNullOrEmpty(uid))
|
||||
{
|
||||
Thread.Sleep(2000);
|
||||
|
||||
List<Cookie> cookieList = WPFCEFBrowser.GetCookieList(shopCookieHead, CefWebBrowser.GetCookieManager(null));
|
||||
|
||||
var buyerUrl = BaseClass.Build29PDDObjString("user/add_buyer");
|
||||
|
||||
if (this.buyer == null || this.buyer.buyer_id != uid)
|
||||
this.buyer = GlobalConfig.PDDBuyerLoginList.FirstOrDefault(b => b.buyer_id == uid);
|
||||
if (this.buyer != null)
|
||||
{
|
||||
this.buyer.user_name = username;
|
||||
this.buyer.buyer_nick = nickname;
|
||||
this.buyer.cookie = JsonHelper.SerializeObject(cookieList);
|
||||
buyerUrl = BaseClass.Build29PDDObjString("user/mdy_buyer");
|
||||
}
|
||||
|
||||
var buyerData = UserLogin.GetSignaturex()
|
||||
+ "&buyer_id=" + uid
|
||||
+ "&buyer_name=" + username
|
||||
+ "&buyer_nick=" + nickname;
|
||||
var result = GetHtml.PostUtf8(buyerUrl, buyerData);
|
||||
if (string.IsNullOrEmpty(result) || !result.Contains("\"code\":200"))
|
||||
result = GetHtml.PostUtf8(buyerUrl, buyerData);
|
||||
|
||||
buyerUrl = BaseClass.Build29PDDObjString("user/mdy_buyer_cookie");
|
||||
buyerData = UserLogin.GetSignaturex()
|
||||
+ "&buyer_id=" + uid
|
||||
+ "&status=" + "在线"
|
||||
+ "&cookie=" + GetHtml.UrlEncodeUtf8(JsonHelper.SerializeObject(cookieList));
|
||||
result = GetHtml.PostUtf8(buyerUrl, buyerData);
|
||||
if (string.IsNullOrEmpty(result) || !result.Contains("\"code\":200"))
|
||||
result = GetHtml.PostUtf8(buyerUrl, buyerData);
|
||||
|
||||
Application.Current?.Dispatcher.Invoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsCloseAfterLogin)
|
||||
RequestClose?.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{ }
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void StopButton()
|
||||
{
|
||||
}
|
||||
|
||||
private void CopyUserName()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(UserName))
|
||||
Clipboard.SetDataObject(UserName);
|
||||
}
|
||||
|
||||
private void CopyPassword()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(PassWord))
|
||||
Clipboard.SetDataObject(PassWord);
|
||||
}
|
||||
|
||||
private void Reload()
|
||||
{
|
||||
if (CefWebBrowser != null)
|
||||
{
|
||||
if (Address != "about:blank")
|
||||
CefWebBrowser.Load(Address);
|
||||
else
|
||||
CefWebBrowser.Load(shopMainUrl);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoginAgain()
|
||||
{
|
||||
if (this.currentUrl != shopLoginUrl)
|
||||
this.CefWebBrowser?.Load(shopLoginUrl);
|
||||
}
|
||||
|
||||
private void SaveCookie()
|
||||
{
|
||||
Cookie cookie = new Cookie();
|
||||
cookie.Name = "PDDAccessToken";
|
||||
cookie.Domain = "mobile.yangkeduo.com";
|
||||
cookie.Expires = DateTime.Now.AddDays(465);
|
||||
cookie.HttpOnly = true;
|
||||
cookie.Secure = true;
|
||||
cookie.Value = AccessToken.Trim();
|
||||
|
||||
if (this.CefWebBrowser != null)
|
||||
{
|
||||
var t = WPFCEFBrowser.SetCookieAsync(JsonHelper.SerializeObject(new List<Cookie> { cookie }), CefWebBrowser.GetCookieManager(null));
|
||||
t.Wait(2000);
|
||||
CefWebBrowser.Load("https://mobile.yangkeduo.com/personal.html");
|
||||
}
|
||||
}
|
||||
|
||||
#region 静态方法(检测/更新买手登录状态)
|
||||
|
||||
public static void CheckBuyerLogin(List<BuyerLoginInfo> checkBuyers, bool isShowProgress = true)
|
||||
{
|
||||
for (int i = 0; i < checkBuyers.Count; i++)
|
||||
{
|
||||
var buyer = GlobalConfig.PDDBuyerLoginList.FirstOrDefault(v => v.buyer_id == checkBuyers[i].buyer_id);
|
||||
var buys = GlobalConfig.PDDBuyerLoginList.Where(v => v.buyer_id == checkBuyers[i].buyer_id).ToList();
|
||||
if (buyer == null)
|
||||
continue;
|
||||
|
||||
// 先从服务端取最新cookie
|
||||
var url = BaseClass.Build29PDDObjString("user/get_buyer");
|
||||
var data = UserLogin.GetSignaturex();
|
||||
data += "&buyer_ids=" + buyer.buyer_id;
|
||||
data += "&page_size=1";
|
||||
var result = GetHtml.PostUtf8(url, data);
|
||||
if (string.IsNullOrEmpty(result) || !result.Contains("\"code\":200"))
|
||||
result = GetHtml.PostUtf8(url, data);
|
||||
if (!result.Contains("\"code\":200"))
|
||||
continue;
|
||||
List<BuyerLoginInfo> loginInfoLst = JsonHelper.DeserializeJsonToList<BuyerLoginInfo>(JsonHelper.JObjectParse(result)["data"].ParseToString());
|
||||
|
||||
var serverBuyer = loginInfoLst.FirstOrDefault();
|
||||
if (buyer != null && serverBuyer != null)
|
||||
{
|
||||
buyer.cookie = serverBuyer.cookie;
|
||||
foreach (var buy in buys)
|
||||
{
|
||||
buy.cookie = serverBuyer.cookie;
|
||||
}
|
||||
}
|
||||
|
||||
if (buyer != null)
|
||||
{
|
||||
var loginResult = CheckBuyerLogin(buyer);
|
||||
if (!loginResult.Contains("\"success\":true") && !loginResult.Contains("\"login\":true") && !loginResult.Contains("\"nickname\""))
|
||||
{
|
||||
buyer.cookie = "[]";
|
||||
foreach (var buy in buys)
|
||||
{
|
||||
buy.status = "掉线";
|
||||
buy.cookie = "[]";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var buy in buys)
|
||||
{
|
||||
buy.status = "在线";
|
||||
}
|
||||
}
|
||||
UpdateServerBuyerCookies(buyer); // 店铺cookie和状态写入服务端
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string CheckBuyerLogin(BuyerLoginInfo buyer, string refere = "https://mobile.yangkeduo.com/goods.html")
|
||||
{
|
||||
string url = "https://mobile.yangkeduo.com/proxy/api/api/apollo/v3/user/me?pdduid=" + buyer.buyer_id;
|
||||
return HttpResponseYkdBLL.YangkeduoGetResponse(buyer, url, refere);
|
||||
}
|
||||
|
||||
public static string UpdateServerBuyerCookies(BuyerLoginInfo buyer, string fromTool = null)
|
||||
{
|
||||
var url = BaseClass.Build29PDDObjString("user/mdy_buyer_cookie");
|
||||
var data = UserLogin.GetSignaturex()
|
||||
+ "&status=" + buyer.status
|
||||
+ "&cookie=" + GetHtml.UrlEncodeUtf8(buyer.cookie)
|
||||
+ "&buyer_id=" + buyer.buyer_id;
|
||||
var result = GetHtml.PostUtf8(url, data);
|
||||
if (!result.Contains("\"code\":200"))
|
||||
result = GetHtml.PostUtf8(url, data);
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<UserControl
|
||||
x:Class="_29WxOrderWpf.Views.PddMobileLoginView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:basewindows="clr-namespace:_29WxOrderWpf.BaseWindows"
|
||||
xmlns:comn="clr-namespace:_29WxOrderWpf.Common"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:wpf="WPFCommon"
|
||||
x:Name="pddMobileLoginView"
|
||||
Width="1024"
|
||||
Height="768"
|
||||
MinWidth="800"
|
||||
MinHeight="600"
|
||||
comn:DialogWindowProperty.ResizeMode="CanResizeWithGrip"
|
||||
comn:DialogWindowProperty.ShowMinButton="False"
|
||||
comn:DialogWindowProperty.Width="1300"
|
||||
Loaded="PddMobileLoginView_Loaded"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="BoolToVis" />
|
||||
</UserControl.Resources>
|
||||
<Grid Background="White">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="28" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 地址栏 -->
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
Background="White"
|
||||
BorderBrush="#E0E0E0"
|
||||
BorderThickness="0,0,0,1">
|
||||
<TextBox
|
||||
x:Name="txtAddress"
|
||||
Margin="3,2"
|
||||
VerticalContentAlignment="Center"
|
||||
BorderBrush="#D0D0D0"
|
||||
BorderThickness="1"
|
||||
KeyDown="txtAddress_KeyDown"
|
||||
Text="{Binding Address, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</Border>
|
||||
|
||||
<!-- 主区域:浏览器 + 右侧面板 -->
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="530" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 浏览器容器 -->
|
||||
<Grid
|
||||
x:Name="BrowserHost"
|
||||
Grid.Column="0"
|
||||
Background="White"
|
||||
ClipToBounds="True" />
|
||||
|
||||
<GridSplitter
|
||||
Grid.Column="1"
|
||||
Width="3"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="#E0E0E0"
|
||||
ResizeBehavior="PreviousAndNext"
|
||||
ShowsPreview="True" />
|
||||
|
||||
<!-- 右侧面板 -->
|
||||
<Border
|
||||
Grid.Column="2"
|
||||
Background="White"
|
||||
BorderBrush="#E0E0E0"
|
||||
BorderThickness="1,0,0,0">
|
||||
<StackPanel Margin="24,24,24,0">
|
||||
<TextBlock
|
||||
Margin="0,0,0,20"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="15"
|
||||
Foreground="#333333"
|
||||
Text="请使用拼多多手机版扫码登录"
|
||||
Visibility="{Binding IsLoginMode, Converter={StaticResource BoolToVis}}" />
|
||||
|
||||
<StackPanel Visibility="{Binding IsLoginMode, Converter={StaticResource BoolToVis}}">
|
||||
<TextBlock
|
||||
Margin="0,0,0,8"
|
||||
Foreground="#555555"
|
||||
Text="导入PDDAccessToken:" />
|
||||
<TextBox
|
||||
x:Name="txtAccessToken"
|
||||
Height="120"
|
||||
VerticalContentAlignment="Top"
|
||||
AcceptsReturn="True"
|
||||
BorderBrush="#D0D0D0"
|
||||
BorderThickness="1"
|
||||
Text="{Binding AccessToken, UpdateSourceTrigger=PropertyChanged}"
|
||||
TextWrapping="Wrap"
|
||||
VerticalScrollBarVisibility="Auto" />
|
||||
<Button
|
||||
Width="64"
|
||||
Height="26"
|
||||
Margin="0,10,0,0"
|
||||
HorizontalAlignment="Right"
|
||||
Command="{Binding SaveCookieCommand}"
|
||||
Content="保存"
|
||||
Style="{StaticResource BasePageButtonStyle}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,61 @@
|
||||
using _29WxOrderWpf.BaseWindows;
|
||||
using _29WxOrderWpf.ViewModels;
|
||||
using AllBase.Model.DBModel;
|
||||
using CefSharp.Wpf;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace _29WxOrderWpf.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// 拼多多买手移动版登录窗口(WPF 版 PddMobileLoginForm)
|
||||
/// </summary>
|
||||
public partial class PddMobileLoginView : UserControl
|
||||
{
|
||||
/// <summary>视图模型</summary>
|
||||
public PddMobileLoginViewModel ViewModel { get; }
|
||||
|
||||
public PddMobileLoginView(BuyerLoginInfo buyerInfo)
|
||||
{
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
ViewModel = new PddMobileLoginViewModel(buyerInfo);
|
||||
ViewModel.BrowserCreated += ViewModel_BrowserCreated;
|
||||
//ViewModel.RequestClose += ViewModel_RequestClose;
|
||||
DataContext = ViewModel;
|
||||
}
|
||||
|
||||
private void PddMobileLoginView_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.Start();
|
||||
}
|
||||
|
||||
private void PddMobileLoginView_Closed(object sender, System.EventArgs e)
|
||||
{
|
||||
ViewModel.Stop();
|
||||
}
|
||||
|
||||
private void ViewModel_BrowserCreated(ChromiumWebBrowser browser)
|
||||
{
|
||||
Grid.SetRow(browser, 0);
|
||||
Grid.SetColumn(browser, 0);
|
||||
BrowserHost.Children.Add(browser);
|
||||
}
|
||||
|
||||
//private void ViewModel_RequestClose()
|
||||
//{
|
||||
// if (this.IsLoaded)
|
||||
// this.Close();
|
||||
//}
|
||||
|
||||
private void txtAddress_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter)
|
||||
{
|
||||
ViewModel.ReloadCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +1,163 @@
|
||||
<basewindows:BaseWindow x:Class="_29WxOrderWpf.Windows.LoginWindows.LoginWindow" xmlns:basewindows="clr-namespace:_29WxOrderWpf.BaseWindows"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:_29WxOrderWpf.Windows.LoginWindows"
|
||||
xmlns:userctrls="clr-namespace:_29WxOrderWpf.UserControls"
|
||||
mc:Ignorable="d" Style="{StaticResource BaseNoneBorderWindowStyle}"
|
||||
x:Name="loginWindow" Title="登录" Height="300" Width="400" ResizeMode="NoResize" Loaded="loginWindow_Loaded" Closing="BaseWindow_Closing">
|
||||
<Grid Background="Transparent">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="120"></RowDefinition>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<Border Background="{StaticResource mainLinearColorBrush}"></Border>
|
||||
<Grid Grid.Row="1" Margin="2,0,2,2" Background="White">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition></ColumnDefinition>
|
||||
<ColumnDefinition Width="180"></ColumnDefinition>
|
||||
<ColumnDefinition></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid Grid.Column="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="20"></RowDefinition>
|
||||
<RowDefinition></RowDefinition>
|
||||
<RowDefinition></RowDefinition>
|
||||
<RowDefinition></RowDefinition>
|
||||
<RowDefinition></RowDefinition>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
<RowDefinition Height="20"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<Border Grid.Row="1" BorderBrush="{StaticResource mainColorBrush}" BorderThickness="1" CornerRadius="4" Height="26">
|
||||
<Grid>
|
||||
<ComboBox x:Name="cmbUserNameMobile" Margin="3,0" VerticalContentAlignment="Center" Style="{StaticResource BaseComboBoxStyle}"
|
||||
SelectionChanged="cmbUserNameMobile_SelectionChanged">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<!--<Grid HorizontalAlignment="Stretch" Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ComboBox}, Path=Width}">
|
||||
<basewindows:BaseWindow
|
||||
x:Class="_29WxOrderWpf.Windows.LoginWindows.LoginWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:basewindows="clr-namespace:_29WxOrderWpf.BaseWindows"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:_29WxOrderWpf.Windows.LoginWindows"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:userctrls="clr-namespace:_29WxOrderWpf.UserControls"
|
||||
x:Name="loginWindow"
|
||||
Title="登录"
|
||||
Width="400"
|
||||
Height="300"
|
||||
Closing="BaseWindow_Closing"
|
||||
Loaded="loginWindow_Loaded"
|
||||
ResizeMode="NoResize"
|
||||
Style="{StaticResource BaseNoneBorderWindowStyle}"
|
||||
mc:Ignorable="d">
|
||||
<Grid Background="Transparent">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="120" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Border Background="{StaticResource mainLinearColorBrush}" />
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Margin="2,0,2,2"
|
||||
Background="White">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="180" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid Grid.Column="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="20" />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="20" />
|
||||
</Grid.RowDefinitions>
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Height="26"
|
||||
BorderBrush="{StaticResource mainColorBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4">
|
||||
<Grid>
|
||||
<ComboBox
|
||||
x:Name="cmbUserNameMobile"
|
||||
Margin="3,0"
|
||||
VerticalContentAlignment="Center"
|
||||
SelectionChanged="cmbUserNameMobile_SelectionChanged"
|
||||
Style="{StaticResource BaseComboBoxStyle}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<!--<Grid HorizontalAlignment="Stretch" Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ComboBox}, Path=Width}">
|
||||
<TextBlock Text="{Binding}"/>
|
||||
<Button x:Name="btnDeleteItem" HorizontalAlignment="Right" Content="X" Click="btnDeleteItem_Click"/>
|
||||
</Grid>-->
|
||||
<Grid HorizontalAlignment="Stretch" Width="140">
|
||||
<TextBlock x:Name="txtName" Text="{Binding}" Padding="0" VerticalAlignment="Center"/>
|
||||
<userctrls:UcImageButton Width="17" Height="17" ButtonImage="{StaticResource imgCloseGray}" BorderThickness="0" Cursor="Hand"
|
||||
MouseOverButtonImage="{StaticResource imgCloseGray}" MousePressButtonImage="{StaticResource imgCloseGray}" Background="Transparent"
|
||||
x:Name="btnDeleteItem" Tag="{Binding ElementName=txtName, Path=Text}" HorizontalAlignment="Right" Click="btnDeleteItem_Click"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border Grid.Row="2" BorderBrush="{StaticResource mainColorBrush}" BorderThickness="1" CornerRadius="4" Height="26">
|
||||
<PasswordBox x:Name="txtPassword" Margin="6,0" VerticalContentAlignment="Center" Style="{StaticResource BasePasswordBoxStyle}"></PasswordBox>
|
||||
</Border>
|
||||
<CheckBox Content="记住密码" x:Name="chkRemember" BorderBrush="{StaticResource mainColorBrush}" FontSize="12" HorizontalAlignment="Left" Grid.Row="3" VerticalAlignment="Center" Checked="chkRemember_Checked"/>
|
||||
<Button x:Name="btnLogin" Style="{StaticResource BaseButtonStyle}" Grid.Row="4" Content="安 全 登 录" Height="26" Click="btnLogin_Click"></Button>
|
||||
<CheckBox Content="调试模式" Height="24" Visibility="Collapsed" x:Name="chkDebug" BorderBrush="{StaticResource mainColorBrush}"
|
||||
FontSize="12" VerticalContentAlignment="Center" HorizontalAlignment="Left" Grid.Row="5" VerticalAlignment="Center" Checked="chkDebug_Checked" Unchecked="chkDebug_Checked"/>
|
||||
<Grid Width="140" HorizontalAlignment="Stretch">
|
||||
<TextBlock
|
||||
x:Name="txtName"
|
||||
Padding="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding}" />
|
||||
<userctrls:UcImageButton
|
||||
x:Name="btnDeleteItem"
|
||||
Width="17"
|
||||
Height="17"
|
||||
HorizontalAlignment="Right"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
ButtonImage="{StaticResource imgCloseGray}"
|
||||
Click="btnDeleteItem_Click"
|
||||
Cursor="Hand"
|
||||
MouseOverButtonImage="{StaticResource imgCloseGray}"
|
||||
MousePressButtonImage="{StaticResource imgCloseGray}"
|
||||
Tag="{Binding ElementName=txtName, Path=Text}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border
|
||||
Grid.Row="2"
|
||||
Height="26"
|
||||
BorderBrush="{StaticResource mainColorBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4">
|
||||
<PasswordBox
|
||||
x:Name="txtPassword"
|
||||
Margin="6,0"
|
||||
VerticalContentAlignment="Center"
|
||||
Style="{StaticResource BasePasswordBoxStyle}" />
|
||||
</Border>
|
||||
<CheckBox
|
||||
x:Name="chkRemember"
|
||||
Grid.Row="3"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
BorderBrush="{StaticResource mainColorBrush}"
|
||||
Checked="chkRemember_Checked"
|
||||
Content="记住密码"
|
||||
FontSize="12" />
|
||||
<Button
|
||||
x:Name="btnLogin"
|
||||
Grid.Row="4"
|
||||
Height="26"
|
||||
Click="btnLogin_Click"
|
||||
Content="安 全 登 录"
|
||||
Style="{StaticResource BaseButtonStyle}" />
|
||||
<CheckBox
|
||||
x:Name="chkDebug"
|
||||
Grid.Row="5"
|
||||
Height="24"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
BorderBrush="{StaticResource mainColorBrush}"
|
||||
Checked="chkDebug_Checked"
|
||||
Content="调试模式"
|
||||
FontSize="12"
|
||||
Unchecked="chkDebug_Checked"
|
||||
Visibility="Collapsed" />
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Image Source="{StaticResource main29WeixinIcon}" Height="24" Width="24" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="4"></Image>
|
||||
<Label x:Name="lblAppName" FontSize="28" Margin="0,14,0,0" Foreground="{StaticResource mainColorLightBrush}" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" Content="拍单软件"></Label>
|
||||
<Label x:Name="lblTitle" Margin="30,2" Foreground="White" Content="{Binding ElementName=loginWindow, Path=Title}"></Label>
|
||||
<userctrls:UcWindowBoxes VerticalAlignment="Top" HorizontalAlignment="Right" Margin="4" MaxBoxVisibility="Collapsed" MinBoxVisibility="Collapsed"/>
|
||||
|
||||
</Grid>
|
||||
<Image
|
||||
Width="24"
|
||||
Height="24"
|
||||
Margin="4"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Source="{StaticResource main29WeixinIcon}" />
|
||||
<Label
|
||||
x:Name="lblAppName"
|
||||
Margin="0,14,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Background="Transparent"
|
||||
Content="拍单软件"
|
||||
FontSize="28"
|
||||
Foreground="{StaticResource mainColorLightBrush}"
|
||||
Style="{x:Null}" />
|
||||
<Label
|
||||
x:Name="lblTitle"
|
||||
Margin="30,2"
|
||||
Content="{Binding ElementName=loginWindow, Path=Title}"
|
||||
Foreground="White" />
|
||||
<userctrls:UcWindowBoxes
|
||||
Margin="4"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
MaxBoxVisibility="Collapsed"
|
||||
MinBoxVisibility="Collapsed" />
|
||||
|
||||
</Grid>
|
||||
</basewindows:BaseWindow>
|
||||
@@ -0,0 +1,347 @@
|
||||
<basewindows:BaseWindow
|
||||
x:Class="_29WxOrderWpf.Windows.OrderWindows.BuyMapSkuSelectWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:basewindows="clr-namespace:_29WxOrderWpf.BaseWindows"
|
||||
xmlns:buyerctrls="clr-namespace:_29WxOrderWpf.Ctrls.BuyerCtrls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:_29WxOrderWpf.Windows.OrderWindows"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:skudata="clr-namespace:AllBase.Model.Result;assembly=29WxOrderAllBase"
|
||||
xmlns:userctrls="clr-namespace:_29WxOrderWpf.UserControls"
|
||||
Title="SkuSelectWindow"
|
||||
Width="1360"
|
||||
Height="768"
|
||||
d:Height="700"
|
||||
d:Width="1200"
|
||||
FontSize="15"
|
||||
Loaded="SkuSelectWindow_Loaded"
|
||||
ResizeMode="NoResize"
|
||||
Style="{StaticResource BaseNoneBorderWindowStyle}"
|
||||
mc:Ignorable="d">
|
||||
<Window.Resources>
|
||||
<!--<x:Array x:Key="frontSkuInfo" Type="skudata:frontSkuInfo3">
|
||||
<skudata:frontSkuInfo3 />
|
||||
</x:Array>-->
|
||||
</Window.Resources>
|
||||
<Grid
|
||||
x:Name="mainGrid"
|
||||
Margin="2"
|
||||
Background="Transparent"
|
||||
MouseLeftButtonDown="gridTitle_MouseLeftButtonDown">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="50" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid x:Name="gridTitle">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image
|
||||
Width="50"
|
||||
Height="50"
|
||||
Margin="4"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Source="{StaticResource main29WeixinIcon}" />
|
||||
<Label
|
||||
x:Name="lblAppName"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Content="选择SKU规格"
|
||||
FontSize="20"
|
||||
Foreground="{StaticResource mainColorLightBrush}" />
|
||||
</Grid>
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Padding="40,20"
|
||||
Background="White">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" MinHeight="50" />
|
||||
<RowDefinition Height="Auto" MinHeight="50" />
|
||||
<RowDefinition Height="*" MinHeight="50" />
|
||||
<RowDefinition Height="Auto" MinHeight="50" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto" MinWidth="100" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
FontSize="15"
|
||||
Text="订单商品:" />
|
||||
<Grid Grid.Column="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image
|
||||
x:Name="imgSku"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Width="100"
|
||||
Height="100"
|
||||
Margin="8"
|
||||
VerticalAlignment="Top"
|
||||
Cursor="Hand"
|
||||
MouseDown="imgSku_MouseDown"
|
||||
MouseEnter="imgSku_MouseEnter"
|
||||
MouseLeave="imgSku_MouseLeave" />
|
||||
<Grid
|
||||
Grid.Column="1"
|
||||
Margin="0,8"
|
||||
VerticalAlignment="Center">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<WrapPanel Visibility="{Binding ShopGoodsName, Converter={StaticResource HasTextVisibility}}">
|
||||
<TextBox
|
||||
VerticalAlignment="Top"
|
||||
d:Opacity="0.5"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
IsReadOnly="True"
|
||||
Text="店铺商品标题:"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBox
|
||||
VerticalAlignment="Top"
|
||||
d:Opacity="0.5"
|
||||
d:Text="店铺商品标题"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding ShopGoodsName}"
|
||||
TextWrapping="Wrap" />
|
||||
</WrapPanel>
|
||||
<WrapPanel Grid.Row="1" Visibility="{Binding ShopSkuName, Converter={StaticResource HasTextVisibility}}">
|
||||
<TextBox
|
||||
VerticalAlignment="Top"
|
||||
d:Opacity="0.5"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
IsReadOnly="True"
|
||||
Text="店铺商品规格:"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBox
|
||||
Grid.Row="0"
|
||||
VerticalAlignment="Top"
|
||||
d:Opacity="0.5"
|
||||
d:Text="店铺SKU规格"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
FontWeight="Bold"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding ShopSkuName}"
|
||||
TextWrapping="Wrap" />
|
||||
</WrapPanel>
|
||||
|
||||
<WrapPanel Grid.Row="2" Visibility="{Binding ShopSkuPrice, Converter={StaticResource HasTextVisibility}}">
|
||||
<TextBox
|
||||
VerticalAlignment="Top"
|
||||
d:Opacity="0.5"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
IsReadOnly="True"
|
||||
Text="店铺规格单价:"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBox
|
||||
Grid.Row="0"
|
||||
VerticalAlignment="Top"
|
||||
d:Opacity="0.5"
|
||||
d:Text="0.00"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding ShopSkuPrice}"
|
||||
TextWrapping="Wrap" />
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
FontSize="15"
|
||||
Text="关联商品:" />
|
||||
<Grid Grid.Row="1" Grid.Column="1">
|
||||
<ListBox
|
||||
x:Name="lbxOrderItem"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="7,0,7,0"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
BorderThickness="0"
|
||||
ItemsSource="{Binding BuyMapSkus}"
|
||||
ScrollViewer.CanContentScroll="True"
|
||||
VirtualizingPanel.ScrollUnit="Pixel">
|
||||
<!-- 使用自定义属性模板去除ListBox高亮显示框 -->
|
||||
<ListBox.ItemContainerStyle>
|
||||
<Style TargetType="ListBoxItem">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListBoxItem">
|
||||
<ContentPresenter
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentStringFormat="{TemplateBinding ContentStringFormat}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ListBox.ItemContainerStyle>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<!-- 此处绑定将数据源传递给子自定义控件OrderListRowCtrl -->
|
||||
<!--<buyerctrls:SkuSelectListRowCtrl
|
||||
VerticalAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
DataContext="{Binding}" />-->
|
||||
<RadioButton
|
||||
MaxWidth="250"
|
||||
Margin="0,0,12,0"
|
||||
Click="RadioButton_Click"
|
||||
IsChecked="{Binding IsChecked}">
|
||||
<TextBox
|
||||
VerticalAlignment="Top"
|
||||
d:Opacity="0.5"
|
||||
d:Text="商品标题"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding map_goods_name}"
|
||||
TextWrapping="Wrap" />
|
||||
</RadioButton>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<!-- 单行横向,超出部分隐藏或滚动 -->
|
||||
<StackPanel IsItemsHost="True" Orientation="Horizontal" />
|
||||
|
||||
<!-- 若需自动换行,替换为以下 WrapPanel -->
|
||||
<!-- <WrapPanel Orientation="Horizontal" IsItemsHost="True"/> -->
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
FontSize="15"
|
||||
Text="选择SKU:" />
|
||||
<ListBox
|
||||
x:Name="lbxSku"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="7,0,7,0"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
BorderThickness="0"
|
||||
ItemsSource="{Binding FrontSkuInfos}"
|
||||
ScrollViewer.CanContentScroll="True"
|
||||
VirtualizingPanel.ScrollUnit="Pixel">
|
||||
<!-- 使用自定义属性模板去除ListBox高亮显示框 -->
|
||||
<ListBox.ItemContainerStyle>
|
||||
<Style TargetType="ListBoxItem">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListBoxItem">
|
||||
<ContentPresenter
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentStringFormat="{TemplateBinding ContentStringFormat}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ListBox.ItemContainerStyle>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<!-- 此处绑定将数据源传递给子自定义控件OrderListRowCtrl -->
|
||||
<buyerctrls:SkuSelectListRowCtrl
|
||||
VerticalAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
DataContext="{Binding}" />
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<Button
|
||||
x:Name="btnAdd"
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
MinWidth="48"
|
||||
MinHeight="28"
|
||||
Padding="4"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
d:Visibility="Visible"
|
||||
Click="btnAdd_Click"
|
||||
Content="添 加"
|
||||
Style="{StaticResource BasePageButtonStyle}"
|
||||
Visibility="Collapsed" />
|
||||
<Image
|
||||
x:Name="imgLoading"
|
||||
Grid.Row="9"
|
||||
Grid.Column="1"
|
||||
Width="20"
|
||||
Height="20"
|
||||
Margin="52,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
d:Visibility="Visible"
|
||||
IsEnabled="False"
|
||||
Source="{StaticResource imgLoadingPic}"
|
||||
Visibility="Collapsed">
|
||||
<Image.RenderTransform>
|
||||
<RotateTransform Angle="45" CenterX="10" CenterY="10" />
|
||||
</Image.RenderTransform>
|
||||
<Image.Triggers>
|
||||
<EventTrigger RoutedEvent="Image.Loaded">
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation
|
||||
RepeatBehavior="Forever"
|
||||
Storyboard.TargetName="imgLoading"
|
||||
Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)"
|
||||
From="0"
|
||||
To="360"
|
||||
Duration="0:0:3" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
</Image.Triggers>
|
||||
</Image>
|
||||
<Label
|
||||
x:Name="lblMsg"
|
||||
Grid.Row="9"
|
||||
Grid.Column="1"
|
||||
Margin="70,0,0,0"
|
||||
Foreground="{StaticResource PageWarningBrushCC}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<userctrls:UcWindowBoxes
|
||||
x:Name="ucWindowBox"
|
||||
Margin="4"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
MaxBoxVisibility="Visible" />
|
||||
</Grid>
|
||||
</basewindows:BaseWindow>
|
||||
@@ -0,0 +1,117 @@
|
||||
using _29WxOrderWpf.BaseWindows;
|
||||
using _29WxOrderWpf.ViewModels;
|
||||
using AllBase;
|
||||
using AllBase.Helper;
|
||||
using AllBase.Model.Config;
|
||||
using AllBase.Model.DBModel;
|
||||
using AllBase.Model.Result;
|
||||
using NaiBa.Util.Extension;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NPOI.POIFS.NIO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
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.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using Work._29Tools.BLL;
|
||||
using Work._29Tools.Forms.ToolForms;
|
||||
|
||||
namespace _29WxOrderWpf.Windows.OrderWindows
|
||||
{
|
||||
/// <summary>
|
||||
/// SkuSelectWindow.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class BuyMapSkuSelectWindow : BaseWindow
|
||||
{
|
||||
public oms_wei_order_item_info OrderItem { get; set; }
|
||||
public BuyMapSkuViewModel ViewModel { get; set; } = null;
|
||||
|
||||
|
||||
public BuyMapSkuSelectWindow(oms_wei_order_item_info orderItem)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
if (orderItem != null)
|
||||
OrderItem = orderItem;
|
||||
|
||||
ViewModel = new BuyMapSkuViewModel(orderItem);
|
||||
this.DataContext = ViewModel;
|
||||
ViewModel.RequestClose += ViewModel_RequestClose;
|
||||
}
|
||||
|
||||
private void ViewModel_RequestClose()
|
||||
{
|
||||
this.Close();
|
||||
}
|
||||
|
||||
private async void SkuSelectWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
btnAdd.IsEnabled = true;
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
// 异步加载图片防止卡顿
|
||||
// 缩略图在有的电脑上无法显示
|
||||
//BitmapImage bImg = ImageUrlToBitmapImageConverter.LoadImageFromUrl(order_item.thumb_img + "?imageView2/1/w/100/format/webp|imageMogr2/auto-orient=");
|
||||
BitmapImage bImg = AllBase.ImageBuild.LoadBitmipImageFromUrl(OrderItem.thumb_img);
|
||||
Thread.Sleep(Randomx.GetId(200));
|
||||
Dispatcher.Invoke(new Action(() =>
|
||||
{
|
||||
imgSku.Source = bImg;
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private void btnAdd_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
private void gridTitle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
this.DragMove();
|
||||
}
|
||||
}
|
||||
|
||||
private void RadioButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (this.IsLoaded && sender is RadioButton rb)
|
||||
{
|
||||
if (rb.DataContext is WXBuyMapSkusInfo skuInfo)
|
||||
{
|
||||
this.ViewModel.SetCurrentFrontSkuInfo3(skuInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void imgSku_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
ShowImgForm imgFrom = new ShowImgForm(this.OrderItem?.thumb_img);
|
||||
imgFrom.TopMost = true;
|
||||
imgFrom.Show();
|
||||
}
|
||||
|
||||
private void imgSku_MouseEnter(object sender, MouseEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void imgSku_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -36,7 +36,7 @@ namespace AllBase
|
||||
/// 版本ID
|
||||
/// </summary>
|
||||
|
||||
public static int VersionCount = 269;
|
||||
public static int VersionCount = 276;
|
||||
|
||||
public static string ServerUrl { get; set; } = "http://1.13.195.131:9000";
|
||||
public static string DebugUrl { get; set; } = "http://localhost:9000";
|
||||
|
||||
@@ -14,6 +14,7 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using Color = System.Windows.Media.Color;
|
||||
|
||||
namespace AllBase.Helper
|
||||
{
|
||||
@@ -76,9 +77,13 @@ namespace AllBase.Helper
|
||||
//public static string dangerItemWords = "打火机,压力罐,刀具,铡刀,充电宝,罂粟,礼炮,煤气罐,仿制枪,暖手宝,灭火器,火炮,纸炮枪,手枪,毫安,喷漆,酒精,锂电,电池,火柴,气罐,气泡弹,喷雾";
|
||||
public static string dangerItemWords = "打火机,压力罐,刀具,铡刀,充电宝,罂粟,礼炮,煤气罐,仿制枪,暖手宝,灭火器,火炮,纸炮枪,手枪,毫安,喷漆,酒精,锂电,电池,火柴,气罐,气泡弹";
|
||||
public static List<string> lstDangerItemWord { get; set; } = dangerItemWords.Split(',').ToList();
|
||||
public static BuyConfig BuyConfig { get; set; }
|
||||
|
||||
#region 界面颜色
|
||||
public static System.Drawing.Color DefaultMainColor { get; set; } = System.Drawing.Color.FromArgb(63, 126, 255);
|
||||
public static ConcurrentList<WXBuyMapInfo> BuyMapList { get; set; } = new ConcurrentList<WXBuyMapInfo>();
|
||||
public static ConcurrentList<WXBuyMapSkusInfo> BuyMapSkusList { get; set; } = new ConcurrentList<WXBuyMapSkusInfo>();
|
||||
|
||||
#region 界面颜色
|
||||
public static System.Drawing.Color DefaultMainColor { get; set; } = System.Drawing.Color.FromArgb(63, 126, 255);
|
||||
public static System.Drawing.Color DefaultMainColorLight { get; set; } = System.Drawing.Color.FromArgb(185, 215, 255);
|
||||
public static System.Drawing.Color DefaultBorderColor { get; set; } = System.Drawing.Color.FromArgb(25, 98, 200);
|
||||
|
||||
@@ -95,8 +100,6 @@ namespace AllBase.Helper
|
||||
public static System.Drawing.Color WeiXinBorderColor { get; set; } = System.Drawing.Color.FromArgb(107, 83, 147);
|
||||
#endregion
|
||||
|
||||
public static BuyConfig BuyConfig { get; set; }
|
||||
|
||||
#region 文本颜色
|
||||
public static SolidColorBrush BlackBrush = new SolidColorBrush(Color.FromArgb(255, 0, 0, 0));
|
||||
|
||||
|
||||
@@ -62,6 +62,9 @@ namespace AllBase
|
||||
/// <returns>对象实体集合</returns>
|
||||
public static List<T> DeserializeJsonToList<T>(string json) where T : class
|
||||
{
|
||||
if (string.IsNullOrEmpty(json))
|
||||
return new List<T>();
|
||||
|
||||
JsonSerializer serializer = new JsonSerializer();
|
||||
StringReader sr = new StringReader(json);
|
||||
object o = serializer.Deserialize(new JsonTextReader(sr), typeof(List<T>));
|
||||
|
||||
@@ -45,8 +45,8 @@ XDYGZC ;广州仓 ;何外华 ;13826339849 ;广州市从化区太平镇
|
||||
XDYWHC ;武汉2仓 ;杨先生 ;13044121071 ;湖北省武汉市汉阳区洲头街道汉新大道2号3栋17楼 ;13 ;180 ;1548 ;湖北省 ;武汉市
|
||||
XDYSHANDC ;山东2仓 ;鸿鹄小申 ;13044121239 ;山东省临沂市费县南外环东转盘往东200米顺通快递有限公司(仓库二楼) ;22 ;292 ;2415 ;山东省 ;临沂市
|
||||
XDYSHC ;上海新仓 ;纪先生 ;13044121150 ;上海市上海市普陀区真如镇街道真北路2653弄T区 ;25 ;321 ;2709 ;上海市 ;上海市
|
||||
XDYSZC ;深圳仓 ;峰峰 ;15343351331 ;广东省东莞市凤岗镇浸校塘东良巷逸乐轩E栋5号楼 ;6 ;79 ;732 ;广东省 ;东莞市";
|
||||
//XDYYWC ;义乌仓 ;小田 ;19058117371 ;浙江省金华市义乌市北苑街道西站大道妈妈商贸18号库39号楼誉达 ;31 ;386 ;3409 ;浙江省 ;金华市
|
||||
XDYSZC ;深圳仓 ;峰峰 ;15343351331 ;广东省东莞市凤岗镇浸校塘东良巷逸乐轩E栋5号楼 ;6 ;79 ;732 ;广东省 ;东莞市
|
||||
XDYYWC ;义乌仓 ;小田 ;19058117371 ;浙江省金华市义乌市北苑街道西站大道妈妈商贸18号库39号楼誉达 ;31 ;386 ;3409 ;浙江省 ;金华市";
|
||||
//XDYJJC ;晋江仓 ;安群 ;19905951984 ;福建省泉州市南安市霞美镇温山村431号鸿鹄大厦 ;4 ;58 ;569 ;福建省 ;泉州市
|
||||
//XDYCDC ;成都仓 ;谢春虹 ;15828201479 ;四川省资阳市乐至县仙鹤大道桑都街15-19号仓库(不要放驿站,送到仓库) ;26 ;340 ;2898 ;四川省 ;资阳市
|
||||
//XDYCHONGQC ;重庆2仓 ;翁桂柱 ;18923911055 ;重庆市九龙坡区中梁山街道云飞九龙香山(二纵线东50米)重庆九龙坡区渝州公司 ;32 ;394 ;3339
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
using System;
|
||||
using AllBase.Model.MvvmBaseClasses;
|
||||
using AllBase.Model.Result;
|
||||
using NaiBa.Util;
|
||||
using Newtonsoft.Json;
|
||||
using NPOI.SS.Formula.Functions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -28,4 +33,58 @@ namespace AllBase.Model.DBModel
|
||||
public DateTime add_time { get; set; }
|
||||
public DateTime mdy_time { get; set; }
|
||||
}
|
||||
public class WXBuyMapSkusInfo : PropertyNotifyObject
|
||||
{
|
||||
public bool? IsChecked
|
||||
{
|
||||
get { return this.GetValue(x => x.IsChecked); }
|
||||
set { this.SetValue(x => x.IsChecked, value); }
|
||||
}
|
||||
|
||||
public long id { get; set; }
|
||||
public string user_name { get; set; }
|
||||
public string spu_code { get; set; }
|
||||
public string map_url
|
||||
{
|
||||
get { return this.GetValue(x => x.map_url); }
|
||||
set { this.SetValue(x => x.map_url, value); }
|
||||
}
|
||||
public string map_goods_name
|
||||
{
|
||||
get { return this.GetValue(x => x.map_goods_name); }
|
||||
set { this.SetValue(x => x.map_goods_name, value); }
|
||||
}
|
||||
public string map_goods_id
|
||||
{
|
||||
get { return this.GetValue(x => x.map_goods_id); }
|
||||
set { this.SetValue(x => x.map_goods_id, value); }
|
||||
}
|
||||
string _map_skus;
|
||||
public string map_skus
|
||||
{
|
||||
get { return _map_skus; }
|
||||
set
|
||||
{
|
||||
_map_skus = value;
|
||||
try
|
||||
{
|
||||
pdd_front_goods3_Lite = JsonHelper.DeserializeJsonToObject<pddFrontGoods3Lite>(value);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
[JsonIgnore]
|
||||
public pddFrontGoods3Lite pdd_front_goods3_Lite
|
||||
{
|
||||
get { return this.GetValue(x => x.pdd_front_goods3_Lite); }
|
||||
set { this.SetValue(x => x.pdd_front_goods3_Lite, value); }
|
||||
}
|
||||
/// <summary>
|
||||
/// 发货地(省,市)
|
||||
/// </summary>
|
||||
public string map_send_address { get; set; }
|
||||
public string transit_warehouse_id { get; set; }
|
||||
public DateTime add_time { get; set; }
|
||||
public DateTime mdy_time { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,12 @@ namespace AllBase.Model.DBModel
|
||||
{
|
||||
public string OutOrderNumStr
|
||||
{
|
||||
get { return "采购单号:" + out_order_no; }
|
||||
get { return out_order_no; }
|
||||
set { }
|
||||
}
|
||||
public string OutOrderBuyerStr
|
||||
{
|
||||
get { return "采购账户:" + buyer_account; }
|
||||
get { return buyer_account; }
|
||||
set { }
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using static System.Windows.Forms.VisualStyles.VisualStyleElement.StartPanel;
|
||||
using Color = System.Windows.Media.Color;
|
||||
|
||||
namespace AllBase.Model.DBModel
|
||||
{
|
||||
|
||||
@@ -38,6 +38,21 @@ namespace AllBase.Model.DBModel
|
||||
get { return this.GetValue(x => x.BuyMap); }
|
||||
set { this.SetValue(x => x.BuyMap, value); }
|
||||
}
|
||||
public Visibility MapSkusBtnVisibility
|
||||
{
|
||||
get { return this.GetValue(x => x.MapSkusBtnVisibility); }
|
||||
set { this.SetValue(x => x.MapSkusBtnVisibility, value); }
|
||||
}
|
||||
public List<WXBuyMapSkusInfo> BuyMapSkus
|
||||
{
|
||||
get { return this.GetValue(x => x.BuyMapSkus); }
|
||||
set
|
||||
{
|
||||
this.SetValue(x => x.BuyMapSkus, value);
|
||||
if (value != null && value.Count > 0) MapSkusBtnVisibility = Visibility.Visible;
|
||||
else MapSkusBtnVisibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 采购按钮文字
|
||||
/// </summary>
|
||||
@@ -247,19 +262,19 @@ namespace AllBase.Model.DBModel
|
||||
if (ItemTransitOrder?.pre_allocated_order_no != 1 && ItemTransitOrder?.pre_allocated_order_no != 3)
|
||||
this.TransitOrderWaybillToWareHouseStr = null;
|
||||
else
|
||||
this.TransitOrderWaybillToWareHouseStr = "买手物流:" + ItemTransitOrder.express_1_company + "-" + ItemTransitOrder.express_1_waybill;
|
||||
this.TransitOrderWaybillToWareHouseStr = ItemTransitOrder.express_1_company + "-" + ItemTransitOrder.express_1_waybill;
|
||||
|
||||
// 提前取号物流
|
||||
if (ItemTransitOrder?.pre_allocated_order_no != 2)
|
||||
this.TransitOrderWaybillFakeStr = null;
|
||||
else
|
||||
this.TransitOrderWaybillFakeStr = "取号物流:" + ItemTransitOrder.express_1_company + "-" + ItemTransitOrder.express_1_waybill;
|
||||
this.TransitOrderWaybillFakeStr = ItemTransitOrder.express_1_company + "-" + ItemTransitOrder.express_1_waybill;
|
||||
|
||||
// 仓库下游物流
|
||||
if (ItemTransitOrder == null || ItemTransitOrder.express_2_company.IsNullOrEmpty() || ItemTransitOrder.express_2_waybill.IsNullOrEmpty())
|
||||
TransitOrderWaybillFromWareHouseStr = null;
|
||||
else
|
||||
TransitOrderWaybillFromWareHouseStr = "仓库物流:" + ItemTransitOrder.express_2_company + "-" + ItemTransitOrder.express_2_waybill;
|
||||
TransitOrderWaybillFromWareHouseStr = ItemTransitOrder.express_2_company + "-" + ItemTransitOrder.express_2_waybill;
|
||||
|
||||
if (order_id == "3738461463336072960")
|
||||
{
|
||||
@@ -707,7 +722,7 @@ namespace AllBase.Model.DBModel
|
||||
if (ItemTransitOrderWithVoid == null)
|
||||
return "";
|
||||
|
||||
string timeStr = "采购时间:" + ItemTransitOrderWithVoid.created_time.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
string timeStr = ItemTransitOrderWithVoid.created_time.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
if (CreateDateTime.Date != ItemTransitOrderWithVoid.created_time.Date && ItemTransitOrderWithVoid.created_time > ItemTransitOrderWithVoid.created_time.Date.AddHours(1.5))
|
||||
return timeStr + " ×";
|
||||
return timeStr + " √";
|
||||
|
||||
@@ -83,6 +83,7 @@ namespace AllBase.Model.DBModel
|
||||
退货包裹扣费支出 = 26,
|
||||
支付宝在线充值 = 30,
|
||||
云仓退货支出 = 31,
|
||||
礼物单退费 = 34,
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
using AllBase.Model.MvvmBaseClasses;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AllBase.Model.Result
|
||||
{
|
||||
public class PDDFrontProductInfo3
|
||||
{
|
||||
public storeInfo store { get; set; }
|
||||
}
|
||||
|
||||
public class storeInfo
|
||||
{
|
||||
public initDataObjInfo initDataObj { get; set; }
|
||||
}
|
||||
|
||||
public class initDataObjInfo
|
||||
{
|
||||
public frontGoods3 goods { get; set; }
|
||||
public frontMall3 mall { get; set; }
|
||||
}
|
||||
|
||||
public class frontMall3
|
||||
{
|
||||
public string mallId { get; set; } // "525159017",
|
||||
public string mallName { get; set; } // "丰润粮行",
|
||||
public string mallNameBeforeTrans { get; set; } //"丰润粮行",
|
||||
public string commonMallSn { get; set; } // "CgI2WRIIS0xJNU5SeWUaEIyvRPN5kXSIHgLRzO8cKQQiEG3MCMAvMxkfdCTD6cGcpysoAjAC",
|
||||
public int? mallShowType { get; set; } // 0,
|
||||
public int? mallTransferType { get; set; } // 0,
|
||||
public string salesTip { get; set; } // "本店已拼2.9万件",
|
||||
public string salesTipV2 { get; set; } //"本店已拼2.9万件",
|
||||
public int? mallSales { get; set; } // 29000,
|
||||
public string mallLogo { get; set; } //"http://t16img.yangkeduo.com/pdd_ims/b8a22d4f0a836f1092a5ca4616ae504a.jpg",
|
||||
public int? goodsNum { get; set; } // 32,
|
||||
public string goodsNumDesc { get; set; } //"商品数量: 32",
|
||||
public string pddRoute { get; set; } // "mall_page.html?mall_sn=CgI2WRIIS0xJNU5SeWUaEMo5%2BOQRk5ds3pAKdvP7h%2B0iENx7YPKmJOuOHBOXqFh6WWYoAjAC&decrypt_mall_sn=1&msn=2msllrrezqz342rqxtnapi5pya_axbuy&_sop_rcto=YWInBFuzqa5GZuTI3XrNpg03TuXTx_3fIjo&mall_info=%7B%22mall_name%22%3A%22%E4%B8%B0%E6%B6%A6%E7%B2%AE%E8%A1%8C%22%7D",
|
||||
public string pddRouteName { get; set; } // "进店逛逛",
|
||||
public string mallServiceTag { get; set; } // "客服",
|
||||
public bool? isFlagShip { get; set; } // false,
|
||||
}
|
||||
|
||||
public class frontGoods3
|
||||
{
|
||||
public string sold_count = null;
|
||||
public string sold_count_str = null;
|
||||
|
||||
public long? serverTime { get; set; } // 1782836359,
|
||||
public long? serverTimeTen { get; set; } // 17828363609,
|
||||
public long? weakenGoodsName { get; set; } // 0,
|
||||
public long? catID { get; set; } // 7575,
|
||||
public long? goodsID { get; set; } // 1661424397,
|
||||
public string brandId { get; set; } // "",
|
||||
public string goodsName { get; set; } // "红薏仁5斤装 红薏仁米小薏米仁农家自产新红薏米1斤2斤苡仁糙薏米",
|
||||
public string shareDesc { get; set; } // "红薏仁5斤装 红薏仁米小薏米仁农家自产新红薏米1斤2斤苡仁糙薏米",
|
||||
public string goodsType { get; set; } // 1,
|
||||
public List<topGalleryInfo> topGallery { get; set; }
|
||||
public List<string> viewImageData { get; set; } // [ "https://t00img.yangkeduo.com/goods/images/2019-12-14/3d66ace6-62cd-4d82-bfff-65c84b268361.jpg", ... ],
|
||||
public List<detailGalleryInfo> detailGallery { get; set; }
|
||||
|
||||
public List<detailGalleryInfo> videoGallery { get; set; }
|
||||
public long? mallID { get; set; } // 525159017
|
||||
|
||||
public List<frontSkuInfo3> skus { get; set; }
|
||||
|
||||
public string thumbUrl { get; set; } // "https://img.pddpic.com/gaudit-image/2022-01-25/b5b0aa45a8ce7a59ef8eb0798fb537a4.jpeg",
|
||||
public string hdThumbUrl { get; set; } // "https://img.pddpic.com/gaudit-image/2022-01-25/76e52ff7ff407a6d5c1753e40294bcb7.jpeg",
|
||||
public int? eventType { get; set; } // 0,
|
||||
public int? isApp { get; set; } // 0,
|
||||
public int? isFreshmanApp { get; set; } // 0,
|
||||
public string sideSalesTip { get; set; } // "热销4652斤",
|
||||
public int? catID1 { get; set; } // 6630,
|
||||
public int? catID2 { get; set; } // 6654,
|
||||
public int? catID3 { get; set; } // 6655,
|
||||
public int? catID4 { get; set; } // 7575,
|
||||
public int? spuID { get; set; } // 0,
|
||||
public bool? eventComing { get; set; } // false,
|
||||
public bool? isSpike { get; set; } // false,
|
||||
public bool? isTodaySpike { get; set; } // false,
|
||||
public bool? isTomorrowSpike { get; set; } // false,
|
||||
public bool? isSpikeComing { get; set; } // false,
|
||||
public int? overseaType { get; set; } // 0,
|
||||
public object gpv { get; set; } // null,
|
||||
public bool? quickRefund { get; set; } // false,
|
||||
public bool? rv { get; set; } // true,
|
||||
public string maxNormalPrice { get; set; } // "42.5",
|
||||
public string minNormalPrice { get; set; } // "14.5",
|
||||
public string maxGroupPrice { get; set; } // "38.8",
|
||||
public string minGroupPrice { get; set; } // "13.5",
|
||||
public string maxOnSaleGroupPrice { get; set; } // "38.8",
|
||||
public string minOnSaleGroupPrice { get; set; } // "13.5",
|
||||
public int? maxOnSaleGroupPriceInCent { get; set; } // 3880,
|
||||
public int? minOnSaleGroupPriceInCent { get; set; } // 1350,
|
||||
public string maxOnSaleNormalPrice { get; set; } // "42.5",
|
||||
public string minOnSaleNormalPrice { get; set; } // "14.5",
|
||||
public string unselectNormalSavePrice { get; set; } // "1",
|
||||
public int? oldMinOnSaleGroupPriceInCent { get; set; } // 1350,
|
||||
public List<string> skipGoodsIDs { get; set; } // ["0"]
|
||||
public object tag { get; set; } // null,
|
||||
public object icon { get; set; } // null,
|
||||
public List<object> tagIcon { get; set; } // [ ],
|
||||
public int? isSecondHand { get; set; } // 0,
|
||||
public int? appClientOnly { get; set; } // 0,
|
||||
public int? isColdGoods { get; set; } // 1,
|
||||
public List<frontGoodsPropertyInfo3> goodsProperty { get; set; }
|
||||
public List<object> skuProperty { get; set; }
|
||||
|
||||
|
||||
public int? propertyShowNum { get; set; } // 5,
|
||||
public string linePrice { get; set; } // "68",
|
||||
public int? priceStyle { get; set; } // 2,
|
||||
public string priceSuffix { get; set; } // "起",
|
||||
public string shareTitle { get; set; } // "13.5元 红薏仁5斤装 红薏仁米小薏米仁农家自产新红薏米1斤2斤苡仁糙薏米 拼多多",
|
||||
public newShareTitleInfo newShareTitl { get; set; }
|
||||
public object bottomBanner { get; set; } // null,
|
||||
public bool? isAppFlow { get; set; } // true,
|
||||
public int? isPersonalFav { get; set; } // 0,
|
||||
public bool? groupNumFull { get; set; } // false,
|
||||
public int? showHistoryGroup { get; set; } // 1,
|
||||
public string shareLink { get; set; } // "goods1.html?goods_id=1661424397&page_from=23&pxq_secret_key=QWQMWFJMVA74GIIMHFUXALB5ZCBNJCYV5ANINMD7UOMJYEXK4KNA&_oak_share_snapshot_num=1350&_oak_share_time=1782836359&share_oak_rcto=YWL2oNqXCEvRIUlyReRrcF7njWrmpZN8wWj_KHekAqSbEFuQ1MTDsd0RrYEQyfFHBnA",
|
||||
public string promptExplain { get; set; } // "",
|
||||
public bool? isAbnormalStatus { get; set; } // false,
|
||||
public string statusExplain { get; set; } // "",
|
||||
public int? checkQuantity { get; set; } // 1,
|
||||
public string destinationUrl { get; set; } // "order_checkout.html",
|
||||
public bool? hideMall { get; set; } // false,
|
||||
public bool? isMallRec { get; set; } // false,
|
||||
public int? status { get; set; } // 1,
|
||||
public bool? isGoodsOnSale { get; set; } // true,
|
||||
public bool? isSkuOnSale { get; set; } // true,
|
||||
public bool? isOnSale { get; set; } // true,
|
||||
public int? quantity { get; set; } // 1000,
|
||||
public int? propertyCardClick { get; set; } // 0,
|
||||
public int? showRecTitle { get; set; } // 1,
|
||||
public int? showRec { get; set; } // 1,
|
||||
|
||||
}
|
||||
|
||||
public class pddFrontGoods3Lite : PropertyNotifyObject
|
||||
{
|
||||
public long? goodsId { get; set; } // 1661424397,
|
||||
public string goodsName { get; set; } // "红薏仁5斤装 红薏仁米小薏米仁农家自产新红薏米1斤2斤苡仁糙薏米",
|
||||
public long? mallId { get; set; } // 525159017
|
||||
public string mallName { get; set; }
|
||||
public List<frontSkuInfo3> skus
|
||||
{
|
||||
get { return this.GetValue(x => x.skus); }
|
||||
set { this.SetValue(x => x.skus, value); }
|
||||
}
|
||||
}
|
||||
|
||||
public class newShareTitleInfo
|
||||
{
|
||||
public string price { get; set; } // "13.5元 ",
|
||||
public string suf { get; set; } // "红薏仁5斤装 红薏仁米小薏米仁农家自产新红薏米1斤2斤苡仁糙薏米 拼多多",
|
||||
public string not_null { get; set; } // true
|
||||
|
||||
}
|
||||
|
||||
public class frontGoodsPropertyInfo3
|
||||
{
|
||||
public string key { get; set; } // "包装方式",
|
||||
public List<string> values { get; set; } // [ "食用农产品" ],
|
||||
public long? ref_pid { get; set; } // 677,
|
||||
public long? reference_id { get; set; } // 0
|
||||
|
||||
}
|
||||
|
||||
public class frontSkuInfo3 : PropertyNotifyObject
|
||||
{
|
||||
public long? skuId { get; set; } // 28790184768,
|
||||
public long? goodsId { get; set; } // 1661424397,
|
||||
public string GoodsNameStr
|
||||
{
|
||||
get { return this.GetValue(x => x.GoodsNameStr); }
|
||||
set { this.SetValue(x => x.GoodsNameStr, value); }
|
||||
}
|
||||
public string thumbUrl
|
||||
{
|
||||
get { return this.GetValue(x => x.thumbUrl); }
|
||||
set { this.SetValue(x => x.thumbUrl, value); }
|
||||
} // "https://t00img.yangkeduo.com/t10img/images/2018-06-09/5c7746a3608e75a2b65926f4ed0f34cb.jpeg",
|
||||
public int? initQuantity { get; set; } // 0,
|
||||
public int? quantity { get; set; } // 1000,
|
||||
public int? limitQuantity { get; set; } // 999999,
|
||||
public int? soldQuantity { get; set; } // 0,
|
||||
public int? hotSale { get; set; } // 1,
|
||||
public int? defaultQuantity { get; set; } // 100,
|
||||
public int? isOnsale { get; set; } // 1,
|
||||
public string spec { get; set; } // "677094835",
|
||||
public string SpecNameStr
|
||||
{
|
||||
get { return this.GetValue(x => x.SpecNameStr) == null ? string.Join(",", specs?.Select(s => s.spec_value)) : this.GetValue(x => x.SpecNameStr); }
|
||||
set { this.SetValue(x => x.SpecNameStr, value); }
|
||||
}
|
||||
List<frontSpecInfo3> _specs = new List<frontSpecInfo3>();
|
||||
public List<frontSpecInfo3> specs
|
||||
{
|
||||
get => _specs;
|
||||
set
|
||||
{
|
||||
_specs = value;
|
||||
SpecNameStr = string.Join(",", value.Select(s => s.spec_value));
|
||||
}
|
||||
}
|
||||
public int? price { get; set; } // 0,
|
||||
public string normalPrice { get; set; } // "14.5",
|
||||
public string groupPrice { get; set; } // "13.5",
|
||||
public int? oldGroupPrice { get; set; } // 1350,
|
||||
public int? marketPrice { get; set; } // 0,
|
||||
public int? weight { get; set; } // 2500,
|
||||
public List<yellowLabelListInfo> yellowLabelList { get; set; }
|
||||
public int? previewPriority { get; set; } // 0,
|
||||
public int? startTime { get; set; } // 0,
|
||||
public int? endTime { get; set; } // 0,
|
||||
public int? staticLimitQuantity { get; set; } // 999999,
|
||||
public string attribute { get; set; } // "{\"skuPreSaleTime\":0}",
|
||||
public string normalSavePrice { get; set; } // "1",
|
||||
public long? skuID { get; set; } // 28790184768,
|
||||
public string skuExpansionPrice { get; set; } // "0"
|
||||
}
|
||||
|
||||
public class yellowLabelListInfo
|
||||
{
|
||||
public string labelText { get; set; } // "2件9.9折",
|
||||
public string labelType { get; set; } // "single_goods_percentage_event"
|
||||
}
|
||||
|
||||
public class frontSpecInfo3
|
||||
{
|
||||
public string spec_key { get; set; } // "重量",
|
||||
public string spec_value { get; set; } // "红薏仁米1斤装",
|
||||
public long? spec_key_id { get; set; } // 1253,
|
||||
public long? spec_value_id { get; set; } // 677094835,
|
||||
public bool? custom { get; set; } // false
|
||||
}
|
||||
|
||||
|
||||
public class detailGalleryInfo
|
||||
{
|
||||
public string url { get; set; } // "https://img.pddpic.com/goods/images/2019-12-14/3d66ace6-62cd-4d82-bfff-65c84b268361.jpg",
|
||||
public int? width { get; set; } // 800,
|
||||
public int? height { get; set; } // 800
|
||||
|
||||
}
|
||||
public class videoGalleryInfo
|
||||
{
|
||||
public string url { get; set; } // "https://img.pddpic.com/goods/images/2019-12-14/3d66ace6-62cd-4d82-bfff-65c84b268361.jpg",
|
||||
public int? width { get; set; } // 800,
|
||||
public int? height { get; set; } // 800
|
||||
|
||||
}
|
||||
public class topGalleryInfo
|
||||
{
|
||||
public string url { get; set; } // "https://img.pddpic.com/goods/images/2019-12-14/3d66ace6-62cd-4d82-bfff-65c84b268361.jpg",
|
||||
public long? id { get; set; } // 20969775049,
|
||||
public int? aspectRatio { get; set; } // 1
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<Project>
|
||||
|
||||
<!-- See https://aka.ms/dotnet/msbuild/customize for more details on customizing your build -->
|
||||
<PropertyGroup>
|
||||
<DefaultTargetFramework>net8.0</DefaultTargetFramework>
|
||||
<TargetFramework>$(DefaultTargetFramework)</TargetFramework>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage>
|
||||
<Nullable>annotations</Nullable>
|
||||
<NoWarn>CS1591</NoWarn>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="PolySharp" Version="1.16.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,19 @@
|
||||
<tf:FrameworkApplication
|
||||
x:Class="Main.Test.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:Main.Test"
|
||||
xmlns:tf="Trade.Framework">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Basic.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml" />
|
||||
<ResourceDictionary Source="StyledDataGrid.xaml" />
|
||||
<ResourceDictionary Source="Dictionary2.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</tf:FrameworkApplication>
|
||||
@@ -0,0 +1,57 @@
|
||||
using Main.Test.ViewModels;
|
||||
using Main.Test.Views;
|
||||
using MAX.Core.DependencyInjection;
|
||||
using MAX.Core.Logger;
|
||||
using Prism.Ioc;
|
||||
using Prism.Modularity;
|
||||
using Prism.Mvvm;
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Windows;
|
||||
using Trade.Framework;
|
||||
using Trade.Framework.Extensions;
|
||||
using Trade.Framework.Services;
|
||||
|
||||
namespace Main.Test;
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : FrameworkApplication
|
||||
{
|
||||
public IScopedProvider CurrentScope => throw new NotImplementedException();
|
||||
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
base.OnStartup(e);
|
||||
|
||||
}
|
||||
protected override Window CreateShell()
|
||||
{
|
||||
return Container.Resolve<MainWindow>();
|
||||
}
|
||||
|
||||
protected override void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
// 严格模式:FrameworkApplication 已关闭 DryIoc 按需动态注册(WithConcreteTypeDynamicRegistrations),
|
||||
// 所有被解析的类型必须显式注册,否则抛 ContainerException。
|
||||
|
||||
// 启动 Shell
|
||||
containerRegistry.Register<MainWindow>();
|
||||
|
||||
// 窗口/控件 ViewModel(DISource 从容器解析)
|
||||
containerRegistry.Register<MainViewModel>();
|
||||
containerRegistry.Register<HeaderViewModel>();
|
||||
|
||||
// 对话框:RegisterDialog 只注册 View 本身,VM 由 ViewModelLocator 从容器解析,需单独注册
|
||||
containerRegistry.RegisterDialog<bbbDialogView, bbbDialogViewModel>(nameof(bbbDialogView));
|
||||
containerRegistry.Register<bbbDialogViewModel>();
|
||||
|
||||
// 服务:原先靠 DryIoc 动态注册(Transient),显式注册保持同样行为
|
||||
//containerRegistry.Register<UserManager>();
|
||||
// 若希望 UserManager 全局只实例化一次:
|
||||
// containerRegistry.RegisterSingleton<UserManager>();
|
||||
|
||||
//containerRegistry.RegisterSingleton<ILoggerFacade, ConsoleLogger>();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
@@ -0,0 +1,173 @@
|
||||
using HandyControl.Controls;
|
||||
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.Controls.Primitives;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Main.Test;
|
||||
|
||||
public class DataGridHelper
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetNoDataTips(DependencyObject obj)
|
||||
{
|
||||
return (string)obj.GetValue(NoDataTipsProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <param name="value"></param>
|
||||
public static void SetNoDataTips(DependencyObject obj, string value)
|
||||
{
|
||||
obj.SetValue(NoDataTipsProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty NoDataTipsProperty =
|
||||
DependencyProperty.RegisterAttached("NoDataTips", typeof(string), typeof(DataGridHelper),new PropertyMetadata("无数据"));
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static bool GetIsActive(DependencyObject obj)
|
||||
{
|
||||
return (bool)obj.GetValue(IsActiveProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <param name="value"></param>
|
||||
public static void SetIsActive(DependencyObject obj, bool value)
|
||||
{
|
||||
obj.SetValue(IsActiveProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsActiveProperty =
|
||||
DependencyProperty.RegisterAttached("IsActive", typeof(bool), typeof(DataGridHelper));
|
||||
|
||||
|
||||
|
||||
public static DependencyProperty ShowRowNumberProperty = DependencyProperty.RegisterAttached("ShowRowNumber",
|
||||
typeof(bool), typeof(DataGridHelper),
|
||||
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits, OnShowRowNumberChanged));
|
||||
|
||||
public static bool GetShowRowNumber(DependencyObject target) => (bool)target.GetValue(ShowRowNumberProperty);
|
||||
|
||||
public static void SetShowRowNumber(DependencyObject target, bool value)
|
||||
{
|
||||
target.SetValue(ShowRowNumberProperty, value);
|
||||
}
|
||||
|
||||
private static void OnShowRowNumberChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (target is not DataGrid dataGrid) return;
|
||||
var show = (bool)e.NewValue;
|
||||
|
||||
if (show)
|
||||
{
|
||||
dataGrid.LoadingRow += DataGrid_LoadingRow;
|
||||
dataGrid.ItemContainerGenerator.ItemsChanged += ItemContainerGeneratorItemsChanged;
|
||||
}
|
||||
else
|
||||
{
|
||||
dataGrid.LoadingRow -= DataGrid_LoadingRow;
|
||||
dataGrid.ItemContainerGenerator.ItemsChanged -= ItemContainerGeneratorItemsChanged;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ItemContainerGeneratorItemsChanged(object sender, ItemsChangedEventArgs e)
|
||||
{
|
||||
var generator = dataGrid.ItemContainerGenerator;
|
||||
var itemsCount = dataGrid.Items.Count;
|
||||
|
||||
if (show)
|
||||
{
|
||||
for (var i = 0; i < itemsCount; i++)
|
||||
{
|
||||
var row = (DataGridRow)generator.ContainerFromIndex(i);
|
||||
if (row != null)
|
||||
{
|
||||
row.Header = (i + 1).ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < itemsCount; i++)
|
||||
{
|
||||
var row = (DataGridRow)generator.ContainerFromIndex(i);
|
||||
if (row != null)
|
||||
{
|
||||
row.Header = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DataGrid_LoadingRow(object sender, DataGridRowEventArgs e)
|
||||
{
|
||||
e.Row.Header = (e.Row.GetIndex() + 1).ToString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static readonly DependencyProperty ShowBorderProperty =
|
||||
DependencyProperty.RegisterAttached("ShowBorder", typeof(bool),
|
||||
typeof(DataGridHelper), new PropertyMetadata(false, OnShowBorderChanged));
|
||||
public static void SetShowBorder(DependencyObject element, bool value)
|
||||
=> element.SetValue(ShowBorderProperty, value);
|
||||
|
||||
public static bool GetShowBorder(DependencyObject element)
|
||||
=> (bool)element.GetValue(ShowBorderProperty);
|
||||
|
||||
private static void OnShowBorderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is DataGridColumnHeader header && (bool)e.NewValue)
|
||||
{
|
||||
header.Loaded += (s, args) =>
|
||||
{
|
||||
var layer = AdornerLayer.GetAdornerLayer(header);
|
||||
if (layer != null)
|
||||
{
|
||||
layer.Add(new BorderAdorner(header));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public class BorderAdorner : Adorner
|
||||
{
|
||||
public BorderAdorner(UIElement adornedElement) : base(adornedElement) { }
|
||||
|
||||
protected override void OnRender(DrawingContext drawingContext)
|
||||
{
|
||||
var header = AdornedElement as DataGridColumnHeader;
|
||||
if (header == null) return;
|
||||
|
||||
var pen = new Pen(header.BorderBrush ?? new SolidColorBrush(Colors.LightGray), 1);
|
||||
|
||||
// 绘制右侧竖线(永远不消失)
|
||||
drawingContext.DrawLine(pen,
|
||||
new Point(header.ActualWidth - 0.5, 0),
|
||||
new Point(header.ActualWidth - 0.5, header.ActualHeight));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Prism.Mvvm;
|
||||
|
||||
namespace Main.Test;
|
||||
|
||||
public class ItemInfo : BindableBase
|
||||
{
|
||||
public int Code { get; init; }
|
||||
public string Name { get; init; } = "";
|
||||
public string Value => Price.ToString("N2", CultureInfo.CurrentCulture);
|
||||
public decimal Price { get; init; }
|
||||
public int Qty { get; init; }
|
||||
public string Sku { get; init; } = "";
|
||||
public string Shop { get; init; } = "";
|
||||
public string Buyer { get; init; } = "";
|
||||
public string Address { get; init; } = "";
|
||||
public string Remark { get; init; } = "";
|
||||
public DateTime CreatedAt { get; init; }
|
||||
public ImageSource Thumbnail { get; init; } = null!;
|
||||
public string? ThumbnailUrl { get; init; }
|
||||
private string status = "待处理";
|
||||
public string Status { get => status; set => SetProperty(ref status, value); }
|
||||
}
|
||||
|
||||
internal static class DemoData
|
||||
{
|
||||
// 冻结并共享小尺寸位图,滚动不触发网络请求或重复解码。
|
||||
private static readonly ImageSource[] Thumbnails = CreateThumbnails();
|
||||
|
||||
public static List<ItemInfo> Create(int count, int networkImageSize = 0)
|
||||
{
|
||||
string[] names = ["轻量通勤双肩包", "纯棉宽松短袖", "旅行收纳组合", "便携保温水杯", "无线办公鼠标", "桌面收纳置物盒", "运动休闲鞋", "折叠防晒遮阳伞"];
|
||||
string[] shops = ["城市生活旗舰店", "优选数码专营店", "日常好物集合店", "远行户外用品店"];
|
||||
var random = new Random(20261022);
|
||||
var session = Guid.NewGuid().ToString("N");
|
||||
var result = new List<ItemInfo>(count);
|
||||
for (var i = 0; i < count; i++)
|
||||
result.Add(new ItemInfo
|
||||
{
|
||||
Code = 100000 + i, Name = names[i % names.Length] + " · 秋季新品 / 品质款",
|
||||
Price = random.Next(1900, 69900) / 100m, Qty = random.Next(1, 100),
|
||||
Sku = $"SKU-{i:000000} / {(i % 2 == 0 ? "雾蓝" : "米白")} / 标准款",
|
||||
Shop = shops[i % shops.Length], Buyer = $"买家{i:00000}",
|
||||
Address = $"浙江省杭州市西湖区 文一路 {i % 300 + 1} 号 · 联系尾号 {i % 10000:0000}",
|
||||
Remark = i % 3 == 0 ? "请核对规格,周末可收货;外包装请加固。" : "常规发货 / 已核对收货信息",
|
||||
CreatedAt = new DateTime(2026, 10, 22, 12, 0, 0).AddMinutes(-i),
|
||||
Status = new[] { "待处理", "已确认", "待发货", "已完成" }[i % 4],
|
||||
Thumbnail = Thumbnails[i % Thumbnails.Length],
|
||||
ThumbnailUrl = networkImageSize == 0 ? null : $"https://picsum.photos/seed/{session}-{i}/{networkImageSize}/{networkImageSize}"
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ImageSource[] CreateThumbnails()
|
||||
{
|
||||
var images = new ImageSource[16];
|
||||
for (var i = 0; i < images.Length; i++)
|
||||
{
|
||||
var visual = new DrawingVisual();
|
||||
using (var dc = visual.RenderOpen())
|
||||
{
|
||||
var color = Color.FromRgb((byte)(55 + i * 11), (byte)(100 + i * 6), (byte)(165 - i * 4));
|
||||
dc.DrawRoundedRectangle(new SolidColorBrush(Color.FromRgb(241, 245, 249)), null, new Rect(0, 0, 72, 72), 8, 8);
|
||||
var brush = new SolidColorBrush(color);
|
||||
var pen = new Pen(brush, 4);
|
||||
dc.DrawRoundedRectangle(null, pen, new Rect(25, 12, 22, 24), 10, 10);
|
||||
dc.DrawRoundedRectangle(brush, null, new Rect(14, 26, 44, 36), 6, 6);
|
||||
dc.DrawLine(new Pen(Brushes.White, 2), new Point(24, 44), new Point(48, 44));
|
||||
}
|
||||
var bitmap = new RenderTargetBitmap(72, 72, 96, 96, PixelFormats.Pbgra32);
|
||||
bitmap.Render(visual); bitmap.Freeze(); images[i] = bitmap;
|
||||
}
|
||||
return images;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,428 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:hc="https://handyorg.github.io/handycontrol">
|
||||
|
||||
<!-- DataGrid -->
|
||||
<SolidColorBrush x:Key="ColumnHeaderBackgroundBrush" Color="#FAFAFA" />
|
||||
<SolidColorBrush x:Key="ColumnHeaderBorderBrush" Color="#FAFAFA" />
|
||||
|
||||
<Style
|
||||
x:Key="DataGridColumnHeader.MarginSmall"
|
||||
BasedOn="{StaticResource DataGridColumnHeaderStyle}"
|
||||
TargetType="{x:Type DataGridColumnHeader}">
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource PrimaryTextBrush}" />
|
||||
<Setter Property="Background" Value="{StaticResource BackgroundBrush}" />
|
||||
<Setter Property="Padding" Value="6,0" />
|
||||
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||
<Setter Property="BorderThickness" Value="0,0,1,1" />
|
||||
<Setter Property="BorderBrush" Value="{Binding VerticalGridLinesBrush, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="DataGridColumnHeader">
|
||||
<hc:SimplePanel SnapsToDevicePixels="True">
|
||||
<Border
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Background="{StaticResource ColumnHeaderBackgroundBrush}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
SnapsToDevicePixels="True"
|
||||
UseLayoutRounding="True">
|
||||
<Grid HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ContentPresenter
|
||||
VerticalAlignment="Center"
|
||||
RecognizesAccessKey="True"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
<ToggleButton
|
||||
x:Name="ToggleButtonSortDirection"
|
||||
Grid.Column="1"
|
||||
Width="20"
|
||||
Padding="4,0"
|
||||
VerticalAlignment="Center"
|
||||
hc:IconSwitchElement.Geometry="{StaticResource DownGeometry}"
|
||||
hc:IconSwitchElement.GeometrySelected="{StaticResource UpGeometry}"
|
||||
Foreground="{DynamicResource TextIconBrush}"
|
||||
IsEnabled="False"
|
||||
Opacity="1"
|
||||
Style="{StaticResource ToggleButtonIconTransparent}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<Thumb
|
||||
x:Name="PART_LeftHeaderGripper"
|
||||
HorizontalAlignment="Left"
|
||||
Style="{StaticResource ColumnHeaderGripperStyle}" />
|
||||
<Thumb
|
||||
x:Name="PART_RightHeaderGripper"
|
||||
HorizontalAlignment="Right"
|
||||
Style="{StaticResource ColumnHeaderGripperStyle}" />
|
||||
</hc:SimplePanel>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="SortDirection" Value="{x:Null}">
|
||||
<Setter TargetName="ToggleButtonSortDirection" Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
<Trigger Property="SortDirection" Value="Ascending">
|
||||
<Setter TargetName="ToggleButtonSortDirection" Property="IsChecked" Value="True" />
|
||||
</Trigger>
|
||||
<Trigger Property="SortDirection" Value="Descending">
|
||||
<Setter TargetName="ToggleButtonSortDirection" Property="IsChecked" Value="False" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!--
|
||||
行样式:不再手工画顶部边框,横线统一交给 DataGrid.HorizontalGridLinesBrush 处理,
|
||||
避免行/单元格分层画线导致的交界处错位、断线问题。
|
||||
-->
|
||||
<Style
|
||||
x:Key="DataGridRowStyle.MarginSmall"
|
||||
BasedOn="{StaticResource DataGridRowStyle}"
|
||||
TargetType="{x:Type DataGridRow}">
|
||||
<Setter Property="Margin" Value="0,0,0,0" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="DataGridRowStyle.Rightangle"
|
||||
BasedOn="{StaticResource DataGridRowStyle.MarginSmall}"
|
||||
TargetType="{x:Type DataGridRow}">
|
||||
<Setter Property="Margin" Value="0,0,0,0" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type DataGridRow}">
|
||||
<SelectiveScrollingGrid>
|
||||
<SelectiveScrollingGrid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</SelectiveScrollingGrid.ColumnDefinitions>
|
||||
<SelectiveScrollingGrid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</SelectiveScrollingGrid.RowDefinitions>
|
||||
<Border
|
||||
Name="DGR_Border"
|
||||
Background="{TemplateBinding Background}"
|
||||
CornerRadius="0"
|
||||
SelectiveScrollingGrid.ColumnSpan="2"
|
||||
SnapsToDevicePixels="True" />
|
||||
<DataGridCellsPresenter
|
||||
MinHeight="{TemplateBinding MinHeight}"
|
||||
VerticalContentAlignment="Center"
|
||||
ItemsPanel="{TemplateBinding ItemsPanel}"
|
||||
SelectiveScrollingGrid.Column="1"
|
||||
SelectiveScrollingGrid.Row="0"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
<DataGridDetailsPresenter
|
||||
Margin="0,6"
|
||||
SelectiveScrollingGrid.Column="1"
|
||||
SelectiveScrollingGrid.Row="1"
|
||||
SelectiveScrollingGrid.SelectiveScrollingOrientation="{Binding AreRowDetailsFrozen, ConverterParameter={x:Static SelectiveScrollingOrientation.Vertical}, Converter={x:Static DataGrid.RowDetailsScrollingConverter}, RelativeSource={RelativeSource AncestorType=DataGrid}}"
|
||||
Visibility="{TemplateBinding DetailsVisibility}" />
|
||||
<DataGridRowHeader
|
||||
SelectiveScrollingGrid.Column="0"
|
||||
SelectiveScrollingGrid.Row="0"
|
||||
SelectiveScrollingGrid.SelectiveScrollingOrientation="Vertical"
|
||||
Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.Row}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType=DataGrid}}" />
|
||||
</SelectiveScrollingGrid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!--
|
||||
单元格样式:去掉自绘右边框,竖线统一交给 DataGrid.VerticalGridLinesBrush 处理。
|
||||
-->
|
||||
<Style
|
||||
x:Key="DataGridCellStyle.Rightangle"
|
||||
BasedOn="{StaticResource DataGridCellStyle}"
|
||||
TargetType="{x:Type DataGridCell}">
|
||||
<Setter Property="hc:BorderElement.CornerRadius" Value="0" />
|
||||
<Setter Property="Margin" Value="0,0,0,0" />
|
||||
</Style>
|
||||
<!-- {x:Type DataGrid} -->
|
||||
|
||||
<Style
|
||||
x:Key="DataGrid.Custom"
|
||||
BasedOn="{StaticResource DataGridBaseStyle}"
|
||||
TargetType="{x:Type DataGrid}">
|
||||
<Setter Property="IsReadOnly" Value="True" />
|
||||
<Setter Property="RowStyle" Value="{StaticResource DataGridRowStyle.Rightangle}" />
|
||||
<Setter Property="CellStyle" Value="{StaticResource DataGridCellStyle.Rightangle}" />
|
||||
<Setter Property="ColumnHeaderStyle" Value="{StaticResource DataGridColumnHeader.MarginSmall}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Background" Value="{StaticResource BackgroundBrush}" />
|
||||
<Setter Property="hc:BorderElement.CornerRadius" Value="0" />
|
||||
<!-- 统一由 DataGrid 渲染网格线,避免行/列各自画线导致的错位 -->
|
||||
<Setter Property="GridLinesVisibility" Value="All" />
|
||||
<Setter Property="HorizontalGridLinesBrush" Value="{StaticResource BorderBrush}" />
|
||||
<Setter Property="VerticalGridLinesBrush" Value="{StaticResource BorderBrush}" />
|
||||
<Setter Property="RowHeight" Value="26" />
|
||||
<Setter Property="ColumnHeaderHeight" Value="28" />
|
||||
<!--
|
||||
表头边框(Border 绘制)和内容区网格线(DataGrid 内部 Pen 绘制)走的是两套渲染路径,
|
||||
统一开启像素对齐,避免非整数 DPI 下两者出现 1px 错位
|
||||
-->
|
||||
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||
<Setter Property="UseLayoutRounding" Value="True" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="DataGrid">
|
||||
<ControlTemplate.Resources>
|
||||
<!--<Storyboard x:Key="StoryboardMouseEnter">
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_VerticalScrollBar" Storyboard.TargetProperty="Opacity">
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:.2" Value=".8" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_HorizontalScrollBar" Storyboard.TargetProperty="Opacity">
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:.2" Value=".8" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<Storyboard x:Key="StoryboardMouseLeave">
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_VerticalScrollBar" Storyboard.TargetProperty="Opacity">
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:.2" Value="0" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_HorizontalScrollBar" Storyboard.TargetProperty="Opacity">
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:.2" Value="0" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>-->
|
||||
</ControlTemplate.Resources>
|
||||
<Border
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}"
|
||||
SnapsToDevicePixels="True">
|
||||
<ScrollViewer x:Name="DG_ScrollViewer" Focusable="false">
|
||||
<ScrollViewer.Template>
|
||||
<ControlTemplate TargetType="ScrollViewer">
|
||||
<Border Margin="0,0,-1,0" ClipToBounds="True">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Background="{StaticResource ColumnHeaderBackgroundBrush}">
|
||||
<Button
|
||||
Width="{Binding CellsPanelHorizontalOffset, RelativeSource={RelativeSource AncestorType=DataGrid}, Converter={StaticResource DoubleMinConverter}}"
|
||||
Height="auto"
|
||||
MaxHeight="30"
|
||||
hc:IconElement.Geometry="{StaticResource AllGeometry}"
|
||||
Command="{x:Static DataGrid.SelectAllCommand}"
|
||||
Foreground="{DynamicResource PrimaryBrush}"
|
||||
IsEnabled="False"
|
||||
Style="{StaticResource ButtonIcon}"
|
||||
Visibility="Hidden">
|
||||
<!--<Button.Visibility>
|
||||
<MultiBinding Converter="{StaticResource DataGridSelectAllButtonVisibilityConverter}">
|
||||
<Binding Path="HeadersVisibility" RelativeSource="{RelativeSource AncestorType=DataGrid}" />
|
||||
<Binding Path="(hc:DataGridAttach.ShowSelectAllButton)" RelativeSource="{RelativeSource AncestorType=DataGrid}" />
|
||||
</MultiBinding>
|
||||
</Button.Visibility>-->
|
||||
</Button>
|
||||
</Grid>
|
||||
<DataGridColumnHeadersPresenter
|
||||
x:Name="PART_ColumnHeadersPresenter"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.Column}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType=DataGrid}}" />
|
||||
<ScrollContentPresenter
|
||||
x:Name="PART_ScrollContentPresenter"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
CanContentScroll="{TemplateBinding CanContentScroll}" />
|
||||
<hc:ToggleBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
IsChecked="{Binding HasItems, RelativeSource={RelativeSource AncestorType=DataGrid}, Mode=OneWay}">
|
||||
<hc:ToggleBlock.UnCheckedContent>
|
||||
<hc:Empty
|
||||
Background="{DynamicResource RegionBrush}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}" />
|
||||
</hc:ToggleBlock.UnCheckedContent>
|
||||
</hc:ToggleBlock>
|
||||
<ScrollBar
|
||||
x:Name="PART_VerticalScrollBar"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="2"
|
||||
Maximum="{TemplateBinding ScrollableHeight}"
|
||||
Orientation="Vertical"
|
||||
Style="{StaticResource BaseStyle}"
|
||||
ViewportSize="{TemplateBinding ViewportHeight}"
|
||||
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
|
||||
Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
<ScrollBar
|
||||
x:Name="PART_HorizontalScrollBar"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Bottom"
|
||||
Maximum="{TemplateBinding ScrollableWidth}"
|
||||
Orientation="Horizontal"
|
||||
Style="{StaticResource BaseStyle}"
|
||||
ViewportSize="{TemplateBinding ViewportWidth}"
|
||||
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
|
||||
Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<!--<ControlTemplate.Triggers>
|
||||
<EventTrigger RoutedEvent="MouseEnter">
|
||||
<BeginStoryboard Storyboard="{StaticResource StoryboardMouseEnter}" />
|
||||
</EventTrigger>
|
||||
<EventTrigger RoutedEvent="MouseLeave">
|
||||
<BeginStoryboard Storyboard="{StaticResource StoryboardMouseLeave}" />
|
||||
</EventTrigger>
|
||||
</ControlTemplate.Triggers>-->
|
||||
</ControlTemplate>
|
||||
</ScrollViewer.Template>
|
||||
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
|
||||
|
||||
<Style x:Key="DataGridLinkColumn" TargetType="Hyperlink">
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryBrush}" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=DataGridRow}, Path=IsSelected}" Value="True">
|
||||
<Setter Property="Foreground" Value="{StaticResource TextIconBrush}" />
|
||||
</DataTrigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="Selector.IsSelectionActive" Value="false" />
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property="Background" Value="{DynamicResource DarkDefaultBrush}" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource PrimaryBrush}" />
|
||||
</MultiTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
|
||||
|
||||
|
||||
<!--
|
||||
Small 行样式:沿用 Rightangle 的模板(不再自画顶部边框),
|
||||
网格线线统一交由 DataGrid.Custom.Small 的 GridLinesVisibility 处理。
|
||||
-->
|
||||
<Style
|
||||
x:Key="DataGridRowStyle.Rightangle.Small"
|
||||
BasedOn="{StaticResource DataGridRowStyle.Rightangle}"
|
||||
TargetType="{x:Type DataGridRow}">
|
||||
<Setter Property="Margin" Value="0,0,0,0" />
|
||||
<Setter Property="Padding" Value="0,0" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="DataGridCellStyle.Custom.Small"
|
||||
BasedOn="{StaticResource DataGridCellStyle}"
|
||||
TargetType="DataGridCell">
|
||||
<Setter Property="Padding" Value="6,3" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="TextBlock.TextAlignment" Value="Right" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type DataGridCell}">
|
||||
<Border Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
|
||||
<ContentPresenter
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsSelected}" Value="True" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<MultiDataTrigger.Setters>
|
||||
<Setter Property="Background" Value="#cde6f7" />
|
||||
</MultiDataTrigger.Setters>
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="DataGridColumnHeaderStyle.Custom"
|
||||
BasedOn="{StaticResource DataGridColumnHeader.MarginSmall}"
|
||||
TargetType="DataGridColumnHeader">
|
||||
<Setter Property="Margin" Value="0,0,0,0" />
|
||||
<Setter Property="Padding" Value="6,4" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="DataGridRowHeaderStyle.Custom"
|
||||
BasedOn="{StaticResource DataGridRowHeaderStyle}"
|
||||
TargetType="DataGridRowHeader">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type DataGridRowHeader}">
|
||||
<hc:SimplePanel>
|
||||
<Border Padding="{TemplateBinding Padding}" Background="{TemplateBinding Background}">
|
||||
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
|
||||
<ContentPresenter
|
||||
VerticalAlignment="Center"
|
||||
RecognizesAccessKey="True"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Thumb
|
||||
Name="PART_TopHeaderGripper"
|
||||
VerticalAlignment="Top"
|
||||
Style="{StaticResource RowHeaderGripperStyle}" />
|
||||
<Thumb
|
||||
Name="PART_BottomHeaderGripper"
|
||||
VerticalAlignment="Bottom"
|
||||
Style="{StaticResource RowHeaderGripperStyle}" />
|
||||
</hc:SimplePanel>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<!-- {x:Type DataGrid} -->
|
||||
<Style
|
||||
x:Key="DataGrid.Custom.Small"
|
||||
BasedOn="{StaticResource DataGrid.Custom}"
|
||||
TargetType="{x:Type DataGrid}">
|
||||
<!-- 原来是 18,行内容会被挤压;24 能容纳默认字号并留出上下呼吸空间 -->
|
||||
<Setter Property="RowHeight" Value="24" />
|
||||
<Setter Property="RowStyle" Value="{StaticResource DataGridRowStyle.Rightangle.Small}" />
|
||||
<Setter Property="Padding" Value="1" />
|
||||
<Setter Property="RowHeaderStyle" Value="{StaticResource DataGridRowHeaderStyle.Custom}" />
|
||||
<Setter Property="CellStyle" Value="{StaticResource DataGridCellStyle.Custom.Small}" />
|
||||
<Setter Property="ColumnHeaderStyle" Value="{StaticResource DataGridColumnHeaderStyle.Custom}" />
|
||||
<Setter Property="ColumnHeaderHeight" Value="24" />
|
||||
</Style>
|
||||
|
||||
|
||||
<!-- DataGridEnd -->
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>$(DefaultTargetFramework)-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Bogus" Version="35.6.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Source\Trade.Framework\Trade.Framework.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Views\bbbDialogView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
<None Update="Views\HeaderView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,13 @@
|
||||
<UserControl
|
||||
x:Class="Main.Test.MainHeaderImageContent"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Main.Test"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Height="200"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
<Grid />
|
||||
</UserControl>
|
||||
@@ -0,0 +1,57 @@
|
||||
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 Main.Test
|
||||
{
|
||||
/// <summary>
|
||||
/// MainHeaderImageContent.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class MainHeaderImageContent : UserControl
|
||||
{
|
||||
public MainHeaderImageContent()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
private void IndicatorItem_PreviewMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
e.Handled = true;
|
||||
TryDragMove();
|
||||
}
|
||||
|
||||
private void TimeItem_PreviewMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
e.Handled = true;
|
||||
TryDragMove();
|
||||
}
|
||||
|
||||
private void TryDragMove()
|
||||
{
|
||||
Window window = Application.Current?.MainWindow;
|
||||
if (window is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
//if (window.WindowState == WindowState.Maximized)
|
||||
//{
|
||||
// window.WindowState = WindowState.Normal;
|
||||
//}
|
||||
window.DragMove();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
<tf:WindowBase
|
||||
x:Class="Main.Test.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Main.Test"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:tf="Trade.Framework"
|
||||
xmlns:vm="clr-namespace:Main.Test.ViewModels"
|
||||
Title="复杂表格 · 运行测试"
|
||||
Width="1380"
|
||||
Height="820"
|
||||
MinWidth="900"
|
||||
MinHeight="500"
|
||||
DataContext="{tf:DISource Type=vm:MainViewModel}"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d">
|
||||
<tf:WindowBase.Resources>
|
||||
<Style x:Key="ActionButton" TargetType="Button">
|
||||
<Setter Property="Padding" Value="9,4" />
|
||||
<Setter Property="Margin" Value="0,0,6,0" />
|
||||
<Setter Property="MinWidth" Value="48" />
|
||||
</Style>
|
||||
<ContextMenu x:Key="OrderRowMenu" DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
|
||||
<MenuItem
|
||||
Command="{Binding PlacementTarget.Tag.DetailCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="查看详情" />
|
||||
<MenuItem
|
||||
Command="{Binding PlacementTarget.Tag.ConfirmCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="确认 / 撤销确认" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{Binding PlacementTarget.Tag.CopyCodeCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
|
||||
CommandParameter="{Binding}"
|
||||
Header="复制编码" />
|
||||
</ContextMenu>
|
||||
</tf:WindowBase.Resources>
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<DockPanel Margin="0,0,0,12">
|
||||
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal">
|
||||
<ComboBox
|
||||
x:Name="ImageMode"
|
||||
Width="155"
|
||||
Margin="0,0,8,0"
|
||||
VerticalContentAlignment="Center"
|
||||
SelectedIndex="0">
|
||||
<ComboBoxItem Content="本地图片" Tag="0" />
|
||||
<ComboBoxItem Content="Picsum 160px" Tag="160" />
|
||||
<ComboBoxItem Content="Picsum 800px" Tag="800" />
|
||||
</ComboBox>
|
||||
<ComboBox
|
||||
x:Name="DataSize"
|
||||
Width="115"
|
||||
Margin="0,0,8,0"
|
||||
VerticalContentAlignment="Center"
|
||||
SelectedIndex="1">
|
||||
<ComboBoxItem Content="1,000 行" Tag="1000" />
|
||||
<ComboBoxItem Content="10,000 行" Tag="10000" />
|
||||
<ComboBoxItem Content="50,000 行" Tag="50000" />
|
||||
</ComboBox>
|
||||
<Button
|
||||
x:Name="LoadButton"
|
||||
Click="LoadData_Click"
|
||||
Content="加载数据"
|
||||
Style="{StaticResource ActionButton}" />
|
||||
<Button
|
||||
x:Name="ImageBenchmarkButton"
|
||||
Click="ImageBenchmark_Click"
|
||||
Content="图片滚动测试"
|
||||
Style="{StaticResource ActionButton}" />
|
||||
<Button
|
||||
x:Name="BenchmarkButton"
|
||||
Click="Benchmark_Click"
|
||||
Content="运行性能测试"
|
||||
Style="{StaticResource ActionButton}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
FontSize="20"
|
||||
FontWeight="SemiBold"
|
||||
Text="订单工作台 / 复杂内容测试" />
|
||||
<StackPanel Margin="0,5,0,0" Orientation="Horizontal">
|
||||
<TextBlock Foreground="#64748B" Text="{Binding Data.Count, StringFormat=共 {0:N0} 行}" />
|
||||
<TextBlock
|
||||
Margin="12,0,0,0"
|
||||
Foreground="#64748B"
|
||||
Text="{Binding SelectedCount, StringFormat=已选 {0:N0} 行}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
<local:StyledDataGrid
|
||||
x:Name="OrdersGrid"
|
||||
Grid.Row="1"
|
||||
d:ItemsSource="{d:SampleData ItemCount=10}"
|
||||
GridLinesVisibility="Horizontal"
|
||||
IsReadOnly="True"
|
||||
ItemsSource="{Binding Data}"
|
||||
RowContextMenu="{StaticResource OrderRowMenu}"
|
||||
RowDetailsVisibilityMode="Collapsed"
|
||||
RowHeight="92"
|
||||
SelectRowOnRightClick="True"
|
||||
SelectedItems="{Binding SelectedItems, Mode=TwoWay}"
|
||||
SelectionMode="Extended"
|
||||
ShowCheckBox="True"
|
||||
ShowRowNumber="True"
|
||||
ShowSelectAll="True">
|
||||
<local:StyledDataGrid.Columns>
|
||||
<DataGridTextColumn
|
||||
Width="90"
|
||||
Binding="{Binding Code}"
|
||||
Header="编码" />
|
||||
<DataGridTemplateColumn
|
||||
Width="300"
|
||||
Header="商品 / 规格"
|
||||
SortMemberPath="Name">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Margin="0,7">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="68" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border
|
||||
Width="58"
|
||||
Height="58"
|
||||
Background="#F1F5F9"
|
||||
CornerRadius="6">
|
||||
<local:NetworkThumbnail
|
||||
Width="58"
|
||||
Height="58"
|
||||
Placeholder="{Binding Thumbnail}"
|
||||
Stretch="Uniform"
|
||||
Url="{Binding ThumbnailUrl}" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
<TextBlock
|
||||
FontWeight="SemiBold"
|
||||
Text="{Binding Name}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
ToolTip="{Binding Name}" />
|
||||
<TextBlock
|
||||
Margin="0,5,0,0"
|
||||
FontSize="11"
|
||||
Opacity="0.65"
|
||||
Text="{Binding Sku}" />
|
||||
<TextBlock
|
||||
Margin="0,4,0,0"
|
||||
FontSize="11"
|
||||
Opacity="0.65"
|
||||
Text="{Binding Shop}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTextColumn
|
||||
Width="90"
|
||||
Binding="{Binding Price, StringFormat=N2}"
|
||||
CellStyle="{DynamicResource StyledNumberCellStyle}"
|
||||
Header="单价 / 元"
|
||||
HeaderStyle="{DynamicResource DefaultNumberCellHeaderStyle}" />
|
||||
<DataGridTextColumn
|
||||
Width="65"
|
||||
Binding="{Binding Qty}"
|
||||
CellStyle="{DynamicResource StyledNumberCellStyle}"
|
||||
Header="数量"
|
||||
HeaderStyle="{DynamicResource DefaultNumberCellHeaderStyle}" />
|
||||
<DataGridTemplateColumn
|
||||
Width="230"
|
||||
Header="买家 / 收货信息"
|
||||
SortMemberPath="Buyer">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock FontWeight="SemiBold" Text="{Binding Buyer}" />
|
||||
<TextBlock
|
||||
Margin="0,6,0,0"
|
||||
FontSize="11"
|
||||
Opacity="0.7"
|
||||
Text="{Binding Address}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTemplateColumn
|
||||
Width="95"
|
||||
Header="状态"
|
||||
SortMemberPath="Status">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
Padding="8,4"
|
||||
HorizontalAlignment="Left"
|
||||
Background="#E8F4FF"
|
||||
CornerRadius="4">
|
||||
<TextBlock
|
||||
FontSize="12"
|
||||
Foreground="#1769AA"
|
||||
Text="{Binding Status}" />
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTextColumn
|
||||
Width="105"
|
||||
Binding="{Binding CreatedAt, StringFormat=MM-dd HH:mm}"
|
||||
Header="创建时间" />
|
||||
<DataGridTemplateColumn
|
||||
Width="165"
|
||||
Header="备注"
|
||||
SortMemberPath="Remark">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock
|
||||
MaxHeight="60"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Remark}"
|
||||
TextWrapping="Wrap" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTemplateColumn Width="145" Header="操作">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
|
||||
<Button
|
||||
Command="{Binding DataContext.DetailCommand, RelativeSource={RelativeSource AncestorType=local:StyledDataGrid}}"
|
||||
CommandParameter="{Binding}"
|
||||
Content="详情"
|
||||
Style="{StaticResource ActionButton}" />
|
||||
<Button
|
||||
Command="{Binding DataContext.ConfirmCommand, RelativeSource={RelativeSource AncestorType=local:StyledDataGrid}}"
|
||||
CommandParameter="{Binding}"
|
||||
Content="确认"
|
||||
Style="{StaticResource ActionButton}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</local:StyledDataGrid.Columns>
|
||||
</local:StyledDataGrid>
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Margin="0,10,0,6"
|
||||
Text="{Binding ActionMessage}"
|
||||
TextWrapping="Wrap" />
|
||||
<Border
|
||||
Grid.Row="3"
|
||||
Padding="10,8"
|
||||
Background="#F1F5F9"
|
||||
CornerRadius="5">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
x:Name="ImageStatisticsText"
|
||||
Margin="0,0,0,4"
|
||||
Foreground="#475569"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
x:Name="PerformanceText"
|
||||
Foreground="#475569"
|
||||
Text="选择数据量后运行性能测试:连续滚动 60 次,并测量全选、取消全选及已生成行数。"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</tf:WindowBase>
|
||||
@@ -0,0 +1,153 @@
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using Main.Test.ViewModels;
|
||||
using Trade.Framework.Controls;
|
||||
|
||||
namespace Main.Test;
|
||||
|
||||
public partial class MainWindow : WindowBase
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(500) };
|
||||
timer.Tick += (_, _) => ImageStatisticsText.Text = ThumbnailLoader.Statistics;
|
||||
Loaded += (_, _) => timer.Start();
|
||||
Closed += (_, _) => timer.Stop();
|
||||
}
|
||||
|
||||
private async void LoadData_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SetBusy(true);
|
||||
try
|
||||
{
|
||||
var count = int.Parse((string)((ComboBoxItem)DataSize.SelectedItem).Tag);
|
||||
var watch = Stopwatch.StartNew();
|
||||
var vm = (MainViewModel)DataContext;
|
||||
vm.NetworkImageSize = int.Parse((string)((ComboBoxItem)ImageMode.SelectedItem).Tag);
|
||||
await vm.LoadAsync(count);
|
||||
OrdersGrid.ScrollIntoView(OrdersGrid.Items[0]);
|
||||
await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);
|
||||
PerformanceText.Text = $"加载 {count:N0} 行(含生成与首次布局):{watch.Elapsed.TotalMilliseconds:N0} ms。可继续测试滚动和全选。";
|
||||
}
|
||||
catch (Exception ex) { PerformanceText.Text = $"加载失败:{ex.Message}"; }
|
||||
finally { SetBusy(false); }
|
||||
}
|
||||
|
||||
private async void Benchmark_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SetBusy(true);
|
||||
try { PerformanceText.Text = await RunPerformanceTestAsync(); }
|
||||
catch (Exception ex) { PerformanceText.Text = $"测试失败:{ex.Message}"; }
|
||||
finally { SetBusy(false); }
|
||||
}
|
||||
|
||||
private async void ImageBenchmark_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SetBusy(true);
|
||||
try { PerformanceText.Text = await RunImagePerformanceTestAsync(); }
|
||||
catch (Exception ex) { PerformanceText.Text = $"图片测试失败:{ex.Message}"; }
|
||||
finally { SetBusy(false); }
|
||||
}
|
||||
|
||||
public async Task<string> RunImagePerformanceTestAsync()
|
||||
{
|
||||
if (OrdersGrid.Items.Count == 0 || ((MainViewModel)DataContext).NetworkImageSize == 0)
|
||||
return "请先选择 Picsum 图片模式并点击加载数据,再运行图片滚动测试。";
|
||||
var viewer = FindVisual<ScrollViewer>(OrdersGrid).First();
|
||||
var offset = viewer.VerticalOffset;
|
||||
var samples = new[] { new List<double>(), new List<double>() };
|
||||
var heartbeat = Stopwatch.StartNew();
|
||||
var lastTick = heartbeat.Elapsed.TotalMilliseconds;
|
||||
var maxDelay = 0d;
|
||||
var pulse = new DispatcherTimer(DispatcherPriority.Normal) { Interval = TimeSpan.FromMilliseconds(50) };
|
||||
pulse.Tick += (_, _) =>
|
||||
{
|
||||
var now = heartbeat.Elapsed.TotalMilliseconds;
|
||||
maxDelay = Math.Max(maxDelay, Math.Max(0, now - lastTick - 50));
|
||||
lastTick = now;
|
||||
};
|
||||
pulse.Start();
|
||||
try
|
||||
{
|
||||
viewer.ScrollToHorizontalOffset(0);
|
||||
for (var pass = 0; pass < 2; pass++)
|
||||
{
|
||||
PerformanceText.Text = pass == 0 ? "首次滚动:网络图片正在加载…" : "重复访问相同位置:比较缓存与重新加载…";
|
||||
for (var i = 0; i < 40; i++)
|
||||
{
|
||||
var watch = Stopwatch.StartNew();
|
||||
viewer.ScrollToVerticalOffset(i % 24);
|
||||
await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);
|
||||
samples[pass].Add(watch.Elapsed.TotalMilliseconds);
|
||||
await Task.Delay(150);
|
||||
}
|
||||
}
|
||||
// 有界等待当前可见图片,避免把尚未完成的请求当作加载成功。
|
||||
var wait = Stopwatch.StartNew();
|
||||
while (ThumbnailLoader.Pending > 0 && wait.Elapsed < TimeSpan.FromSeconds(8)) await Task.Delay(100);
|
||||
var first = samples[0].Order().ToArray(); var revisit = samples[1].Order().ToArray();
|
||||
return $"Picsum {((MainViewModel)DataContext).NetworkImageSize}px / {OrdersGrid.Items.Count:N0} 行 | 首次滚动均值 {first.Average():N1} ms / P95 {first[37]:N1} ms | 复访均值 {revisit.Average():N1} ms / P95 {revisit[37]:N1} ms | UI 心跳最大延迟 {maxDelay:N1} ms(非帧率);图片统计为本次进程累计,取消表示滚出视口。";
|
||||
}
|
||||
finally { pulse.Stop(); viewer.ScrollToVerticalOffset(offset); }
|
||||
}
|
||||
|
||||
// 测量 UI 线程滚动与布局响应,不等同于 GPU 帧率。
|
||||
public async Task<string> RunPerformanceTestAsync()
|
||||
{
|
||||
if (OrdersGrid.Items.Count == 0) return "没有可测试的数据。";
|
||||
var viewer = FindVisual<ScrollViewer>(OrdersGrid).First();
|
||||
var offset = viewer.VerticalOffset;
|
||||
var selected = ((DataGrid)OrdersGrid).SelectedItems.Cast<object>().ToArray();
|
||||
var samples = new List<double>();
|
||||
try
|
||||
{
|
||||
OrdersGrid.UnselectAll();
|
||||
OrdersGrid.ScrollIntoView(OrdersGrid.Items[0]);
|
||||
viewer.ScrollToHorizontalOffset(0);
|
||||
await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);
|
||||
for (var i = 0; i < 60; i++)
|
||||
{
|
||||
var watch = Stopwatch.StartNew();
|
||||
viewer.ScrollToVerticalOffset(viewer.ScrollableHeight * ((i * 17) % 60) / 59d);
|
||||
await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);
|
||||
samples.Add(watch.Elapsed.TotalMilliseconds);
|
||||
}
|
||||
var selectWatch = Stopwatch.StartNew();
|
||||
OrdersGrid.SelectAll();
|
||||
await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);
|
||||
var selectMs = selectWatch.Elapsed.TotalMilliseconds;
|
||||
selectWatch.Restart(); OrdersGrid.UnselectAll();
|
||||
await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);
|
||||
var clearMs = selectWatch.Elapsed.TotalMilliseconds;
|
||||
var realized = FindVisual<DataGridRow>(OrdersGrid).Count();
|
||||
samples.Sort();
|
||||
return $"{OrdersGrid.Items.Count:N0} 行 / 9 列 | 滚动与布局:均值 {samples.Average():N1} ms,P95 {samples[56]:N1} ms,最大 {samples[^1]:N1} ms | 全选 {selectMs:N0} ms / 取消 {clearMs:N0} ms | 已生成行 {realized} | 进程内存 {Process.GetCurrentProcess().WorkingSet64 / 1024 / 1024} MB(不等同帧率)";
|
||||
}
|
||||
finally
|
||||
{
|
||||
OrdersGrid.RestoreSelection(selected);
|
||||
viewer.ScrollToVerticalOffset(offset);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetBusy(bool busy)
|
||||
{
|
||||
LoadButton.IsEnabled = BenchmarkButton.IsEnabled = ImageBenchmarkButton.IsEnabled = DataSize.IsEnabled = ImageMode.IsEnabled = !busy;
|
||||
OrdersGrid.IsEnabled = !busy;
|
||||
}
|
||||
|
||||
private static IEnumerable<T> FindVisual<T>(DependencyObject parent) where T : DependencyObject
|
||||
{
|
||||
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
|
||||
{
|
||||
var child = VisualTreeHelper.GetChild(parent, i);
|
||||
if (child is T match) yield return match;
|
||||
foreach (var descendant in FindVisual<T>(child)) yield return descendant;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Main.Test;
|
||||
|
||||
public partial class StyledDataGrid
|
||||
{
|
||||
private DispatcherTimer? imageResumeTimer;
|
||||
private bool draggingScrollThumb;
|
||||
|
||||
private void InitializeImageLoadingPause()
|
||||
{
|
||||
imageResumeTimer = new DispatcherTimer(DispatcherPriority.Background, Dispatcher)
|
||||
{
|
||||
Interval = TimeSpan.FromMilliseconds(500)
|
||||
};
|
||||
imageResumeTimer.Tick += (_, _) =>
|
||||
{
|
||||
imageResumeTimer.Stop();
|
||||
if (!draggingScrollThumb && IsLoaded) SetImageLoadingSuspended(false);
|
||||
};
|
||||
AddHandler(Thumb.DragStartedEvent, new DragStartedEventHandler(ScrollThumbDragStarted), true);
|
||||
AddHandler(Thumb.DragCompletedEvent, new DragCompletedEventHandler(ScrollThumbDragCompleted), true);
|
||||
AddHandler(ScrollViewer.ScrollChangedEvent, new ScrollChangedEventHandler(ImageScrollChanged), true);
|
||||
Unloaded += (_, _) =>
|
||||
{
|
||||
imageResumeTimer.Stop(); draggingScrollThumb = false;
|
||||
SetImageLoadingSuspended(true);
|
||||
};
|
||||
Loaded += (_, _) => { ApplyImageLoadingOptions(); SetImageLoadingSuspended(false); };
|
||||
ApplyImageLoadingOptions();
|
||||
}
|
||||
|
||||
private void SetImageLoadingSuspended(bool suspended)
|
||||
{
|
||||
SetValue(IsImageLoadingSuspendedPropertyKey, suspended);
|
||||
NetworkThumbnail.SetIsLoadingSuspended(this, suspended);
|
||||
}
|
||||
|
||||
private void ApplyImageLoadingOptions()
|
||||
{
|
||||
NetworkThumbnail.SetCacheEnabled(this, EnableImageCache);
|
||||
if (imageResumeTimer == null) return;
|
||||
if (!PauseImageLoadingWhileDragging)
|
||||
{
|
||||
imageResumeTimer.Stop(); draggingScrollThumb = false; SetImageLoadingSuspended(false);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsOwnScrollBar(DependencyObject? source)
|
||||
{
|
||||
var foundScrollBar = false;
|
||||
for (var parent = source; parent != null; parent = VisualTreeHelper.GetParent(parent))
|
||||
{
|
||||
if (parent is ScrollBar) foundScrollBar = true;
|
||||
if (parent is DataGrid) return ReferenceEquals(parent, this) && foundScrollBar;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ScrollThumbDragStarted(object sender, DragStartedEventArgs e)
|
||||
{
|
||||
if (!PauseImageLoadingWhileDragging || !IsOwnScrollBar(e.OriginalSource as DependencyObject)) return;
|
||||
draggingScrollThumb = true;
|
||||
imageResumeTimer!.Stop();
|
||||
// 继承到可见图片及随后回收生成的新行,立即取消请求并清空图片。
|
||||
SetImageLoadingSuspended(true);
|
||||
}
|
||||
|
||||
private void ScrollThumbDragCompleted(object sender, DragCompletedEventArgs e)
|
||||
{
|
||||
if (!PauseImageLoadingWhileDragging || !IsOwnScrollBar(e.OriginalSource as DependencyObject)) return;
|
||||
draggingScrollThumb = false;
|
||||
RestartImageResumeTimer();
|
||||
}
|
||||
|
||||
private void ImageScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
if (!NetworkThumbnail.GetIsLoadingSuspended(this) || draggingScrollThumb ||
|
||||
(e.VerticalChange == 0 && e.HorizontalChange == 0) ||
|
||||
!IsOwnScrollViewer(e.OriginalSource as DependencyObject)) return;
|
||||
RestartImageResumeTimer();
|
||||
}
|
||||
|
||||
private bool IsOwnScrollViewer(DependencyObject? source)
|
||||
{
|
||||
for (var parent = source; parent != null; parent = VisualTreeHelper.GetParent(parent))
|
||||
if (parent is DataGrid) return ReferenceEquals(parent, this);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void RestartImageResumeTimer()
|
||||
{
|
||||
imageResumeTimer!.Stop();
|
||||
imageResumeTimer.Start();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Main.Test;
|
||||
|
||||
public partial class StyledDataGrid
|
||||
{
|
||||
public static readonly DependencyProperty NoDataTipsProperty = DataGridHelper.NoDataTipsProperty.AddOwner(typeof(StyledDataGrid));
|
||||
public string NoDataTips { get => (string)GetValue(NoDataTipsProperty); set => SetValue(NoDataTipsProperty, value); }
|
||||
public static readonly DependencyProperty IsActiveProperty = DataGridHelper.IsActiveProperty.AddOwner(typeof(StyledDataGrid));
|
||||
/// <summary>为 true 时抑制空数据提示;可在业务加载数据期间设置。</summary>
|
||||
public bool IsActive { get => (bool)GetValue(IsActiveProperty); set => SetValue(IsActiveProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty RowContextMenuProperty = DependencyProperty.Register(nameof(RowContextMenu), typeof(ContextMenu), typeof(StyledDataGrid), new PropertyMetadata(null));
|
||||
public ContextMenu? RowContextMenu { get => (ContextMenu?)GetValue(RowContextMenuProperty); set => SetValue(RowContextMenuProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty ShowSelectAllProperty = DependencyProperty.Register(nameof(ShowSelectAll), typeof(bool), typeof(StyledDataGrid), new PropertyMetadata(true));
|
||||
public static readonly DependencyProperty ToggleSelectionOnRowHeaderClickProperty = DependencyProperty.Register(nameof(ToggleSelectionOnRowHeaderClick), typeof(bool), typeof(StyledDataGrid), new PropertyMetadata(true));
|
||||
public static readonly DependencyProperty SelectRowOnRightClickProperty = DependencyProperty.Register(nameof(SelectRowOnRightClick), typeof(bool), typeof(StyledDataGrid), new PropertyMetadata(false));
|
||||
public static readonly DependencyProperty PauseImageLoadingWhileDraggingProperty = DependencyProperty.Register(nameof(PauseImageLoadingWhileDragging), typeof(bool), typeof(StyledDataGrid), new PropertyMetadata(true, ImageOptionsChanged));
|
||||
public static readonly DependencyProperty EnableImageCacheProperty = DependencyProperty.Register(nameof(EnableImageCache), typeof(bool), typeof(StyledDataGrid), new PropertyMetadata(true, ImageOptionsChanged));
|
||||
private static readonly DependencyPropertyKey IsImageLoadingSuspendedPropertyKey = DependencyProperty.RegisterReadOnly(nameof(IsImageLoadingSuspended), typeof(bool), typeof(StyledDataGrid), new PropertyMetadata(false));
|
||||
public static readonly DependencyProperty IsImageLoadingSuspendedProperty = IsImageLoadingSuspendedPropertyKey.DependencyProperty;
|
||||
|
||||
public bool ShowSelectAll { get => (bool)GetValue(ShowSelectAllProperty); set => SetValue(ShowSelectAllProperty, value); }
|
||||
public bool ToggleSelectionOnRowHeaderClick { get => (bool)GetValue(ToggleSelectionOnRowHeaderClickProperty); set => SetValue(ToggleSelectionOnRowHeaderClickProperty, value); }
|
||||
public bool SelectRowOnRightClick { get => (bool)GetValue(SelectRowOnRightClickProperty); set => SetValue(SelectRowOnRightClickProperty, value); }
|
||||
/// <summary>默认开启;拖动时暂停并清空图片,停止滚动 500ms 后恢复。</summary>
|
||||
public bool PauseImageLoadingWhileDragging { get => (bool)GetValue(PauseImageLoadingWhileDraggingProperty); set => SetValue(PauseImageLoadingWhileDraggingProperty, value); }
|
||||
/// <summary>默认开启图片缓存;设为 false 时不读取或写入缓存。</summary>
|
||||
public bool EnableImageCache { get => (bool)GetValue(EnableImageCacheProperty); set => SetValue(EnableImageCacheProperty, value); }
|
||||
public bool IsImageLoadingSuspended => (bool)GetValue(IsImageLoadingSuspendedProperty);
|
||||
|
||||
private static void ImageOptionsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
=> ((StyledDataGrid)d).ApplyImageLoadingOptions();
|
||||
|
||||
protected override void OnPreviewMouseRightButtonDown(MouseButtonEventArgs e)
|
||||
{
|
||||
if (SelectRowOnRightClick && !e.Handled && SelectionUnit != DataGridSelectionUnit.Cell && e.OriginalSource is DependencyObject source)
|
||||
{
|
||||
var row = FindRow(source);
|
||||
if (row != null && ItemsControl.ItemsControlFromItemContainer(row) == this && !row.IsSelected &&
|
||||
row.Item != System.Windows.Data.CollectionView.NewItemPlaceholder)
|
||||
{
|
||||
UnselectAll(); row.SetCurrentValue(DataGridRow.IsSelectedProperty, true);
|
||||
}
|
||||
}
|
||||
base.OnPreviewMouseRightButtonDown(e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Main.Test;
|
||||
|
||||
/// <summary>带默认样式、行号和行选择勾选框的 DataGrid。</summary>
|
||||
public partial class StyledDataGrid : DataGrid
|
||||
{
|
||||
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(
|
||||
nameof(SelectedItems), typeof(IList), typeof(StyledDataGrid),
|
||||
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemsChanged));
|
||||
|
||||
/// <summary>可绑定的选中集合。使用 ObservableCollection 可从 VM 增删选中项;未绑定时返回原生选中集合。</summary>
|
||||
public new IList SelectedItems
|
||||
{
|
||||
get => (IList?)GetValue(SelectedItemsProperty) ?? base.SelectedItems;
|
||||
set => SetValue(SelectedItemsProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>批量恢复性能测试前的选择,只发出一次选择变更。</summary>
|
||||
internal void RestoreSelection(IEnumerable<object> selected)
|
||||
{
|
||||
var available = Items.Cast<object>().ToHashSet();
|
||||
BeginUpdateSelectedItems();
|
||||
try
|
||||
{
|
||||
base.SelectedItems.Clear();
|
||||
foreach (var item in selected)
|
||||
if (available.Contains(item)) base.SelectedItems.Add(item);
|
||||
}
|
||||
finally { EndUpdateSelectedItems(); }
|
||||
}
|
||||
|
||||
private bool synchronizingSelection;
|
||||
|
||||
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var grid = (StyledDataGrid)d;
|
||||
if (e.NewValue is IList { IsReadOnly: true } or IList { IsFixedSize: true })
|
||||
throw new ArgumentException("SelectedItems 必须绑定到可增删的集合,例如 ObservableCollection<T>。");
|
||||
if (e.OldValue is INotifyCollectionChanged oldCollection)
|
||||
CollectionChangedEventManager.RemoveHandler(oldCollection, grid.OnBoundSelectionChanged);
|
||||
if (e.NewValue is INotifyCollectionChanged newCollection)
|
||||
CollectionChangedEventManager.AddHandler(newCollection, grid.OnBoundSelectionChanged);
|
||||
grid.ApplyBoundSelection();
|
||||
}
|
||||
|
||||
private void OnBoundSelectionChanged(object? sender, NotifyCollectionChangedEventArgs e) => ApplyBoundSelection();
|
||||
|
||||
private void ApplyBoundSelection()
|
||||
{
|
||||
if (synchronizingSelection || GetValue(SelectedItemsProperty) is not IList boundItems ||
|
||||
ReferenceEquals(boundItems, base.SelectedItems) || SelectionUnit == DataGridSelectionUnit.Cell) return;
|
||||
var available = Items.Cast<object>().ToHashSet();
|
||||
var desired = boundItems.Cast<object>().Where(item => available.Contains(item) &&
|
||||
item != System.Windows.Data.CollectionView.NewItemPlaceholder).Distinct().ToList();
|
||||
if (SelectionMode == DataGridSelectionMode.Single) desired = desired.Take(1).ToList();
|
||||
synchronizingSelection = true;
|
||||
try
|
||||
{
|
||||
var desiredSet = desired.ToHashSet();
|
||||
var current = base.SelectedItems.Cast<object>().ToHashSet();
|
||||
if (SelectionMode == DataGridSelectionMode.Single)
|
||||
SetCurrentValue(SelectedItemProperty, desired.FirstOrDefault());
|
||||
else if (desired.Count == 0) UnselectAll();
|
||||
else
|
||||
{
|
||||
foreach (var item in current)
|
||||
if (!desiredSet.Contains(item)) base.SelectedItems.Remove(item);
|
||||
foreach (var item in desired)
|
||||
if (!current.Contains(item)) base.SelectedItems.Add(item);
|
||||
}
|
||||
}
|
||||
finally { synchronizingSelection = false; }
|
||||
UpdateSelectAllState();
|
||||
}
|
||||
|
||||
private void PublishSelection()
|
||||
{
|
||||
if (synchronizingSelection || GetValue(SelectedItemsProperty) is not IList boundItems ||
|
||||
ReferenceEquals(boundItems, base.SelectedItems)) return;
|
||||
synchronizingSelection = true;
|
||||
try
|
||||
{
|
||||
var current = base.SelectedItems.Cast<object>().ToArray();
|
||||
if (current.Length == 0) boundItems.Clear();
|
||||
else
|
||||
{
|
||||
var currentSet = current.ToHashSet();
|
||||
for (var i = boundItems.Count - 1; i >= 0; i--)
|
||||
if (!currentSet.Contains(boundItems[i]!)) boundItems.RemoveAt(i);
|
||||
var boundSet = boundItems.Cast<object>().ToHashSet();
|
||||
foreach (var item in current)
|
||||
if (boundSet.Add(item)) boundItems.Add(item);
|
||||
}
|
||||
}
|
||||
finally { synchronizingSelection = false; }
|
||||
}
|
||||
|
||||
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
|
||||
{
|
||||
var wasSynchronizing = synchronizingSelection;
|
||||
synchronizingSelection = true;
|
||||
try { base.OnItemsSourceChanged(oldValue, newValue); }
|
||||
finally { synchronizingSelection = wasSynchronizing; }
|
||||
ApplyBoundSelection();
|
||||
}
|
||||
private static readonly DependencyPropertyKey IsSelectionModeActivePropertyKey = DependencyProperty.RegisterReadOnly(
|
||||
nameof(IsSelectionModeActive), typeof(bool), typeof(StyledDataGrid), new PropertyMetadata(false));
|
||||
public static readonly DependencyProperty IsSelectionModeActiveProperty = IsSelectionModeActivePropertyKey.DependencyProperty;
|
||||
/// <summary>点击行头勾选框后进入批量选择,取消所有选中后退出。</summary>
|
||||
public bool IsSelectionModeActive => (bool)GetValue(IsSelectionModeActiveProperty);
|
||||
public static readonly RoutedUICommand ToggleSelectAllCommand = new("全选或取消全选", nameof(ToggleSelectAllCommand), typeof(StyledDataGrid));
|
||||
private static readonly DependencyPropertyKey AreAllRowsSelectedPropertyKey = DependencyProperty.RegisterReadOnly(
|
||||
nameof(AreAllRowsSelected), typeof(bool?), typeof(StyledDataGrid), new PropertyMetadata(false));
|
||||
public static readonly DependencyProperty AreAllRowsSelectedProperty = AreAllRowsSelectedPropertyKey.DependencyProperty;
|
||||
/// <summary>全部选中为 true,部分选中为 null,未选中为 false。</summary>
|
||||
public bool? AreAllRowsSelected => (bool?)GetValue(AreAllRowsSelectedProperty);
|
||||
public static readonly DependencyProperty SelectedRowBackgroundProperty = DependencyProperty.Register(
|
||||
nameof(SelectedRowBackground), typeof(Brush), typeof(StyledDataGrid),
|
||||
new PropertyMetadata(new SolidColorBrush(Color.FromRgb(75, 178, 255))));
|
||||
public static readonly DependencyProperty SelectedRowForegroundProperty = DependencyProperty.Register(
|
||||
nameof(SelectedRowForeground), typeof(Brush), typeof(StyledDataGrid), new PropertyMetadata(Brushes.White));
|
||||
public static readonly DependencyProperty ShowCheckBoxProperty = DependencyProperty.Register(
|
||||
nameof(ShowCheckBox), typeof(bool), typeof(StyledDataGrid), new PropertyMetadata(false, HeaderOptionsChanged));
|
||||
public static readonly DependencyProperty ShowRowNumberProperty = DependencyProperty.Register(
|
||||
nameof(ShowRowNumber), typeof(bool), typeof(StyledDataGrid), new PropertyMetadata(false, HeaderOptionsChanged));
|
||||
|
||||
public Brush SelectedRowBackground { get => (Brush)GetValue(SelectedRowBackgroundProperty); set => SetValue(SelectedRowBackgroundProperty, value); }
|
||||
public Brush SelectedRowForeground { get => (Brush)GetValue(SelectedRowForegroundProperty); set => SetValue(SelectedRowForegroundProperty, value); }
|
||||
/// <summary>勾选状态与行 IsSelected 同步,可通过 SelectedItems 获取。</summary>
|
||||
public bool ShowCheckBox { get => (bool)GetValue(ShowCheckBoxProperty); set => SetValue(ShowCheckBoxProperty, value); }
|
||||
public bool ShowRowNumber { get => (bool)GetValue(ShowRowNumberProperty); set => SetValue(ShowRowNumberProperty, value); }
|
||||
|
||||
static StyledDataGrid()
|
||||
{
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(StyledDataGrid), new FrameworkPropertyMetadata(typeof(StyledDataGrid)));
|
||||
}
|
||||
|
||||
public StyledDataGrid()
|
||||
{
|
||||
InitializeImageLoadingPause();
|
||||
CommandBindings.Add(new CommandBinding(ToggleSelectAllCommand, (_, e) =>
|
||||
{
|
||||
if (AreAllRowsSelected == true) UnselectAll();
|
||||
else
|
||||
{
|
||||
SelectAll();
|
||||
SetValue(IsSelectionModeActivePropertyKey, base.SelectedItems.Count > 0);
|
||||
}
|
||||
e.Handled = true;
|
||||
}, (_, e) =>
|
||||
{
|
||||
e.CanExecute = SelectionMode == DataGridSelectionMode.Extended &&
|
||||
SelectionUnit != DataGridSelectionUnit.Cell && SelectableItemCount > 0;
|
||||
e.Handled = true;
|
||||
}));
|
||||
AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(OnRowCheckBoxClick), true);
|
||||
LoadingRow += (_, e) => UpdateRowNumber(e.Row);
|
||||
ItemContainerGenerator.ItemsChanged += (_, _) => QueueHeaderUpdate();
|
||||
Loaded += (_, _) => { ApplyBoundSelection(); QueueHeaderUpdate(); };
|
||||
}
|
||||
|
||||
private void OnRowCheckBoxClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (e.OriginalSource is not CheckBox checkBox) return;
|
||||
DependencyObject? parent = checkBox;
|
||||
while (parent != null && parent != this)
|
||||
{
|
||||
if (parent is DataGridRowHeader header &&
|
||||
ItemsControl.ItemsControlFromItemContainer(FindRow(header)) == this)
|
||||
{
|
||||
SetValue(IsSelectionModeActivePropertyKey, base.SelectedItems.Count > 0);
|
||||
return;
|
||||
}
|
||||
parent = VisualTreeHelper.GetParent(parent);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
|
||||
{
|
||||
if (!e.Handled && ShowCheckBox && ToggleSelectionOnRowHeaderClick && SelectionUnit != DataGridSelectionUnit.Cell)
|
||||
{
|
||||
for (var source = e.OriginalSource as DependencyObject; source != null && source != this;
|
||||
source = source is FrameworkContentElement content ? content.Parent : VisualTreeHelper.GetParent(source))
|
||||
{
|
||||
// 保留行高拖动和嵌套表格自己的鼠标处理。
|
||||
if (source is Thumb || source is DataGrid) break;
|
||||
if (source is not DataGridRowHeader header) continue;
|
||||
var row = FindRow(header);
|
||||
if (row == null || ItemsControl.ItemsControlFromItemContainer(row) != this ||
|
||||
row.Item == System.Windows.Data.CollectionView.NewItemPlaceholder) break;
|
||||
|
||||
// 在 DataGridRowHeader 默认单选逻辑之前处理整个行头,
|
||||
// 包括勾选框四周的留白;阻止 CheckBox 再次切换造成双重反转。
|
||||
e.Handled = true;
|
||||
row.SetCurrentValue(DataGridRow.IsSelectedProperty, !row.IsSelected);
|
||||
SetValue(IsSelectionModeActivePropertyKey, base.SelectedItems.Count > 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
base.OnPreviewMouseLeftButtonDown(e);
|
||||
}
|
||||
|
||||
private static DataGridRow? FindRow(DependencyObject element)
|
||||
{
|
||||
for (DependencyObject? parent = element; parent != null; parent = parent is FrameworkContentElement content ? content.Parent : VisualTreeHelper.GetParent(parent))
|
||||
if (parent is DataGridRow row) return row;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void HeaderOptionsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
=> ((StyledDataGrid)d).QueueHeaderUpdate();
|
||||
|
||||
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
base.OnItemsChanged(e);
|
||||
QueueHeaderUpdate();
|
||||
UpdateSelectAllState();
|
||||
}
|
||||
|
||||
private int SelectableItemCount => Items.Count - (Items.Contains(System.Windows.Data.CollectionView.NewItemPlaceholder) ? 1 : 0);
|
||||
|
||||
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
|
||||
{
|
||||
PublishSelection();
|
||||
base.OnSelectionChanged(e);
|
||||
UpdateSelectAllState();
|
||||
}
|
||||
|
||||
private void UpdateSelectAllState()
|
||||
{
|
||||
SetValue(AreAllRowsSelectedPropertyKey, base.SelectedItems.Count == 0 ? false :
|
||||
base.SelectedItems.Count == SelectableItemCount ? true : (bool?)null);
|
||||
if (base.SelectedItems.Count == 0) SetValue(IsSelectionModeActivePropertyKey, false);
|
||||
CommandManager.InvalidateRequerySuggested();
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
base.OnPropertyChanged(e);
|
||||
if (e.Property == FontSizeProperty || e.Property == FontFamilyProperty ||
|
||||
e.Property == FontStyleProperty || e.Property == FontWeightProperty || e.Property == FontStretchProperty)
|
||||
QueueHeaderUpdate();
|
||||
}
|
||||
|
||||
private bool headerUpdatePending;
|
||||
private void QueueHeaderUpdate()
|
||||
{
|
||||
if (headerUpdatePending) return;
|
||||
headerUpdatePending = true;
|
||||
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() =>
|
||||
{
|
||||
headerUpdatePending = false;
|
||||
foreach (var row in GetRealizedRows(this)) UpdateRowNumber(row);
|
||||
}));
|
||||
}
|
||||
|
||||
private void UpdateRowNumber(DataGridRow row)
|
||||
=> row.SetCurrentValue(DataGridRow.HeaderProperty, ShowRowNumber ? (row.GetIndex() + 1).ToString(CultureInfo.CurrentCulture) : null);
|
||||
|
||||
private IEnumerable<DataGridRow> GetRealizedRows(DependencyObject parent)
|
||||
{
|
||||
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
|
||||
{
|
||||
var child = VisualTreeHelper.GetChild(parent, i);
|
||||
if (child is DataGridRow row && ItemsControl.ItemsControlFromItemContainer(row) == this) yield return row;
|
||||
else if (child is not DataGrid)
|
||||
foreach (var descendant in GetRealizedRows(child)) yield return descendant;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,805 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:Main.Test"
|
||||
xmlns:ext="clr-namespace:Main.Test"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib">
|
||||
|
||||
|
||||
<Style x:Key="DefaultCellHeaderStyle" TargetType="DataGridColumnHeader">
|
||||
<Setter Property="UseLayoutRounding" Value="True" />
|
||||
<!--<Setter Property="ext:DataGridHelper.ShowBorder" Value="True" />-->
|
||||
|
||||
<Setter Property="Height" Value="{Binding ColumnHeaderHeight, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
|
||||
<Setter Property="Background" Value="#eeeeee" />
|
||||
<Setter Property="BorderThickness" Value="0,0,0,1" />
|
||||
<!-- 只保留底边框,竖线不再用 Border 画 -->
|
||||
<Setter Property="HorizontalContentAlignment" Value="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=HorizontalContentAlignment}" />
|
||||
<Setter Property="Padding" Value="8,0" />
|
||||
<Setter Property="FontSize" Value="{DynamicResource DefaultFontSize}" />
|
||||
<Setter Property="FontFamily" Value="{DynamicResource FontFamily}" />
|
||||
<Setter Property="BorderBrush" Value="{Binding HorizontalGridLinesBrush, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="DataGridColumnHeader">
|
||||
<Grid>
|
||||
<Border
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
UseLayoutRounding="True">
|
||||
<Grid HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ContentPresenter
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
RecognizesAccessKey="True"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
<Grid
|
||||
x:Name="SortArrowGrid"
|
||||
Grid.Column="1"
|
||||
Width="7"
|
||||
Height="15"
|
||||
Margin="10,0,5,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="Collapsed">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Path
|
||||
x:Name="SortArrowUp"
|
||||
Margin="0,0,0,2"
|
||||
Data="M500 365.76l-244.78 279.7a16 16 0 0 0 12 26.54h489.52a16 16 0 0 0 12-26.54L524 365.76a16 16 0 0 0-24 0z"
|
||||
Fill="#D2D2D2"
|
||||
Stretch="Fill" />
|
||||
<Path
|
||||
x:Name="SortArrowDown"
|
||||
Grid.Row="1"
|
||||
Margin="0,2,0,0"
|
||||
Data="M524 658.24l244.74-279.7a16 16 0 0 0-12-26.54H267.26a16 16 0 0 0-12 26.54L500 658.24a16 16 0 0 0 24 0z"
|
||||
Fill="#D2D2D2"
|
||||
Stretch="Fill" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 关键改动:用固定宽度的 Rectangle 画右侧竖线,永不消失 -->
|
||||
<Rectangle
|
||||
Width="1"
|
||||
HorizontalAlignment="Right"
|
||||
Fill="{TemplateBinding BorderBrush}"
|
||||
SnapsToDevicePixels="False" />
|
||||
|
||||
<Thumb
|
||||
x:Name="PART_LeftHeaderGripper"
|
||||
Width="6"
|
||||
HorizontalAlignment="Left"
|
||||
Background="Transparent"
|
||||
Cursor="SizeWE"
|
||||
FocusVisualStyle="{x:Null}">
|
||||
<Thumb.Style>
|
||||
<Style TargetType="Thumb">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Thumb">
|
||||
<Border Padding="{TemplateBinding Padding}" Background="{TemplateBinding Background}" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Thumb.Style>
|
||||
</Thumb>
|
||||
<Thumb
|
||||
x:Name="PART_RightHeaderGripper"
|
||||
Width="6"
|
||||
HorizontalAlignment="Right"
|
||||
Background="Transparent"
|
||||
Cursor="SizeWE"
|
||||
FocusVisualStyle="{x:Null}">
|
||||
<Thumb.Style>
|
||||
<Style TargetType="Thumb">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Thumb">
|
||||
<Border Padding="{TemplateBinding Padding}" Background="{TemplateBinding Background}" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Thumb.Style>
|
||||
</Thumb>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="SortDirection" Value="Ascending">
|
||||
<Setter TargetName="SortArrowGrid" Property="Visibility" Value="Visible" />
|
||||
<Setter TargetName="SortArrowUp" Property="Fill" Value="#61646D" />
|
||||
<Setter TargetName="SortArrowDown" Property="Fill" Value="#D2D2D2" />
|
||||
</Trigger>
|
||||
<Trigger Property="SortDirection" Value="Descending">
|
||||
<Setter TargetName="SortArrowGrid" Property="Visibility" Value="Visible" />
|
||||
<Setter TargetName="SortArrowUp" Property="Fill" Value="#D2D2D2" />
|
||||
<Setter TargetName="SortArrowDown" Property="Fill" Value="#61646D" />
|
||||
</Trigger>
|
||||
<Trigger Property="SortDirection" Value="{x:Null}">
|
||||
<Setter TargetName="SortArrowGrid" Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding GridLinesVisibility, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" Value="None">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="DefaultCellStyle" TargetType="DataGridCell">
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Padding" Value="8,0" />
|
||||
<Setter Property="FontSize" Value="{DynamicResource DefaultFontSize}" />
|
||||
<Setter Property="FontFamily" Value="{DynamicResource FontFamily}" />
|
||||
<!--<Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}, Path=Foreground, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />-->
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type DataGridCell}">
|
||||
<Grid>
|
||||
<Border
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}">
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</Border>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="DefaultRowHeaderStyle" TargetType="{x:Type DataGridRowHeader}">
|
||||
<Setter Property="BorderBrush" Value="{Binding BorderBrush, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
|
||||
<Setter Property="BorderThickness" Value="0,0,1,1" />
|
||||
<Setter Property="FontSize" Value="{DynamicResource DefaultFontSize}" />
|
||||
<Setter Property="FontFamily" Value="{DynamicResource FontFamily}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type DataGridRowHeader}">
|
||||
<Border
|
||||
Background="Transparent"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}">
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
TextElement.FontSize="10" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}" Value="True">
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</DataTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="DefaultRowStyle" TargetType="{x:Type DataGridRow}">
|
||||
<Setter Property="BorderThickness" Value="0,0,1,0" />
|
||||
<Setter Property="Background" Value="{x:Null}" />
|
||||
<Setter Property="FontSize" Value="{DynamicResource DefaultFontSize}" />
|
||||
<Setter Property="FontFamily" Value="{DynamicResource FontFamily}" />
|
||||
<Setter Property="BorderBrush" Value="{Binding RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}, Path=BorderBrush}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="DataGridRow">
|
||||
<Grid>
|
||||
<Border
|
||||
x:Name="border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}" />
|
||||
<SelectiveScrollingGrid>
|
||||
<SelectiveScrollingGrid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</SelectiveScrollingGrid.ColumnDefinitions>
|
||||
<SelectiveScrollingGrid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</SelectiveScrollingGrid.RowDefinitions>
|
||||
<DataGridCellsPresenter
|
||||
Grid.Column="1"
|
||||
ItemsPanel="{TemplateBinding ItemsPanel}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
<DataGridDetailsPresenter
|
||||
x:Name="DetailsPresenter"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
SelectiveScrollingGrid.SelectiveScrollingOrientation="{Binding AreRowDetailsFrozen, ConverterParameter={x:Static SelectiveScrollingOrientation.Vertical}, Converter={x:Static DataGrid.RowDetailsScrollingConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
|
||||
Visibility="{TemplateBinding DetailsVisibility}" />
|
||||
<DataGridRowHeader
|
||||
Grid.RowSpan="2"
|
||||
SelectiveScrollingGrid.SelectiveScrollingOrientation="Vertical"
|
||||
Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.Row}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
|
||||
</SelectiveScrollingGrid>
|
||||
<Grid
|
||||
x:Name="fousIcon"
|
||||
IsHitTestVisible="False"
|
||||
Opacity="0"
|
||||
Visibility="Collapsed">
|
||||
<Path
|
||||
Width="10"
|
||||
Height="10"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Data="M0,0 L0,0 50,0 50,10 10,10 10,50 0,50 0,52 z"
|
||||
Fill="#4BB2FF"
|
||||
RenderTransformOrigin="0.5,0.5"
|
||||
Stretch="Fill">
|
||||
<Path.RenderTransform>
|
||||
<TransformGroup>
|
||||
<ScaleTransform />
|
||||
<SkewTransform />
|
||||
<RotateTransform Angle="0" />
|
||||
<TranslateTransform />
|
||||
</TransformGroup>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
<Path
|
||||
Width="10"
|
||||
Height="10"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Data="M0,0 L0,0 50,0 50,10 10,10 10,50 0,50 0,52 z"
|
||||
Fill="#4BB2FF"
|
||||
RenderTransformOrigin="0.5,0.5"
|
||||
Stretch="Fill">
|
||||
<Path.RenderTransform>
|
||||
<TransformGroup>
|
||||
<ScaleTransform />
|
||||
<SkewTransform />
|
||||
<RotateTransform Angle="90" />
|
||||
<TranslateTransform />
|
||||
</TransformGroup>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
<Path
|
||||
Width="10"
|
||||
Height="10"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Data="M0,0 L0,0 50,0 50,10 10,10 10,50 0,50 0,52 z"
|
||||
Fill="#4BB2FF"
|
||||
RenderTransformOrigin="0.5,0.5"
|
||||
Stretch="Fill">
|
||||
<Path.RenderTransform>
|
||||
<TransformGroup>
|
||||
<ScaleTransform />
|
||||
<SkewTransform />
|
||||
<RotateTransform Angle="180" />
|
||||
<TranslateTransform />
|
||||
</TransformGroup>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
<Path
|
||||
Width="10"
|
||||
Height="10"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Bottom"
|
||||
Data="M0,0 L0,0 50,0 50,10 10,10 10,50 0,50 0,52 z"
|
||||
Fill="#4BB2FF"
|
||||
RenderTransformOrigin="0.5,0.5"
|
||||
Stretch="Fill">
|
||||
<Path.RenderTransform>
|
||||
<TransformGroup>
|
||||
<ScaleTransform />
|
||||
<SkewTransform />
|
||||
<RotateTransform Angle="-90" />
|
||||
<TranslateTransform />
|
||||
</TransformGroup>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="fousIcon" Property="Opacity" Value="1" />
|
||||
<!-- 整行变色 -->
|
||||
<Setter TargetName="border" Property="Background" Value="#4BB2FF" />
|
||||
</Trigger>
|
||||
<Trigger Property="DetailsVisibility" Value="Visible">
|
||||
<Setter TargetName="DetailsPresenter" Property="Margin" Value="0,0,1,0" />
|
||||
</Trigger>
|
||||
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="DefaultNumberCellHeaderStyle"
|
||||
BasedOn="{StaticResource DefaultCellHeaderStyle}"
|
||||
TargetType="DataGridColumnHeader">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="DefaultNumberCellStyle"
|
||||
BasedOn="{StaticResource DefaultCellStyle}"
|
||||
TargetType="DataGridCell">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="DefaultDataGridStyle" TargetType="DataGrid">
|
||||
<Setter Property="UseLayoutRounding" Value="True" />
|
||||
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||
<Setter Property="Background" Value="White" />
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
|
||||
<Setter Property="ColumnHeaderHeight" Value="36" />
|
||||
<Setter Property="MinRowHeight" Value="40" />
|
||||
<Setter Property="CanUserAddRows" Value="False" />
|
||||
<Setter Property="AutoGenerateColumns" Value="False" />
|
||||
<Setter Property="CanUserSortColumns" Value="True" />
|
||||
<Setter Property="CanUserReorderColumns" Value="False" />
|
||||
<Setter Property="AreRowDetailsFrozen" Value="False" />
|
||||
<Setter Property="HeadersVisibility" Value="All" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="Foreground" Value="#343A40" />
|
||||
<Setter Property="BorderBrush" Value="#D2D2D2" />
|
||||
<Setter Property="VerticalGridLinesBrush" Value="#D2D2D2" />
|
||||
<Setter Property="HorizontalGridLinesBrush" Value="#D2D2D2" />
|
||||
<Setter Property="RowDetailsVisibilityMode" Value="VisibleWhenSelected" />
|
||||
<Setter Property="ScrollViewer.CanContentScroll" Value="true" />
|
||||
<Setter Property="ScrollViewer.PanningMode" Value="Both" />
|
||||
<Setter Property="SelectionMode" Value="Single" />
|
||||
<Setter Property="FontSize" Value="{DynamicResource DefaultFontSize}" />
|
||||
<Setter Property="FontFamily" Value="{DynamicResource FontFamily}" />
|
||||
<Setter Property="RowHeaderStyle" Value="{DynamicResource DefaultRowHeaderStyle}" />
|
||||
<Setter Property="RowStyle" Value="{DynamicResource DefaultRowStyle}" />
|
||||
<Setter Property="ColumnHeaderStyle" Value="{DynamicResource DefaultCellHeaderStyle}" />
|
||||
<Setter Property="CellStyle" Value="{DynamicResource DefaultCellStyle}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type DataGrid}">
|
||||
<Border
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
SnapsToDevicePixels="True"
|
||||
UseLayoutRounding="True">
|
||||
<ScrollViewer x:Name="DG_ScrollViewer" Focusable="false">
|
||||
<ScrollViewer.Template>
|
||||
<ControlTemplate TargetType="{x:Type ScrollViewer}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Width="{Binding CellsPanelHorizontalOffset, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
|
||||
Background="#eeeeee"
|
||||
BorderBrush="{Binding BorderBrush, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
|
||||
BorderThickness="1,0,1,1">
|
||||
<CheckBox
|
||||
Width="32"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalContentAlignment="Center"
|
||||
AutomationProperties.Name="全选行"
|
||||
Command="{x:Static ext:StyledDataGrid.ToggleSelectAllCommand}"
|
||||
CommandTarget="{Binding RelativeSource={RelativeSource AncestorType={x:Type ext:StyledDataGrid}}}"
|
||||
IsChecked="{Binding AreAllRowsSelected, RelativeSource={RelativeSource AncestorType={x:Type ext:StyledDataGrid}}, Mode=OneWay}">
|
||||
<CheckBox.Style>
|
||||
<Style BasedOn="{StaticResource {x:Type CheckBox}}" TargetType="CheckBox">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
<Style.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding ShowCheckBox, RelativeSource={RelativeSource AncestorType={x:Type ext:StyledDataGrid}}}" Value="True" />
|
||||
<Condition Binding="{Binding ShowSelectAll, RelativeSource={RelativeSource AncestorType={x:Type ext:StyledDataGrid}}}" Value="True" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Visibility" Value="Visible" />
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</CheckBox.Style>
|
||||
</CheckBox>
|
||||
</Border>
|
||||
|
||||
<DataGridColumnHeadersPresenter
|
||||
x:Name="PART_ColumnHeadersPresenter"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.Column}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
|
||||
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2">
|
||||
<ScrollContentPresenter x:Name="PART_ScrollContentPresenter" CanContentScroll="{TemplateBinding CanContentScroll}" />
|
||||
<Grid
|
||||
x:Name="NoDataTipsGrid"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsHitTestVisible="False"
|
||||
Visibility="Collapsed">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="60" />
|
||||
<RowDefinition Height="auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Path
|
||||
Width="100"
|
||||
Height="60"
|
||||
Data="M156.662278 699.758173h21.097186A10.444152 10.444152 0 0 1 187.994733 710.202325c0 5.765172-4.490985 10.444152-10.235269 10.444152H156.662278v21.097186A10.444152 10.444152 0 0 1 146.218126 751.978932a10.277045 10.277045 0 0 1-10.444152-10.235269V720.646477H114.676787A10.444152 10.444152 0 0 1 104.441518 710.202325c0-5.765172 4.490985-10.444152 10.235269-10.444152H135.773974v-21.097187A10.444152 10.444152 0 0 1 146.218126 668.425717c5.765172 0 10.444152 4.490985 10.444152 10.235269v21.097187z m1378.628042-83.553215v-21.097186A10.277045 10.277045 0 0 0 1524.846168 584.872503a10.444152 10.444152 0 0 0-10.444152 10.235269v21.097186h-21.097186a10.277045 10.277045 0 0 0-10.235269 10.444152c0 5.598065 4.595427 10.444152 10.235269 10.444152h21.097186v21.097187c0 5.744284 4.67898 10.235269 10.444152 10.235268a10.444152 10.444152 0 0 0 10.444152-10.235268V637.093262h21.097187c5.744284 0 10.235269-4.67898 10.235268-10.444152a10.444152 10.444152 0 0 0-10.235268-10.444152H1535.29032zM776.460024 960.861969H250.596979A20.80475 20.80475 0 0 1 229.77134 939.973665c0-11.530344 9.462402-20.888304 20.825639-20.888303h94.728457A83.010119 83.010119 0 0 1 334.212859 877.413196v-605.96969A83.49055 83.49055 0 0 1 417.849627 187.994733H480.430984V167.001988A83.49055 83.49055 0 0 1 564.067752 83.553215h501.152182A83.448773 83.448773 0 0 1 1148.856702 167.001988v605.969689c0 15.185797-4.052331 29.410732-11.133466 41.672166h115.554096c11.551232 0 20.909192 9.274407 20.909192 20.888304 0 11.530344-9.295295 20.888304-20.888304 20.888304H1002.638576v20.992745c0 15.185797-4.052331 29.410732-11.133466 41.672166h11.196131c11.488567 0 20.825639 9.274407 20.825639 20.888303 0 11.530344-9.462402 20.888304-20.825639 20.888304h-109.893365c9.545955 16.000441 7.478013 36.972297-6.41271 50.863019a41.672166 41.672166 0 0 1-59.072122 0L776.460024 960.861969z m76.367638-41.776607h66.424806c22.977134 0 41.609501-18.59059 41.609501-41.881049V270.461756c0-22.559368-18.047494-40.690416-40.314426-40.690416H416.303892c-22.266932 0-40.314426 18.214601-40.314426 40.690416v606.742557c0 23.123352 18.799473 41.881049 41.588613 41.881049h317.084449l-10.736588-10.757477a41.693054 41.693054 0 0 1-10.861918-40.377091l-19.718558-19.739447A146.259902 146.259902 0 0 1 502.363703 627.693525a146.218126 146.218126 0 0 1 220.517822 190.981761l19.739447 19.739447a41.630389 41.630389 0 0 1 40.377091 10.841029L852.827662 919.085362zM1002.638576 814.643843h62.852906A41.797496 41.797496 0 0 0 1107.080095 772.867236V167.106429c0-23.14424-18.632367-41.776607-41.588613-41.776607H563.775316A41.797496 41.797496 0 0 0 522.207592 167.106429v20.888304h396.794216A83.448773 83.448773 0 0 1 1002.638576 271.443506V814.643843zM266.325872 46.998683h31.123572c8.773088 0 15.875111 6.955805 15.875111 15.666228 0 8.647758-7.102023 15.666228-15.875111 15.666228h-31.123572v31.123572c0 8.773088-6.955805 15.875111-15.666228 15.875111a15.770669 15.770669 0 0 1-15.666228-15.875111V78.331139H203.869844A15.728893 15.728893 0 0 1 187.994733 62.664911c0-8.647758 7.102023-15.666228 15.875111-15.666228h31.123572V15.875111c0-8.773088 6.955805-15.875111 15.666228-15.875111 8.647758 0 15.666228 7.102023 15.666228 15.875111v31.123572zM20.888304 939.973665c0-11.530344 9.462402-20.888304 20.825638-20.888303h125.455152c11.488567 0 20.825639 9.274407 20.825639 20.888303 0 11.530344-9.462402 20.888304-20.825639 20.888304H41.713942A20.80475 20.80475 0 0 1 20.888304 939.973665z m658.733544-135.021995a104.441518 104.441518 0 1 0-147.722083-147.722083 104.441518 104.441518 0 0 0 147.722083 147.722083zM459.542681 313.324555a20.888304 20.888304 0 0 1 20.867415-20.888304H710.202325a20.888304 20.888304 0 1 1 0 41.776608H480.430984A20.825639 20.825639 0 0 1 459.542681 313.324555z m0 104.441518c0-11.530344 9.295295-20.888304 20.742085-20.888303h334.505295c11.44679 0 20.742086 9.274407 20.742086 20.888303 0 11.530344-9.295295 20.888304-20.742086 20.888304H480.284766A20.762974 20.762974 0 0 1 459.542681 417.766073z m0 104.441519c0-11.530344 9.316183-20.888304 20.846527-20.888304h146.301679c11.509455 0 20.846527 9.274407 20.846527 20.888304 0 11.530344-9.316183 20.888304-20.846527 20.888303h-146.301679A20.80475 20.80475 0 0 1 459.542681 522.207592zM62.664911 396.87777a62.664911 62.664911 0 1 1 0-125.329822 62.664911 62.664911 0 0 1 0 125.329822z m0-31.332456a31.332456 31.332456 0 1 0 0-62.664911 31.332456 31.332456 0 0 0 0 62.664911zM1357.739739 271.547948a62.664911 62.664911 0 1 1 0-125.329822 62.664911 62.664911 0 0 1 0 125.329822z m0-31.332456a31.332456 31.332456 0 1 0 0-62.664911 31.332456 31.332456 0 0 0 0 62.664911z"
|
||||
Fill="LightGray"
|
||||
Stretch="Fill" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
FontSize="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=FontSize, Mode=OneWay}"
|
||||
Foreground="LightGray"
|
||||
Text="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=(ext:DataGridHelper.NoDataTips), Mode=OneWay}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<ScrollBar
|
||||
x:Name="PART_VerticalScrollBar"
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
Maximum="{TemplateBinding ScrollableHeight}"
|
||||
Orientation="Vertical"
|
||||
Style="{DynamicResource LeadScrollBarStyle}"
|
||||
ViewportSize="{TemplateBinding ViewportHeight}"
|
||||
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
|
||||
Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
<Grid Grid.Row="2" Grid.Column="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{Binding NonFrozenColumnsViewportHorizontalOffset, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ScrollBar
|
||||
x:Name="PART_HorizontalScrollBar"
|
||||
Grid.Column="1"
|
||||
Maximum="{TemplateBinding ScrollableWidth}"
|
||||
Orientation="Horizontal"
|
||||
Style="{DynamicResource LeadScrollBarStyle}"
|
||||
ViewportSize="{TemplateBinding ViewportWidth}"
|
||||
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
|
||||
Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=(ext:DataGridHelper.IsActive)}" Value="false" />
|
||||
<Condition Binding="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=HasItems}" Value="false" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter TargetName="NoDataTipsGrid" Property="Visibility" Value="Visible" />
|
||||
</MultiDataTrigger>
|
||||
<!--<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=(ext:DataGridHelper.NoDataTips)}" Value="{x:Null}">
|
||||
<Setter TargetName="NoDataTipsGrid" Property="Visibility" Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=(ext:DataGridHelper.NoDataTips)}" Value="">
|
||||
<Setter TargetName="NoDataTipsGrid" Property="Visibility" Value="Collapsed" />
|
||||
</DataTrigger>-->
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</ScrollViewer.Template>
|
||||
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsGrouping" Value="true" />
|
||||
<Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false" />
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property="ScrollViewer.CanContentScroll" Value="false" />
|
||||
</MultiTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<FontFamily x:Key="FontFamily">fonts:GeistMono#Geist Mono, SF Mono, Menlo, Cascadia Mono, Consolas, DejaVu Sans Mono, Courier New, $Default</FontFamily>
|
||||
|
||||
<Style x:Key="StyledDefaultCellStyle" TargetType="DataGridCell">
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Padding" Value="8,0" />
|
||||
<Setter Property="FontSize" Value="{DynamicResource DefaultFontSize}" />
|
||||
<Setter Property="FontFamily" Value="{DynamicResource FontFamily}" />
|
||||
<!--<Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}, Path=Foreground, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />-->
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type DataGridCell}">
|
||||
<Grid>
|
||||
<Border
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}">
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</Border>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Foreground" Value="{Binding SelectedRowForeground, RelativeSource={RelativeSource AncestorType={x:Type local:StyledDataGrid}}}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="StyledDefaultRowStyle" TargetType="{x:Type DataGridRow}">
|
||||
<Setter Property="ContextMenu" Value="{Binding RowContextMenu, RelativeSource={RelativeSource AncestorType=local:StyledDataGrid}}" />
|
||||
<Setter Property="Tag" Value="{Binding DataContext, RelativeSource={RelativeSource AncestorType=local:StyledDataGrid}}" />
|
||||
<Setter Property="BorderThickness" Value="0,0,1,0" />
|
||||
<Setter Property="Background" Value="{x:Null}" />
|
||||
<Setter Property="FontSize" Value="{DynamicResource DefaultFontSize}" />
|
||||
<Setter Property="FontFamily" Value="{DynamicResource FontFamily}" />
|
||||
<Setter Property="BorderBrush" Value="{Binding RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}, Path=BorderBrush}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="DataGridRow">
|
||||
<Grid>
|
||||
<Border
|
||||
x:Name="border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}" />
|
||||
<SelectiveScrollingGrid>
|
||||
<SelectiveScrollingGrid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</SelectiveScrollingGrid.ColumnDefinitions>
|
||||
<SelectiveScrollingGrid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</SelectiveScrollingGrid.RowDefinitions>
|
||||
<DataGridCellsPresenter
|
||||
Grid.Column="1"
|
||||
ItemsPanel="{TemplateBinding ItemsPanel}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
<DataGridDetailsPresenter
|
||||
x:Name="DetailsPresenter"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
SelectiveScrollingGrid.SelectiveScrollingOrientation="{Binding AreRowDetailsFrozen, ConverterParameter={x:Static SelectiveScrollingOrientation.Vertical}, Converter={x:Static DataGrid.RowDetailsScrollingConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
|
||||
Visibility="{TemplateBinding DetailsVisibility}" />
|
||||
<DataGridRowHeader
|
||||
Grid.RowSpan="2"
|
||||
SelectiveScrollingGrid.SelectiveScrollingOrientation="Vertical"
|
||||
Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.Row}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
|
||||
</SelectiveScrollingGrid>
|
||||
<Grid
|
||||
x:Name="fousIcon"
|
||||
IsHitTestVisible="False"
|
||||
Opacity="0"
|
||||
Visibility="Collapsed">
|
||||
<Path
|
||||
Width="10"
|
||||
Height="10"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Data="M0,0 L0,0 50,0 50,10 10,10 10,50 0,50 0,52 z"
|
||||
Fill="#4BB2FF"
|
||||
RenderTransformOrigin="0.5,0.5"
|
||||
Stretch="Fill">
|
||||
<Path.RenderTransform>
|
||||
<TransformGroup>
|
||||
<ScaleTransform />
|
||||
<SkewTransform />
|
||||
<RotateTransform Angle="0" />
|
||||
<TranslateTransform />
|
||||
</TransformGroup>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
<Path
|
||||
Width="10"
|
||||
Height="10"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Data="M0,0 L0,0 50,0 50,10 10,10 10,50 0,50 0,52 z"
|
||||
Fill="#4BB2FF"
|
||||
RenderTransformOrigin="0.5,0.5"
|
||||
Stretch="Fill">
|
||||
<Path.RenderTransform>
|
||||
<TransformGroup>
|
||||
<ScaleTransform />
|
||||
<SkewTransform />
|
||||
<RotateTransform Angle="90" />
|
||||
<TranslateTransform />
|
||||
</TransformGroup>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
<Path
|
||||
Width="10"
|
||||
Height="10"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Data="M0,0 L0,0 50,0 50,10 10,10 10,50 0,50 0,52 z"
|
||||
Fill="#4BB2FF"
|
||||
RenderTransformOrigin="0.5,0.5"
|
||||
Stretch="Fill">
|
||||
<Path.RenderTransform>
|
||||
<TransformGroup>
|
||||
<ScaleTransform />
|
||||
<SkewTransform />
|
||||
<RotateTransform Angle="180" />
|
||||
<TranslateTransform />
|
||||
</TransformGroup>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
<Path
|
||||
Width="10"
|
||||
Height="10"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Bottom"
|
||||
Data="M0,0 L0,0 50,0 50,10 10,10 10,50 0,50 0,52 z"
|
||||
Fill="#4BB2FF"
|
||||
RenderTransformOrigin="0.5,0.5"
|
||||
Stretch="Fill">
|
||||
<Path.RenderTransform>
|
||||
<TransformGroup>
|
||||
<ScaleTransform />
|
||||
<SkewTransform />
|
||||
<RotateTransform Angle="-90" />
|
||||
<TranslateTransform />
|
||||
</TransformGroup>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="fousIcon" Property="Opacity" Value="1" />
|
||||
<Setter Property="Foreground" Value="{Binding SelectedRowForeground, RelativeSource={RelativeSource AncestorType={x:Type local:StyledDataGrid}}}" />
|
||||
<Setter TargetName="border" Property="Background" Value="{Binding SelectedRowBackground, RelativeSource={RelativeSource AncestorType={x:Type local:StyledDataGrid}}}" />
|
||||
</Trigger>
|
||||
<Trigger Property="DetailsVisibility" Value="Visible">
|
||||
<Setter TargetName="DetailsPresenter" Property="Margin" Value="0,0,1,0" />
|
||||
</Trigger>
|
||||
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="StyledRowHeaderStyle"
|
||||
BasedOn="{StaticResource DefaultRowHeaderStyle}"
|
||||
TargetType="DataGridRowHeader">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="DataGridRowHeader">
|
||||
<Border
|
||||
Background="Transparent"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="0,0,1,1">
|
||||
<Grid>
|
||||
<ContentPresenter
|
||||
x:Name="Number"
|
||||
Margin="8,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Content="{Binding Header, RelativeSource={RelativeSource AncestorType=DataGridRow}}"
|
||||
Visibility="Collapsed" />
|
||||
<CheckBox
|
||||
x:Name="RowCheckBox"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="选择行"
|
||||
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=DataGridRow}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Visibility="Collapsed" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding ShowRowNumber, RelativeSource={RelativeSource AncestorType=local:StyledDataGrid}}" Value="True">
|
||||
<Setter TargetName="Number" Property="Visibility" Value="Visible" />
|
||||
</DataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding ShowCheckBox, RelativeSource={RelativeSource AncestorType=local:StyledDataGrid}}" Value="True" />
|
||||
<Condition Binding="{Binding ShowRowNumber, RelativeSource={RelativeSource AncestorType=local:StyledDataGrid}}" Value="False" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter TargetName="Number" Property="Visibility" Value="Collapsed" />
|
||||
<Setter TargetName="RowCheckBox" Property="Visibility" Value="Visible" />
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding ShowCheckBox, RelativeSource={RelativeSource AncestorType=local:StyledDataGrid}}" Value="True" />
|
||||
<Condition Binding="{Binding IsSelectionModeActive, RelativeSource={RelativeSource AncestorType=local:StyledDataGrid}}" Value="True" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter TargetName="Number" Property="Visibility" Value="Collapsed" />
|
||||
<Setter TargetName="RowCheckBox" Property="Visibility" Value="Visible" />
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding ShowCheckBox, RelativeSource={RelativeSource AncestorType=local:StyledDataGrid}}" Value="True" />
|
||||
<Condition Binding="{Binding AreAllRowsSelected, RelativeSource={RelativeSource AncestorType=local:StyledDataGrid}}" Value="True" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter TargetName="Number" Property="Visibility" Value="Collapsed" />
|
||||
<Setter TargetName="RowCheckBox" Property="Visibility" Value="Visible" />
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding ShowCheckBox, RelativeSource={RelativeSource AncestorType=local:StyledDataGrid}}" Value="True" />
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=DataGridRow}}" Value="True" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter TargetName="Number" Property="Visibility" Value="Collapsed" />
|
||||
<Setter TargetName="RowCheckBox" Property="Visibility" Value="Visible" />
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding ShowCheckBox, RelativeSource={RelativeSource AncestorType=local:StyledDataGrid}}" Value="True" />
|
||||
<Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType=DataGridRow}}" Value="True" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter TargetName="Number" Property="Visibility" Value="Collapsed" />
|
||||
<Setter TargetName="RowCheckBox" Property="Visibility" Value="Visible" />
|
||||
</MultiDataTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="StyledNumberCellStyle"
|
||||
BasedOn="{StaticResource StyledDefaultCellStyle}"
|
||||
TargetType="DataGridCell">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right" />
|
||||
</Style>
|
||||
|
||||
<Style BasedOn="{StaticResource DefaultDataGridStyle}" TargetType="local:StyledDataGrid">
|
||||
<Setter Property="EnableRowVirtualization" Value="True" />
|
||||
<Setter Property="EnableColumnVirtualization" Value="True" />
|
||||
<Setter Property="VirtualizingPanel.IsVirtualizing" Value="True" />
|
||||
<Setter Property="VirtualizingPanel.VirtualizationMode" Value="Recycling" />
|
||||
<Setter Property="ScrollViewer.CanContentScroll" Value="True" />
|
||||
|
||||
<Setter Property="SelectionMode" Value="Extended" />
|
||||
<Setter Property="SelectionUnit" Value="FullRow" />
|
||||
<Setter Property="RowStyle" Value="{StaticResource StyledDefaultRowStyle}" />
|
||||
<Setter Property="CellStyle" Value="{StaticResource StyledDefaultCellStyle}" />
|
||||
<Setter Property="RowHeaderStyle" Value="{StaticResource StyledRowHeaderStyle}" />
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,169 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Media;
|
||||
using System.Windows;
|
||||
|
||||
namespace Main.Test;
|
||||
|
||||
/// <summary>配套图片列:自动使用所属 StyledDataGrid 的拖动暂停和图片缓存能力。</summary>
|
||||
public class StyledDataGridImageColumn : DataGridBoundColumn
|
||||
{
|
||||
private static readonly Style DefaultImageStyle = CreateDefaultStyle();
|
||||
/// <summary>可选的逐行占位图;未设置时显示空白。</summary>
|
||||
public BindingBase? PlaceholderBinding { get; set; }
|
||||
|
||||
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
|
||||
{
|
||||
var image = new NetworkThumbnail { Style = ElementStyle ?? DefaultImageStyle };
|
||||
if (Binding != null) BindingOperations.SetBinding(image, NetworkThumbnail.UrlProperty, Binding);
|
||||
if (PlaceholderBinding != null)
|
||||
BindingOperations.SetBinding(image, NetworkThumbnail.PlaceholderProperty, PlaceholderBinding);
|
||||
return image;
|
||||
}
|
||||
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem) => GenerateElement(cell, dataItem);
|
||||
private static Style CreateDefaultStyle()
|
||||
{
|
||||
var style = new Style(typeof(NetworkThumbnail));
|
||||
style.Setters.Add(new Setter(FrameworkElement.WidthProperty, 58d));
|
||||
style.Setters.Add(new Setter(FrameworkElement.HeightProperty, 58d));
|
||||
style.Setters.Add(new Setter(System.Windows.Controls.Image.StretchProperty, Stretch.Uniform));
|
||||
style.Seal(); return style;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>仅为已生成的可见单元格加载图片;回收时取消,避免旧图片覆盖新行。</summary>
|
||||
public sealed class NetworkThumbnail : Image
|
||||
{
|
||||
public static readonly DependencyProperty CacheEnabledProperty = DependencyProperty.RegisterAttached("CacheEnabled", typeof(bool), typeof(NetworkThumbnail), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.Inherits, Changed));
|
||||
public static bool GetCacheEnabled(DependencyObject target) => (bool)target.GetValue(CacheEnabledProperty);
|
||||
public static void SetCacheEnabled(DependencyObject target, bool value) => target.SetValue(CacheEnabledProperty, value);
|
||||
|
||||
public static readonly DependencyProperty IsLoadingSuspendedProperty = DependencyProperty.RegisterAttached(
|
||||
"IsLoadingSuspended", typeof(bool), typeof(NetworkThumbnail),
|
||||
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits, SuspensionChanged));
|
||||
public static bool GetIsLoadingSuspended(DependencyObject target) => (bool)target.GetValue(IsLoadingSuspendedProperty);
|
||||
public static void SetIsLoadingSuspended(DependencyObject target, bool value) => target.SetValue(IsLoadingSuspendedProperty, value);
|
||||
|
||||
private static void SuspensionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is not NetworkThumbnail image) return;
|
||||
image.CancelLoading();
|
||||
image.Source = null;
|
||||
if (!(bool)e.NewValue) image.QueueRefresh();
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty UrlProperty = DependencyProperty.Register(nameof(Url), typeof(string), typeof(NetworkThumbnail), new PropertyMetadata(null, Changed));
|
||||
public static readonly DependencyProperty PlaceholderProperty = DependencyProperty.Register(nameof(Placeholder), typeof(ImageSource), typeof(NetworkThumbnail), new PropertyMetadata(null, Changed));
|
||||
public string? Url { get => (string?)GetValue(UrlProperty); set => SetValue(UrlProperty, value); }
|
||||
public ImageSource? Placeholder { get => (ImageSource?)GetValue(PlaceholderProperty); set => SetValue(PlaceholderProperty, value); }
|
||||
private CancellationTokenSource? loading;
|
||||
private bool refreshPending;
|
||||
|
||||
public NetworkThumbnail()
|
||||
{
|
||||
Loaded += (_, _) => QueueRefresh();
|
||||
Unloaded += (_, _) => CancelLoading();
|
||||
}
|
||||
private static void Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is not NetworkThumbnail image) return;
|
||||
image.CancelLoading();
|
||||
image.Source = GetIsLoadingSuspended(image) ? null : image.Placeholder;
|
||||
// 回收行时多个绑定会依次变更,等本轮绑定完成后只加载一次新 URL。
|
||||
image.QueueRefresh();
|
||||
}
|
||||
private void QueueRefresh()
|
||||
{
|
||||
if (!IsLoaded || refreshPending) return;
|
||||
refreshPending = true;
|
||||
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Loaded, new Action(() =>
|
||||
{
|
||||
refreshPending = false;
|
||||
if (IsLoaded) StartLoading();
|
||||
}));
|
||||
}
|
||||
private void CancelLoading()
|
||||
{
|
||||
loading?.Cancel(); loading?.Dispose(); loading = null;
|
||||
}
|
||||
private async void StartLoading()
|
||||
{
|
||||
CancelLoading(); ToolTip = null;
|
||||
if (GetIsLoadingSuspended(this)) { Source = null; return; }
|
||||
Source = Placeholder;
|
||||
if (string.IsNullOrWhiteSpace(Url)) return;
|
||||
var request = new CancellationTokenSource(); loading = request;
|
||||
var token = request.Token;
|
||||
try
|
||||
{
|
||||
var bitmap = await ThumbnailLoader.LoadAsync(Url, GetCacheEnabled(this), token);
|
||||
if (!token.IsCancellationRequested && ReferenceEquals(loading, request)) Source = bitmap;
|
||||
}
|
||||
catch (OperationCanceledException) when (token.IsCancellationRequested) { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!token.IsCancellationRequested && ReferenceEquals(loading, request)) ToolTip = $"图片加载失败:{ex.Message}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class ThumbnailLoader
|
||||
{
|
||||
private static readonly HttpClient Client = new() { Timeout = TimeSpan.FromSeconds(12) };
|
||||
private static readonly SemaphoreSlim Slots = new(4);
|
||||
private static readonly object CacheLock = new();
|
||||
private static readonly Dictionary<string, BitmapSource> Cache = new();
|
||||
private static readonly Queue<string> CacheOrder = new();
|
||||
private static long started, succeeded, failed, canceled, cacheHits, bytes, totalTicks;
|
||||
private static int active, pending;
|
||||
public static int Pending => Volatile.Read(ref pending);
|
||||
public static string Statistics => $"网络图片:请求 {Interlocked.Read(ref started)} / 成功 {Interlocked.Read(ref succeeded)} / 失败 {Interlocked.Read(ref failed)} / 取消 {Interlocked.Read(ref canceled)} / 缓存命中 {Interlocked.Read(ref cacheHits)} | 活跃 {Volatile.Read(ref active)},待完成 {Pending} | 下载 {Interlocked.Read(ref bytes) / 1024d:N0} KB | 成功平均 {AverageMilliseconds:N0} ms";
|
||||
private static double AverageMilliseconds => Interlocked.Read(ref succeeded) == 0 ? 0 : Interlocked.Read(ref totalTicks) * 1000d / Stopwatch.Frequency / Interlocked.Read(ref succeeded);
|
||||
|
||||
public static async Task<BitmapSource> LoadAsync(string url, bool cacheEnabled, CancellationToken token)
|
||||
{
|
||||
var cacheKey = url;
|
||||
lock (CacheLock)
|
||||
if (cacheEnabled && Cache.TryGetValue(cacheKey, out var cached)) { Interlocked.Increment(ref cacheHits); return cached; }
|
||||
Interlocked.Increment(ref pending);
|
||||
var acquired = false;
|
||||
try
|
||||
{
|
||||
await Slots.WaitAsync(token).ConfigureAwait(false); acquired = true;
|
||||
Interlocked.Increment(ref active); Interlocked.Increment(ref started);
|
||||
var watch = Stopwatch.StartNew();
|
||||
var data = await Client.GetByteArrayAsync(url, token).ConfigureAwait(false);
|
||||
Interlocked.Add(ref bytes, data.Length);
|
||||
var bitmap = await Task.Run(() =>
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
using var stream = new MemoryStream(data);
|
||||
var image = new BitmapImage(); image.BeginInit(); image.CacheOption = BitmapCacheOption.OnLoad;
|
||||
image.DecodePixelWidth = 72; image.StreamSource = stream; image.EndInit(); image.Freeze();
|
||||
return image;
|
||||
}, token).ConfigureAwait(false);
|
||||
token.ThrowIfCancellationRequested();
|
||||
lock (CacheLock)
|
||||
{
|
||||
if (cacheEnabled && !Cache.ContainsKey(cacheKey))
|
||||
{
|
||||
if (Cache.Count >= 128) Cache.Remove(CacheOrder.Dequeue());
|
||||
Cache.Add(cacheKey, bitmap); CacheOrder.Enqueue(cacheKey);
|
||||
}
|
||||
}
|
||||
Interlocked.Increment(ref succeeded); Interlocked.Add(ref totalTicks, watch.ElapsedTicks);
|
||||
return bitmap;
|
||||
}
|
||||
catch (OperationCanceledException) when (token.IsCancellationRequested) { Interlocked.Increment(ref canceled); throw; }
|
||||
catch { Interlocked.Increment(ref failed); throw; }
|
||||
finally
|
||||
{
|
||||
if (acquired) { Interlocked.Decrement(ref active); Slots.Release(); }
|
||||
Interlocked.Decrement(ref pending);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Main.Test.Views;
|
||||
using Prism.Commands;
|
||||
using Prism.Services.Dialogs;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Main.Test.ViewModels;
|
||||
|
||||
internal partial class HeaderViewModel
|
||||
{
|
||||
private readonly IDialogService _dialogService;
|
||||
|
||||
public HeaderViewModel(IDialogService dialogService)
|
||||
{
|
||||
_dialogService = dialogService;
|
||||
}
|
||||
//public ICommand ccCommand => new AsyncDelegateCommand(cc);
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
private async Task cc()
|
||||
{
|
||||
_dialogService.ShowDialog(nameof(bbbDialogView));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Windows;
|
||||
using MAX.Core.Logger;
|
||||
using Prism.Commands;
|
||||
using Prism.Mvvm;
|
||||
using Trade.Framework.Services;
|
||||
|
||||
namespace Main.Test.ViewModels;
|
||||
|
||||
internal class MainViewModel : BindableBase
|
||||
{
|
||||
private List<ItemInfo> data = DemoData.Create(10000);
|
||||
public List<ItemInfo> Data { get => data; private set => SetProperty(ref data, value); }
|
||||
private string actionMessage = "可右键查看详情、复制编码或切换状态;默认加载 10,000 行。";
|
||||
public string ActionMessage { get => actionMessage; private set => SetProperty(ref actionMessage, value); }
|
||||
private ObservableCollection<ItemInfo> selectedItems = new();
|
||||
public ObservableCollection<ItemInfo> SelectedItems
|
||||
{
|
||||
get => selectedItems;
|
||||
set
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
if (ReferenceEquals(selectedItems, value)) return;
|
||||
selectedItems.CollectionChanged -= SelectionChanged;
|
||||
SetProperty(ref selectedItems, value);
|
||||
selectedItems.CollectionChanged += SelectionChanged;
|
||||
RaisePropertyChanged(nameof(SelectedCount));
|
||||
}
|
||||
}
|
||||
public int SelectedCount => SelectedItems.Count;
|
||||
public int NetworkImageSize { get; set; }
|
||||
public DelegateCommand<ItemInfo> DetailCommand { get; }
|
||||
public DelegateCommand<ItemInfo> ConfirmCommand { get; }
|
||||
public DelegateCommand<ItemInfo> CopyCodeCommand { get; }
|
||||
|
||||
public MainViewModel(ILoggerFacade logger, UserManager userManager)
|
||||
{
|
||||
selectedItems.CollectionChanged += SelectionChanged;
|
||||
DetailCommand = new(item => { if (item != null) ActionMessage = $"详情:{item.Code} · {item.Name} · ¥{item.Value} · {item.Buyer} · {item.Status}"; });
|
||||
ConfirmCommand = new(item =>
|
||||
{
|
||||
if (item == null) return;
|
||||
item.Status = item.Status == "已确认" ? "待处理" : "已确认";
|
||||
ActionMessage = $"已更新 {item.Code}:{item.Status}";
|
||||
});
|
||||
CopyCodeCommand = new(item =>
|
||||
{
|
||||
if (item == null) return;
|
||||
try { Clipboard.SetText(item.Code.ToString()); ActionMessage = $"已复制编码 {item.Code}"; }
|
||||
catch (System.Runtime.InteropServices.ExternalException) { ActionMessage = "剪贴板正忙,请重试。"; }
|
||||
});
|
||||
}
|
||||
private void SelectionChanged(object? sender, NotifyCollectionChangedEventArgs e) => RaisePropertyChanged(nameof(SelectedCount));
|
||||
|
||||
public async Task LoadAsync(int count)
|
||||
{
|
||||
if (count is < 1 or > 50000) throw new ArgumentOutOfRangeException(nameof(count));
|
||||
var imageSize = NetworkImageSize;
|
||||
var rows = await Task.Run(() => DemoData.Create(count, imageSize));
|
||||
SelectedItems.Clear(); Data = rows;
|
||||
ActionMessage = $"已加载 {count:N0} 行;" + (imageSize == 0 ? "本地缩略图。" : $"Picsum {imageSize}×{imageSize} 网络随机图,可见时加载。每次重新加载使用新种子。冷 / 热测试以当前页面已加载状态为准。");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<UserControl
|
||||
x:Class="Main.Test.Views.HeaderView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Main.Test.Views"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:tf="Trade.Framework"
|
||||
xmlns:vm="clr-namespace:Main.Test.ViewModels"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800" DataContext="{tf:DISource Type=vm:HeaderViewModel}"
|
||||
mc:Ignorable="d">
|
||||
<Grid>
|
||||
<Button Command="{Binding ccCommand}" Content="abc" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,27 @@
|
||||
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 Main.Test.Views;
|
||||
|
||||
/// <summary>
|
||||
/// HeaderView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class HeaderView : UserControl
|
||||
{
|
||||
public HeaderView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<UserControl x:Class="Main.Test.Views.bbbDialogView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Main.Test.Views"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,57 @@
|
||||
using Prism.Services.Dialogs;
|
||||
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;
|
||||
using Trade.Framework.Services;
|
||||
|
||||
namespace Main.Test.Views;
|
||||
|
||||
/// <summary>
|
||||
/// bbbDialogView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class bbbDialogView : UserControl
|
||||
{
|
||||
public bbbDialogView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
public class bbbDialogViewModel : IDialogAware
|
||||
{
|
||||
private readonly IUserManager _user;
|
||||
|
||||
public bbbDialogViewModel(IUserManager user)
|
||||
{
|
||||
_user = user;
|
||||
}
|
||||
public string Title => "ttt";
|
||||
|
||||
public event Action<IDialogResult> RequestClose;
|
||||
|
||||
public bool CanCloseDialog()
|
||||
{
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OnDialogClosed()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
public void OnDialogOpened(IDialogParameters parameters)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace MAX.Core.Autofac
|
||||
{
|
||||
/// <summary>
|
||||
/// 自动填充属性特性
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class AutowiredAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Autofac.Core;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MAX.Core.Autofac
|
||||
{
|
||||
/// <summary>
|
||||
/// autofac 找出所有属性上是否摸个特性
|
||||
/// </summary>
|
||||
public class CustomPropertySelector : IPropertySelector
|
||||
{
|
||||
public bool InjectProperty(PropertyInfo propertyInfo, object instance)
|
||||
{
|
||||
if (propertyInfo.CustomAttributes.Any(iterator => iterator.AttributeType == typeof(AutowiredAttribute)))
|
||||
{
|
||||
|
||||
}
|
||||
//需要一个判断
|
||||
//;如果标记的有CustomPropertyAttribute特性返回True:返回true;就构造实例
|
||||
return propertyInfo.CustomAttributes.Any(iterator => iterator.AttributeType == typeof(AutowiredAttribute));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Autofac;
|
||||
|
||||
namespace MAX.Core.Autofac
|
||||
{
|
||||
public interface IDependencyRegistrar
|
||||
{
|
||||
void Register(ContainerBuilder builder);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using Autofac;
|
||||
using Autofac.Core;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MAX.Core.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// 容器
|
||||
/// </summary>
|
||||
public static partial class IocManager
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认容器
|
||||
/// </summary>
|
||||
public static IContainer Container { get; internal set; }
|
||||
public static void SetContainer(IContainer container)
|
||||
{
|
||||
Container = container;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确定指定的服务在上下文中是否可用。
|
||||
/// </summary>
|
||||
/// <typeparam name="TService"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static bool IsRegistered<TService>()
|
||||
{
|
||||
return Container.IsRegistered<TService>();
|
||||
}
|
||||
/// <summary>
|
||||
/// 确定指定的服务在上下文中是否可用。
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsRegistered(Type serverType)
|
||||
{
|
||||
return Container.IsRegistered(serverType);
|
||||
}
|
||||
|
||||
public static TService Resolve<TService>()
|
||||
{
|
||||
if (Container.IsRegistered<TService>())
|
||||
{
|
||||
return Container.Resolve<TService>();
|
||||
}
|
||||
return default;
|
||||
}
|
||||
public static object Resolve(Type serviceType)
|
||||
{
|
||||
if (Container.IsRegistered(serviceType))
|
||||
{
|
||||
return Container.Resolve(serviceType);
|
||||
}
|
||||
return default;
|
||||
}
|
||||
public static TService Resolve<TService>(params Parameter[] parameters)
|
||||
{
|
||||
if (Container.IsRegistered<TService>())
|
||||
{
|
||||
return Container.Resolve<TService>(parameters);
|
||||
}
|
||||
return default;
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取所有注册
|
||||
/// </summary>
|
||||
/// <typeparam name="TService"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static TService[] ResolveAll<TService>()
|
||||
{
|
||||
return ResolveAll(typeof(TService)) as TService[];
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取所有注册
|
||||
/// </summary>
|
||||
/// <typeparam name="TService"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static object[] ResolveAll(Type type)
|
||||
{
|
||||
Type enumerableOfType = typeof(IEnumerable<>).MakeGenericType(type);
|
||||
return (object[])Container.ResolveService(new TypedService(enumerableOfType));
|
||||
}
|
||||
|
||||
public static Type GetRegistrationType(string key)
|
||||
{
|
||||
foreach (IComponentRegistration registration in Container.ComponentRegistry.Registrations)
|
||||
{
|
||||
foreach (Service service in registration.Services)
|
||||
{
|
||||
if (service is KeyedService ks)
|
||||
{
|
||||
if (key.Equals(ks.ServiceKey.ToString(), StringComparison.Ordinal))
|
||||
{
|
||||
if (Container.ComponentRegistry.TryGetServiceRegistration(service, out ServiceRegistration serviceRegistration))
|
||||
{
|
||||
return serviceRegistration.Registration.Target.Activator.LimitType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
|
||||
namespace MAX.Core.Caches;
|
||||
|
||||
/// <summary>
|
||||
/// 缓存
|
||||
/// </summary>
|
||||
public interface ICache
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否存在指定键的缓存
|
||||
/// </summary>
|
||||
/// <param name="key">缓存键</param>
|
||||
bool Exists(string key);
|
||||
/// <summary>
|
||||
/// 从缓存中获取数据
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
T Get<T>(string key);
|
||||
/// <summary>
|
||||
/// 从缓存中获取数据,如果不存在,则执行获取数据操作并添加到缓存中
|
||||
/// </summary>
|
||||
/// <typeparam name="T">缓存数据类型</typeparam>
|
||||
/// <param name="key">缓存键</param>
|
||||
/// <param name="func">获取数据操作</param>
|
||||
T Get<T>(string key, Func<T> func);
|
||||
/// <summary>
|
||||
/// 当缓存数据不存在则添加,已存在不会添加,添加成功返回true
|
||||
/// </summary>
|
||||
/// <typeparam name="T">缓存数据类型</typeparam>
|
||||
/// <param name="key">缓存键</param>
|
||||
/// <param name="value">值</param>
|
||||
bool TryAdd<T>(string key, T value);
|
||||
/// <summary>
|
||||
/// 覆盖添加
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
bool OverlayAdd<T>(string key, T value);
|
||||
/// <summary>
|
||||
/// 移除缓存
|
||||
/// </summary>
|
||||
/// <param name="key">缓存键</param>
|
||||
void Remove(string key);
|
||||
/// <summary>
|
||||
/// 清空缓存
|
||||
/// </summary>
|
||||
void Clear();
|
||||
}
|
||||
|
||||
public interface ICache<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否存在指定键的缓存
|
||||
/// </summary>
|
||||
/// <param name="key">缓存键</param>
|
||||
bool Exists(string key);
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存中获取数据,如果不存在,则执行获取数据操作并添加到缓存中
|
||||
/// </summary>
|
||||
/// <typeparam name="T">缓存数据类型</typeparam>
|
||||
/// <param name="key">缓存键</param>
|
||||
T Get(string key);
|
||||
/// <summary>
|
||||
/// 从缓存中获取数据,如果不存在,则执行获取数据操作并添加到缓存中
|
||||
/// </summary>
|
||||
/// <typeparam name="T">缓存数据类型</typeparam>
|
||||
/// <param name="key">缓存键</param>
|
||||
/// <param name="func">获取数据操作</param>
|
||||
T Get(string key, Func<T> func);
|
||||
/// <summary>
|
||||
/// 当缓存数据不存在则添加,已存在不会添加,添加成功返回true
|
||||
/// </summary>
|
||||
/// <typeparam name="T">缓存数据类型</typeparam>
|
||||
/// <param name="key">缓存键</param>
|
||||
/// <param name="value">值</param>
|
||||
bool TryAdd(string key, T value);
|
||||
/// <summary>
|
||||
/// 覆盖添加
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
bool OverlayAdd(string key, T value);
|
||||
/// <summary>
|
||||
/// 移除缓存
|
||||
/// </summary>
|
||||
/// <param name="key">缓存键</param>
|
||||
void Remove(string key);
|
||||
/// <summary>
|
||||
/// 清空缓存
|
||||
/// </summary>
|
||||
void Clear();
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
|
||||
using MAX.Core.DependencyInjection;
|
||||
using MAX.Core.Infrastructure;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
|
||||
namespace MAX.Core.Caches;
|
||||
|
||||
/// <summary>
|
||||
/// 内存缓存
|
||||
/// </summary>
|
||||
public class MemoryCache : ICache, ISingletonDependency
|
||||
{
|
||||
//private static readonly Object _locker = new object();
|
||||
private readonly ConcurrentDictionary<string, object> _cache = new();
|
||||
|
||||
public virtual void Clear()
|
||||
{
|
||||
_cache.Clear();
|
||||
}
|
||||
|
||||
public virtual bool Exists(string key)
|
||||
{
|
||||
return _cache.ContainsKey(key);
|
||||
}
|
||||
public virtual T Get<T>(string key)
|
||||
{
|
||||
if (Exists(key))
|
||||
{
|
||||
return (T)_cache[key];
|
||||
}
|
||||
return default;
|
||||
}
|
||||
public virtual TResult Get<TResult>(string key, Func<TResult> func)
|
||||
{
|
||||
if (Exists(key))
|
||||
{
|
||||
return (TResult)_cache[key];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (func != null)
|
||||
{
|
||||
TResult t = func();
|
||||
TryAdd(key, t);
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public virtual bool OverlayAdd<T>(string key, T value)
|
||||
{
|
||||
if (Exists(key))
|
||||
{
|
||||
_cache[key] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.TryAdd(key, value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual void Remove(string key)
|
||||
{
|
||||
_cache.TryRemove(key, out _);
|
||||
}
|
||||
|
||||
public virtual bool TryAdd<T>(string key, T value)
|
||||
{
|
||||
_cache.AddOrUpdate(key, value, (oldkey, oldvalue) => value);
|
||||
return true;
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取所有Key
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string[] AllKeys()
|
||||
{
|
||||
return _cache.Keys.ToArray();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 单例缓存抽象类
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">单例对象</typeparam>
|
||||
/// <typeparam name="TValue">缓存类型</typeparam>
|
||||
public abstract class SingleInstanceLittleCache<TModel, TValue> : SingleInstance<TModel>, ICache<TValue> where TModel : class
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, TValue> _cache = new();
|
||||
/// <summary>
|
||||
/// 自定义前缀
|
||||
/// </summary>
|
||||
//public abstract string KeyPrefix { get; }
|
||||
//private string FormatKey(string key)
|
||||
//{
|
||||
// return KeyPrefix + key;
|
||||
//}
|
||||
|
||||
public virtual void Clear()
|
||||
{
|
||||
_cache.Clear();
|
||||
}
|
||||
|
||||
public virtual bool Exists(string key)
|
||||
{
|
||||
return _cache.ContainsKey(key);
|
||||
}
|
||||
public virtual TValue Get(string key)
|
||||
{
|
||||
if (Exists(key))
|
||||
{
|
||||
return _cache[key];
|
||||
}
|
||||
return default;
|
||||
}
|
||||
public virtual TValue Get(string key, Func<TValue> func)
|
||||
{
|
||||
if (Exists(key))
|
||||
{
|
||||
return _cache[key];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (func != null)
|
||||
{
|
||||
TValue t = func();
|
||||
TryAdd(key, t);
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public virtual bool OverlayAdd(string key, TValue value)
|
||||
{
|
||||
if (Exists(key))
|
||||
{
|
||||
_cache[key] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache.TryAdd(key, value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual void Remove(string key)
|
||||
{
|
||||
_cache.TryRemove(key, out _);
|
||||
}
|
||||
|
||||
public virtual bool TryAdd(string key, TValue value)
|
||||
{
|
||||
_cache.AddOrUpdate(key, value, (oldkey, oldvalue) => value);
|
||||
return true;
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取所有Key
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string[] AllKeys()
|
||||
{
|
||||
return _cache.Keys.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
namespace MAX.Core.Commons;
|
||||
|
||||
/// <summary>
|
||||
/// 公共Value
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
public class CommonValue<TValue>
|
||||
{
|
||||
public CommonValue() { }
|
||||
public CommonValue(TValue value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
public TValue Value { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 公共TextValue
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
public class CommonTextValue<TValue> : CommonValue<TValue>
|
||||
{
|
||||
public string Text { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 公共TextValue
|
||||
/// </summary>
|
||||
public class CommonTextValue : CommonTextValue<string>
|
||||
{
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 默认公共键值对 Key Value
|
||||
/// </summary>
|
||||
public class CommonKeyValue : CommonValue<string>
|
||||
{
|
||||
public string Key { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
|
||||
namespace MAX.Core.Commons;
|
||||
|
||||
/// <summary>
|
||||
/// 通用返回
|
||||
/// </summary>
|
||||
public class Result
|
||||
{
|
||||
/// <summary>
|
||||
/// 返回结果是否成功
|
||||
/// </summary>
|
||||
public bool Success { get; set; } = false;
|
||||
/// <summary>
|
||||
/// 返回消息
|
||||
/// </summary>
|
||||
public string Msg { get; set; } = "";
|
||||
/// <summary>
|
||||
/// 返回内容
|
||||
/// </summary>
|
||||
public object Data { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// 返回状态码
|
||||
/// </summary>
|
||||
public int Code { get; set; }
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 通用返回 指定Data类型
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">数据类型</typeparam>
|
||||
public class Result<TData> : Result
|
||||
{
|
||||
/// <summary>
|
||||
/// 返回内容
|
||||
/// </summary>
|
||||
public new TData Data { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 通用返回
|
||||
/// </summary>
|
||||
/// <typeparam name="TStatus">状态类型枚举</typeparam>
|
||||
/// <typeparam name="TData">数据类型</typeparam>
|
||||
public class Result<TStatus, TData> : Result where TStatus : Enum
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
public TStatus Status { get; set; }
|
||||
/// <summary>
|
||||
/// 返回内容
|
||||
/// </summary>
|
||||
public new TData Data { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace MAX.Core.Cryptography;
|
||||
|
||||
public class Base64Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Base64加密
|
||||
/// </summary>
|
||||
/// <param name="codeName">加密采用的编码方式</param>
|
||||
/// <param name="source">待加密的明文</param>
|
||||
/// <returns></returns>
|
||||
public static string Encode(string source, Encoding encode)
|
||||
{
|
||||
string encodeValue = "";
|
||||
byte[] bytes = encode.GetBytes(source);
|
||||
try
|
||||
{
|
||||
encodeValue = Convert.ToBase64String(bytes);
|
||||
}
|
||||
catch
|
||||
{
|
||||
encodeValue = source;
|
||||
}
|
||||
return encodeValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base64加密,采用utf8编码方式加密
|
||||
/// </summary>
|
||||
/// <param name="source">待加密的明文</param>
|
||||
/// <returns>加密后的字符串</returns>
|
||||
public static string Encode(string source)
|
||||
{
|
||||
return Encode(source, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base64解密
|
||||
/// </summary>
|
||||
/// <param name="codeName">解密采用的编码方式,注意和加密时采用的方式一致</param>
|
||||
/// <param name="result">待解密的密文</param>
|
||||
/// <returns>解密后的字符串</returns>
|
||||
public static string Decode(string result, Encoding encode)
|
||||
{
|
||||
string decode = "";
|
||||
byte[] bytes = Convert.FromBase64String(result);
|
||||
try
|
||||
{
|
||||
decode = encode.GetString(bytes);
|
||||
}
|
||||
catch
|
||||
{
|
||||
decode = result;
|
||||
}
|
||||
return decode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base64解密,采用utf8编码方式解密
|
||||
/// </summary>
|
||||
/// <param name="result">待解密的密文</param>
|
||||
/// <returns>解密后的字符串</returns>
|
||||
public static string Decode(string result)
|
||||
{
|
||||
return Decode(result, Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace MAX.Core.Cryptography;
|
||||
|
||||
public sealed class DESHelper
|
||||
{
|
||||
|
||||
//默认密钥向量
|
||||
private static readonly byte[] IVKey = { 36, 109, 97, 120, 111, 110, 120, 36 };
|
||||
/// <summary>
|
||||
/// DES加密字符串
|
||||
/// </summary>
|
||||
/// <param name="encryptString">待加密的字符串</param>
|
||||
/// <param name="encryptKey">加密密钥,要求为8位</param>
|
||||
/// <returns>加密成功返回加密后的字符串,失败返回源串</returns>
|
||||
public static string Encrypt(string encryptString, string encryptKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
encryptKey = GetSubString(encryptKey, 8, "");
|
||||
encryptKey = encryptKey.PadRight(8, ' ');
|
||||
byte[] rgbKey = Encoding.UTF8.GetBytes(encryptKey.Substring(0, 8));
|
||||
byte[] rgbIV = IVKey;
|
||||
byte[] inputByteArray = Encoding.UTF8.GetBytes(encryptString);
|
||||
DES dCSP = DES.Create();
|
||||
MemoryStream mStream = new();
|
||||
CryptoStream cStream = new(mStream, dCSP.CreateEncryptor(rgbKey, rgbIV), CryptoStreamMode.Write);
|
||||
cStream.Write(inputByteArray, 0, inputByteArray.Length);
|
||||
cStream.FlushFinalBlock();
|
||||
return Convert.ToBase64String(mStream.ToArray());
|
||||
}
|
||||
catch
|
||||
{
|
||||
return encryptString;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DES解密字符串
|
||||
/// </summary>
|
||||
/// <param name="decryptString">待解密的字符串</param>
|
||||
/// <param name="decryptKey">解密密钥,要求为8位,和加密密钥相同</param>
|
||||
/// <returns>解密成功返回解密后的字符串,失败返源串</returns>
|
||||
public static string Decrypt(string decryptString, string decryptKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
decryptKey = GetSubString(decryptKey, 0, 8, "");
|
||||
decryptKey = decryptKey.PadRight(8, ' ');
|
||||
byte[] rgbKey = Encoding.UTF8.GetBytes(decryptKey);
|
||||
byte[] rgbIV = IVKey;
|
||||
byte[] inputByteArray = Convert.FromBase64String(decryptString);
|
||||
DES DCSP = DES.Create();
|
||||
|
||||
MemoryStream mStream = new();
|
||||
CryptoStream cStream = new(mStream, DCSP.CreateDecryptor(rgbKey, rgbIV), CryptoStreamMode.Write);
|
||||
cStream.Write(inputByteArray, 0, inputByteArray.Length);
|
||||
cStream.FlushFinalBlock();
|
||||
return Encoding.UTF8.GetString(mStream.ToArray());
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#region 截取字符串函数
|
||||
|
||||
/// <summary>
|
||||
/// 截取字符串函数
|
||||
/// </summary>
|
||||
/// <param name="Str">所要截取的字符串</param>
|
||||
/// <param name="Num">截取字符串的长度</param>
|
||||
/// <returns></returns>
|
||||
public static string GetSubString(string Str, int Num)
|
||||
{
|
||||
if (Str is null or "")
|
||||
{
|
||||
return "";
|
||||
}
|
||||
string outstr = string.Empty;
|
||||
int n = 0;
|
||||
foreach (char ch in Str)
|
||||
{
|
||||
n += Encoding.Default.GetByteCount(ch.ToString());
|
||||
|
||||
if (n > Num)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
outstr += ch;
|
||||
}
|
||||
}
|
||||
|
||||
return outstr;
|
||||
}
|
||||
/// <summary>
|
||||
/// 截取字符串函数,如果超过指定长度则将超出的部分用指定字符串代替
|
||||
/// </summary>
|
||||
/// <param name="Str">所要截取的字符串</param>
|
||||
/// <param name="Num">截取字符串的长度</param>
|
||||
/// <param name="LastStr">截取字符串后省略部分的字符串</param>
|
||||
/// <returns></returns>
|
||||
public static string GetSubString(string Str, int Num, string LastStr)
|
||||
{
|
||||
return GetSubString(Str, 0, Num, LastStr);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 截取字符串,超过部分用指定字符串替换
|
||||
|
||||
/* 宗波于 2010-6-12 日添加, 来源于DiscuzNt */
|
||||
/// <summary>
|
||||
/// 取指定长度的字符串
|
||||
/// </summary>
|
||||
/// <param name="p_SrcString">要检查的字符串</param>
|
||||
/// <param name="p_StartIndex">起始位置</param>
|
||||
/// <param name="p_Length">指定长度</param>
|
||||
/// <param name="p_TailString">用于替换的字符串</param>
|
||||
/// <returns>截取后的字符串</returns>
|
||||
public static string GetSubString(string p_SrcString, int p_StartIndex, int p_Length, string p_TailString)
|
||||
{
|
||||
string myResult = p_SrcString;
|
||||
|
||||
byte[] bComments = Encoding.UTF8.GetBytes(p_SrcString);
|
||||
foreach (char c in Encoding.UTF8.GetChars(bComments))
|
||||
{ //当是日文或韩文时(注:中文的范围:\u4e00 - \u9fa5, 日文在\u0800 - \u4e00, 韩文为\xAC00-\xD7A3)
|
||||
if (c is (> '\u0800' and < '\u4e00') or (> '\xAC00' and < '\xD7A3'))
|
||||
{
|
||||
//if (System.Text.RegularExpressions.Regex.IsMatch(p_SrcString, "[\u0800-\u4e00]+") || System.Text.RegularExpressions.Regex.IsMatch(p_SrcString, "[\xAC00-\xD7A3]+"))
|
||||
//当截取的起始位置超出字段串长度时
|
||||
if (p_StartIndex >= p_SrcString.Length)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
return p_SrcString.Substring(p_StartIndex,
|
||||
p_Length + p_StartIndex > p_SrcString.Length ? p_SrcString.Length - p_StartIndex : p_Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (p_Length >= 0)
|
||||
{
|
||||
byte[] bsSrcString = Encoding.Default.GetBytes(p_SrcString);
|
||||
|
||||
//当字符串长度大于起始位置
|
||||
if (bsSrcString.Length > p_StartIndex)
|
||||
{
|
||||
int p_EndIndex = bsSrcString.Length;
|
||||
|
||||
//当要截取的长度在字符串的有效长度范围内
|
||||
if (bsSrcString.Length > p_StartIndex + p_Length)
|
||||
{
|
||||
p_EndIndex = p_Length + p_StartIndex;
|
||||
}
|
||||
else
|
||||
{ //当不在有效范围内时,只取到字符串的结尾
|
||||
|
||||
p_Length = bsSrcString.Length - p_StartIndex;
|
||||
p_TailString = "";
|
||||
}
|
||||
|
||||
int nRealLength = p_Length;
|
||||
int[] anResultFlag = new int[p_Length];
|
||||
byte[] bsResult = null;
|
||||
|
||||
int nFlag = 0;
|
||||
for (int i = p_StartIndex; i < p_EndIndex; i++)
|
||||
{
|
||||
if (bsSrcString[i] > 127)
|
||||
{
|
||||
nFlag++;
|
||||
if (nFlag == 3)
|
||||
{
|
||||
nFlag = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nFlag = 0;
|
||||
}
|
||||
|
||||
anResultFlag[i] = nFlag;
|
||||
}
|
||||
|
||||
if (bsSrcString[p_EndIndex - 1] > 127 && anResultFlag[p_Length - 1] == 1)
|
||||
{
|
||||
nRealLength = p_Length + 1;
|
||||
}
|
||||
|
||||
bsResult = new byte[nRealLength];
|
||||
|
||||
Array.Copy(bsSrcString, p_StartIndex, bsResult, 0, nRealLength);
|
||||
|
||||
myResult = Encoding.Default.GetString(bsResult);
|
||||
myResult += p_TailString;
|
||||
}
|
||||
}
|
||||
|
||||
return myResult;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace MAX.Core.Cryptography;
|
||||
|
||||
public class MD5Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// MD5加密
|
||||
/// </summary>
|
||||
/// <param name="instring"></param>
|
||||
/// <returns></returns>
|
||||
public static string Md5_32(string instring)
|
||||
{
|
||||
return _Md532(instring);
|
||||
}
|
||||
public static string Md5_Lower32(string instring)
|
||||
{
|
||||
return _Md532(instring, true);
|
||||
}
|
||||
public static string _Md532(string instring, bool IsLower = false)
|
||||
{
|
||||
byte[] result = Encoding.UTF8.GetBytes(string.IsNullOrEmpty(instring) ? "" : instring.Trim());
|
||||
MD5 md5 = MD5.Create();
|
||||
byte[] output = md5.ComputeHash(result);
|
||||
string value = BitConverter.ToString(output).Replace("-", "");
|
||||
if (IsLower)
|
||||
{
|
||||
return value.ToLower();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// MD5加密
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static long GetLong(byte[] result)
|
||||
{
|
||||
return
|
||||
Convert.ToInt64(
|
||||
BitConverter.ToString(MD5.Create().ComputeHash(result), 4,
|
||||
8).Replace("-", ""), 16);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MD5 16位加密转long
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <returns></returns>
|
||||
public static long GetLong(string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Convert.ToInt64(BitConverter.ToString(MD5.Create().ComputeHash(Encoding.Default.GetBytes(url)), 4, 8).Replace("-", ""), 16);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Text;
|
||||
|
||||
namespace MAX.Core.Cryptography;
|
||||
|
||||
/// <summary>
|
||||
/// Url 加密 解密
|
||||
/// </summary>
|
||||
public class UrlHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Url加密
|
||||
/// </summary>
|
||||
/// <param name="codeName">加密采用的编码方式</param>
|
||||
/// <param name="source">待加密的明文</param>
|
||||
/// <returns></returns>
|
||||
public static string Encode(string source, Encoding encode)
|
||||
{
|
||||
return System.Web.HttpUtility.UrlEncode(source, encode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Url加密,采用utf8编码方式加密
|
||||
/// </summary>
|
||||
/// <param name="source">待加密的明文</param>
|
||||
/// <returns>加密后的字符串</returns>
|
||||
public static string Encode(string source)
|
||||
{
|
||||
return Encode(source, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Url解密
|
||||
/// </summary>
|
||||
/// <param name="codeName">解密采用的编码方式,注意和加密时采用的方式一致</param>
|
||||
/// <param name="result">待解密的密文</param>
|
||||
/// <returns>解密后的字符串</returns>
|
||||
public static string Decode(string result, Encoding encode)
|
||||
{
|
||||
return System.Web.HttpUtility.UrlDecode(result, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Url解密,采用utf8编码方式解密
|
||||
/// </summary>
|
||||
/// <param name="result">待解密的密文</param>
|
||||
/// <returns>解密后的字符串</returns>
|
||||
public static string Decode(string result)
|
||||
{
|
||||
return Decode(result, Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace MAX.Core.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// 作用域(每个 Scope/请求一个实例)
|
||||
/// </summary>
|
||||
public interface IScopedDependency
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
//using System;
|
||||
|
||||
//namespace MAX.Core.DependencyInjection;
|
||||
|
||||
///// <summary>
|
||||
///// 依赖注册抽象(由 MAX.Core 自己定义,不依赖任何第三方 DI 容器)。
|
||||
///// 具体实现由宿主项目提供,内部适配外部容器(Autofac、Microsoft.Extensions.DependencyInjection 等)。
|
||||
///// </summary>
|
||||
//public interface IServiceRegistrar
|
||||
//{
|
||||
// /// <summary>
|
||||
// /// 注册单例:<typeparamref name="TImplementation"/> 实现 <typeparamref name="TService"/>
|
||||
// /// </summary>
|
||||
// void RegisterSingleton<TService, TImplementation>() where TService : class where TImplementation : class, TService;
|
||||
|
||||
// /// <summary>
|
||||
// /// 注册单例实例
|
||||
// /// </summary>
|
||||
// void RegisterSingleton<TService>(TService instance) where TService : class;
|
||||
|
||||
// /// <summary>
|
||||
// /// 注册单例(匿名工厂)
|
||||
// /// </summary>
|
||||
// void RegisterSingleton<TService>(Func<IServiceProvider, TService> factory) where TService : class;
|
||||
|
||||
// /// <summary>
|
||||
// /// 注册单例(key):<typeparamref name="TImplementation"/> 实现 <typeparamref name="TService"/>
|
||||
// /// </summary>
|
||||
// void RegisterSingleton<TService, TImplementation>(object? serviceKey) where TService : class where TImplementation : class, TService;
|
||||
|
||||
// /// <summary>
|
||||
// /// 注册单例实例(key)
|
||||
// /// </summary>
|
||||
// void RegisterSingleton<TService>(object? serviceKey, TService instance) where TService : class;
|
||||
|
||||
// /// <summary>
|
||||
// /// 注册单例(key + 匿名工厂)
|
||||
// /// </summary>
|
||||
// void RegisterSingleton<TService>(object? serviceKey, Func<IServiceProvider, object?, TService> factory) where TService : class;
|
||||
|
||||
// /// <summary>
|
||||
// /// 注册作用域生命周期
|
||||
// /// </summary>
|
||||
// void RegisterScoped<TService, TImplementation>() where TService : class where TImplementation : class, TService;
|
||||
|
||||
// /// <summary>
|
||||
// /// 注册作用域生命周期(匿名工厂)
|
||||
// /// </summary>
|
||||
// void RegisterScoped<TService>(Func<IServiceProvider, TService> factory) where TService : class;
|
||||
|
||||
// /// <summary>
|
||||
// /// 注册作用域生命周期(key):<typeparamref name="TImplementation"/> 实现 <typeparamref name="TService"/>
|
||||
// /// </summary>
|
||||
// void RegisterScoped<TService, TImplementation>(object? serviceKey) where TService : class where TImplementation : class, TService;
|
||||
|
||||
// /// <summary>
|
||||
// /// 注册作用域生命周期(key + 匿名工厂)
|
||||
// /// </summary>
|
||||
// void RegisterScoped<TService>(object? serviceKey, Func<IServiceProvider, object?, TService> factory) where TService : class;
|
||||
|
||||
// /// <summary>
|
||||
// /// 注册瞬时生命周期
|
||||
// /// </summary>
|
||||
// void RegisterTransient<TService, TImplementation>() where TService : class where TImplementation : class, TService;
|
||||
|
||||
// /// <summary>
|
||||
// /// 注册瞬时生命周期(匿名工厂)
|
||||
// /// </summary>
|
||||
// void RegisterTransient<TService>(Func<IServiceProvider, TService> factory) where TService : class;
|
||||
|
||||
// /// <summary>
|
||||
// /// 注册瞬时生命周期(key):<typeparamref name="TImplementation"/> 实现 <typeparamref name="TService"/>
|
||||
// /// </summary>
|
||||
// void RegisterTransient<TService, TImplementation>(object? serviceKey) where TService : class where TImplementation : class, TService;
|
||||
|
||||
// /// <summary>
|
||||
// /// 注册瞬时生命周期(key + 匿名工厂)
|
||||
// /// </summary>
|
||||
// void RegisterTransient<TService>(object? serviceKey, Func<IServiceProvider, object?, TService> factory) where TService : class;
|
||||
|
||||
//}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace MAX.Core.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// 单例
|
||||
/// </summary>
|
||||
public interface ISingletonDependency
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace MAX.Core.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// 每次实例化
|
||||
/// </summary>
|
||||
public interface ITransientDependency
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using MAX.Core.Infrastructure;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace MAX.Core.DependencyInjection;
|
||||
|
||||
public interface ILazyServiceProvider
|
||||
{
|
||||
object LazyGetService(Type serviceType);
|
||||
T LazyGetService<T>();
|
||||
}
|
||||
|
||||
public class LazyServiceProvider : SingleInstance<LazyServiceProvider>, ILazyServiceProvider
|
||||
{
|
||||
protected ConcurrentDictionary<Type, Lazy<object>> CachedServices { get; }
|
||||
private LazyServiceProvider()
|
||||
{
|
||||
CachedServices = new ConcurrentDictionary<Type, Lazy<object>>();
|
||||
}
|
||||
public virtual T LazyGetService<T>()
|
||||
{
|
||||
return (T)LazyGetService(typeof(T));
|
||||
}
|
||||
|
||||
public virtual object LazyGetService(Type serviceType)
|
||||
{
|
||||
return GetService(serviceType);
|
||||
}
|
||||
public virtual object GetService(Type serviceType)
|
||||
{
|
||||
return CachedServices.GetOrAdd(
|
||||
serviceType,
|
||||
_ => new Lazy<object>(() => IocManager.Resolve(serviceType))
|
||||
).Value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
using MAX.Core.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MAX.Core.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 系统扩展 - 类型转换
|
||||
/// </summary>
|
||||
public static partial class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// IsNullOrEmpty
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsNullOrEmpty(this string input)
|
||||
{
|
||||
return string.IsNullOrEmpty(input);
|
||||
}
|
||||
/// <summary>
|
||||
/// 安全转换为字符串,去除两端空格,当值为null时返回""
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static string SafeString(this object input)
|
||||
{
|
||||
return input?.ToString().Trim() ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为bool
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static bool ToBool(this string obj, bool defvalue = false)
|
||||
{
|
||||
return Helpers.Convert.ToBool(obj, defvalue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为可空bool
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static bool? ToBoolOrNull(this string obj)
|
||||
{
|
||||
return Helpers.Convert.ToBoolOrNull(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为int
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static int ToInt(this string obj, int defvalue = 0)
|
||||
{
|
||||
return Helpers.Convert.ToInt(obj, defvalue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为可空int
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static int? ToIntOrNull(this string obj)
|
||||
{
|
||||
return Helpers.Convert.ToIntOrNull(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为long
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static long ToLong(this string obj, long defvalue = 0)
|
||||
{
|
||||
return Helpers.Convert.ToLong(obj, defvalue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为可空long
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static long? ToLongOrNull(this string obj)
|
||||
{
|
||||
return Helpers.Convert.ToLongOrNull(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为double
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static double ToDouble(this string obj, double defvalue = 0)
|
||||
{
|
||||
return Helpers.Convert.ToDouble(obj, defvalue: defvalue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为可空double
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static double? ToDoubleOrNull(this string obj)
|
||||
{
|
||||
return Helpers.Convert.ToDoubleOrNull(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为decimal
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static decimal ToDecimal(this string obj, decimal defvalue = 0)
|
||||
{
|
||||
return Helpers.Convert.ToDecimal(obj, defvalue: defvalue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为可空decimal
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static decimal? ToDecimalOrNull(this string obj)
|
||||
{
|
||||
return Helpers.Convert.ToDecimalOrNull(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为日期
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static DateTime ToDate(this string obj)
|
||||
{
|
||||
return Helpers.Convert.ToDate(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为可空日期
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static DateTime? ToDateOrNull(this string obj)
|
||||
{
|
||||
return Helpers.Convert.ToDateOrNull(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为Guid
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static Guid ToGuid(this string obj)
|
||||
{
|
||||
return Helpers.Convert.ToGuid(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为可空Guid
|
||||
/// </summary>
|
||||
/// <param name="obj">数据</param>
|
||||
public static Guid? ToGuidOrNull(this string obj)
|
||||
{
|
||||
return Helpers.Convert.ToGuidOrNull(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为Guid集合
|
||||
/// </summary>
|
||||
/// <param name="obj">数据,范例: "83B0233C-A24F-49FD-8083-1337209EBC9A,EAB523C6-2FE7-47BE-89D5-C6D440C3033A"</param>
|
||||
public static List<Guid> ToGuidList(this string obj)
|
||||
{
|
||||
return Helpers.Convert.ToGuidList(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为Guid集合
|
||||
/// </summary>
|
||||
/// <param name="obj">字符串集合</param>
|
||||
public static List<Guid> ToGuidList(this IList<string> obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return obj.Select(t => t.ToGuid()).ToList();
|
||||
}
|
||||
/// <summary>
|
||||
/// 转换枚举描述
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToDescription(this Enum obj)
|
||||
{
|
||||
return EnumHelper.GetDescription(obj.GetType(), obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 枚举值 → 单个 EnumItem
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum"></typeparam>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static EnumItem<TEnum> ToItem<TEnum>(this TEnum value) where TEnum : struct, Enum
|
||||
{
|
||||
return new EnumItem<TEnum>(value);
|
||||
}
|
||||
/// <summary>
|
||||
/// 是否单一标记
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsSingleFlag(Enum obj)
|
||||
{
|
||||
return EnumHelper.IsSingleFlag(obj);
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取第一个标记
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static Enum GetFirstFlag(this Enum obj)
|
||||
{
|
||||
return EnumHelper.GetFirstFlag(obj);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using MAX.Core.Cryptography;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MAX.Core.Extensions;
|
||||
|
||||
public static class DictionaryExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 字典转url Get 参数
|
||||
/// </summary>
|
||||
/// <param name="parameters"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToUrlParams(this IDictionary<string, string> parameters, bool IsUrlEncode = true)
|
||||
{
|
||||
IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters);
|
||||
IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator();
|
||||
StringBuilder sb = new("");
|
||||
while (dem.MoveNext())
|
||||
{
|
||||
string key = dem.Current.Key;
|
||||
string value = dem.Current.Value;
|
||||
if (!string.IsNullOrEmpty(key))
|
||||
{
|
||||
sb.Append(key).Append("=").Append(IsUrlEncode ? UrlHelper.Encode(value) : value).Append("&");
|
||||
|
||||
}
|
||||
}
|
||||
string content = sb.ToString().Substring(0, sb.Length - 1);
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
|
||||
namespace MAX.Core.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// 常用公共操作
|
||||
/// </summary>
|
||||
public static partial class Common
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取类型
|
||||
/// </summary>
|
||||
/// <typeparam name="T">类型</typeparam>
|
||||
public static Type GetType<T>()
|
||||
{
|
||||
return GetType(typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取类型
|
||||
/// </summary>
|
||||
/// <param name="type">类型</param>
|
||||
public static Type GetType(Type type)
|
||||
{
|
||||
return Nullable.GetUnderlyingType(type) ?? type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 换行符
|
||||
/// </summary>
|
||||
public static string Line => Environment.NewLine;
|
||||
|
||||
|
||||
#if NET5_0
|
||||
/// <summary>
|
||||
/// 是否Linux操作系统
|
||||
/// </summary>
|
||||
public static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||
|
||||
/// <summary>
|
||||
/// 是否Windows操作系统
|
||||
/// </summary>
|
||||
public static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
|
||||
/// <summary>
|
||||
/// 是否苹果操作系统
|
||||
/// </summary>
|
||||
public static bool IsOsx => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
||||
|
||||
/// <summary>
|
||||
/// 当前操作系统
|
||||
/// </summary>
|
||||
public static string System => IsWindows ? "Windows" : IsLinux ? "Linux" : IsOsx ? "OSX" : string.Empty;
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
|
||||
namespace MAX.Core.Helpers;
|
||||
|
||||
public class CommonItem : IComparable<CommonItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化
|
||||
/// </summary>
|
||||
/// <param name="text">文本</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <param name="sortId">排序号</param>
|
||||
/// <param name="group">组</param>
|
||||
/// <param name="disabled">禁用</param>
|
||||
public CommonItem(string text, object value, string des = "", int? sortId = null, string group = null, bool? disabled = null)
|
||||
{
|
||||
Text = text;
|
||||
Value = value;
|
||||
Description = des;
|
||||
SortId = sortId;
|
||||
Group = group;
|
||||
Disabled = disabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文本
|
||||
/// </summary>
|
||||
public string Text { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 值
|
||||
/// </summary>
|
||||
public object Value { get; protected set; }
|
||||
/// <summary>
|
||||
/// 描述
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>
|
||||
/// 排序号
|
||||
/// </summary>
|
||||
public int? SortId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 组
|
||||
/// </summary>
|
||||
public string Group { get; }
|
||||
/// <summary>
|
||||
/// 是否选中
|
||||
/// </summary>
|
||||
public bool IsSelected { get; set; }
|
||||
/// <summary>
|
||||
/// 禁用
|
||||
/// </summary>
|
||||
public bool? Disabled { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 比较
|
||||
/// </summary>
|
||||
/// <param name="other">其它列表项</param>
|
||||
public int CompareTo(CommonItem other)
|
||||
{
|
||||
return string.Compare(Text, other.Text, StringComparison.CurrentCulture);
|
||||
}
|
||||
}
|
||||
|
||||
public class CommonItem<T> : CommonItem
|
||||
{
|
||||
public new T Value
|
||||
{
|
||||
get { return (T)base.Value; }
|
||||
set { base.Value = value; }
|
||||
}
|
||||
public CommonItem(string text, T value, string des = "", int? sortId = null, string group = null, bool? disabled = null) : base(text, value, des, sortId, group, disabled)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
using MAX.Core.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace MAX.Core.Helpers;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field)]
|
||||
public class ConvertAttribute : Attribute
|
||||
{
|
||||
public Type ConverterType { get; set; }
|
||||
public ConvertAttribute(Type type)
|
||||
{
|
||||
ConverterType = type;
|
||||
}
|
||||
}
|
||||
public interface IConverter
|
||||
{
|
||||
object Converter(object input);
|
||||
}
|
||||
/// <summary>
|
||||
/// 类型转换
|
||||
/// </summary>
|
||||
public static partial class Convert
|
||||
{
|
||||
/// <summary>
|
||||
/// 转换为32位整型
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static int ToInt(object input, int defvalue = 0)
|
||||
{
|
||||
return ToIntOrNull(input) ?? defvalue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为32位可空整型
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static int? ToIntOrNull(object input)
|
||||
{
|
||||
bool success = int.TryParse(input.SafeString(), out int result);
|
||||
if (success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
double? temp = ToDoubleOrNull(input, 0);
|
||||
if (temp == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return System.Convert.ToInt32(temp);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为64位整型
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static long ToLong(object input, long defvalue = 0)
|
||||
{
|
||||
return ToLongOrNull(input) ?? defvalue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为64位可空整型
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static long? ToLongOrNull(object input)
|
||||
{
|
||||
bool success = long.TryParse(input.SafeString(), out long result);
|
||||
if (success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
decimal? temp = ToDecimalOrNull(input, 0);
|
||||
if (temp == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return System.Convert.ToInt64(temp);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为32位浮点型,并按指定小数位舍入
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
/// <param name="digits">小数位数</param>
|
||||
public static float ToFloat(object input, int? digits = null)
|
||||
{
|
||||
return ToFloatOrNull(input, digits) ?? 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为32位可空浮点型,并按指定小数位舍入
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
/// <param name="digits">小数位数</param>
|
||||
public static float? ToFloatOrNull(object input, int? digits = null)
|
||||
{
|
||||
bool success = float.TryParse(input.SafeString(), out float result);
|
||||
if (!success)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (digits == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return (float)Math.Round(result, digits.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为64位浮点型,并按指定小数位舍入
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
/// <param name="digits">小数位数</param>
|
||||
public static double ToDouble(object input, int? digits = null, double defvalue = 0)
|
||||
{
|
||||
return ToDoubleOrNull(input, digits) ?? defvalue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为64位可空浮点型,并按指定小数位舍入
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
/// <param name="digits">小数位数</param>
|
||||
public static double? ToDoubleOrNull(object input, int? digits = null)
|
||||
{
|
||||
if (input is bool b)
|
||||
{
|
||||
return b ? 1 : 0;
|
||||
}
|
||||
bool success = double.TryParse(input.SafeString(), out double result);
|
||||
if (!success)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (digits == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return Math.Round(result, digits.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为128位浮点型,并按指定小数位舍入
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
/// <param name="digits">小数位数</param>
|
||||
public static decimal ToDecimal(object input, int? digits = null, decimal defvalue = 0)
|
||||
{
|
||||
return ToDecimalOrNull(input, digits) ?? defvalue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为128位可空浮点型,并按指定小数位舍入
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
/// <param name="digits">小数位数</param>
|
||||
public static decimal? ToDecimalOrNull(object input, int? digits = null)
|
||||
{
|
||||
bool success = decimal.TryParse(input.SafeString(), out decimal result);
|
||||
if (!success)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (digits == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return Math.Round(result, digits.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为布尔值
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static bool ToBool(object input, bool defvalue = false)
|
||||
{
|
||||
return ToBoolOrNull(input) ?? defvalue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为可空布尔值
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static bool? ToBoolOrNull(object input)
|
||||
{
|
||||
bool? value = GetBool(input);
|
||||
if (value != null)
|
||||
{
|
||||
return value.Value;
|
||||
}
|
||||
|
||||
return bool.TryParse(input.SafeString(), out bool result) ? result : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取布尔值
|
||||
/// </summary>
|
||||
private static bool? GetBool(object input)
|
||||
{
|
||||
return input.SafeString().ToLower() switch
|
||||
{
|
||||
"0" => false,
|
||||
"否" => false,
|
||||
"不" => false,
|
||||
"no" => false,
|
||||
"fail" => false,
|
||||
"1" => true,
|
||||
"是" => true,
|
||||
"ok" => true,
|
||||
"yes" => true,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为日期
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static DateTime ToDate(object input)
|
||||
{
|
||||
return ToDateOrNull(input) ?? DateTime.MinValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为可空日期
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static DateTime? ToDateOrNull(object input)
|
||||
{
|
||||
return DateTime.TryParse(input.SafeString(), out DateTime result) ? result : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为Guid
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static Guid ToGuid(object input)
|
||||
{
|
||||
return ToGuidOrNull(input) ?? Guid.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为可空Guid
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static Guid? ToGuidOrNull(object input)
|
||||
{
|
||||
return Guid.TryParse(input.SafeString(), out Guid result) ? result : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为Guid集合
|
||||
/// </summary>
|
||||
/// <param name="input">以逗号分隔的Guid集合字符串,范例:83B0233C-A24F-49FD-8083-1337209EBC9A,EAB523C6-2FE7-47BE-89D5-C6D440C3033A</param>
|
||||
public static List<Guid> ToGuidList(string input)
|
||||
{
|
||||
return ToList<Guid>(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 泛型集合转换
|
||||
/// </summary>
|
||||
/// <typeparam name="T">目标元素类型</typeparam>
|
||||
/// <param name="input">以逗号分隔的元素集合字符串,范例:83B0233C-A24F-49FD-8083-1337209EBC9A,EAB523C6-2FE7-47BE-89D5-C6D440C3033A</param>
|
||||
public static List<T> ToList<T>(string input)
|
||||
{
|
||||
List<T> result = [];
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
string[] array = input.Split(',');
|
||||
result.AddRange(from each in array where !string.IsNullOrWhiteSpace(each) select To<T>(each));
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通用泛型转换
|
||||
/// </summary>
|
||||
/// <typeparam name="T">目标类型</typeparam>
|
||||
/// <param name="input">输入值</param>
|
||||
public static T To<T>(object input)
|
||||
{
|
||||
Type type = Common.GetType<T>();
|
||||
return (T)To(type, input);
|
||||
}
|
||||
public static object To(Type type, object input)
|
||||
{
|
||||
object defValue = null;
|
||||
if (type != null)
|
||||
{
|
||||
defValue = type.IsValueType ? Activator.CreateInstance(type) : null;
|
||||
}
|
||||
if (input == null)
|
||||
{
|
||||
return defValue;
|
||||
}
|
||||
|
||||
if (input is string && string.IsNullOrWhiteSpace(input.ToString()))
|
||||
{
|
||||
return defValue;
|
||||
}
|
||||
|
||||
string typeName = type.Name.ToLower();
|
||||
try
|
||||
{
|
||||
if (typeName == "string")
|
||||
{
|
||||
return input.ToString();
|
||||
}
|
||||
|
||||
if (typeName == "guid")
|
||||
{
|
||||
return new Guid(input.ToString());
|
||||
}
|
||||
|
||||
if (type.IsEnum)
|
||||
{
|
||||
return EnumHelper.Parse(type, input);
|
||||
}
|
||||
|
||||
if (typeof(IConverter).IsAssignableFrom(type))
|
||||
{
|
||||
object instance = Activator.CreateInstance(type);
|
||||
return ((IConverter)instance).Converter(input);
|
||||
}
|
||||
if (input is IConvertible)
|
||||
{
|
||||
return System.Convert.ChangeType(input, type);
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 转换为字节数组
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
public static byte[] ToBytes(string input)
|
||||
{
|
||||
return ToBytes(input, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为字节数组
|
||||
/// </summary>
|
||||
/// <param name="input">输入值</param>
|
||||
/// <param name="encoding">字符编码</param>
|
||||
public static byte[] ToBytes(string input, Encoding encoding)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(input) ? new byte[] { } : encoding.GetBytes(input);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
|
||||
namespace MAX.Core.Helpers;
|
||||
|
||||
public class DateHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Unix纪元时间
|
||||
/// </summary>
|
||||
private static readonly DateTime UnixEpoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
/// <summary>
|
||||
/// 将时间转换为秒级时间戳
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <returns></returns>
|
||||
public static long GetUnixTimestampInSeconds(DateTime dateTime)
|
||||
{
|
||||
TimeSpan timeSpan = dateTime.ToUniversalTime() - UnixEpoch;
|
||||
return (long)timeSpan.TotalSeconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将时间转换为毫秒级时间戳
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <returns></returns>
|
||||
public static long GetUnixTimestampInMilliseconds(DateTime dateTime)
|
||||
{
|
||||
TimeSpan timeSpan = dateTime.ToUniversalTime() - UnixEpoch;
|
||||
return (long)timeSpan.TotalMilliseconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将时间转换为微秒时间戳
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <returns></returns>
|
||||
public static long GetUnixTimestampInMicroseconds(DateTime dateTime)
|
||||
{
|
||||
long us = dateTime.ToUniversalTime().Ticks - UnixEpoch.Ticks;
|
||||
return us;
|
||||
}
|
||||
/// <summary>
|
||||
/// 将时间Ticks转换为毫微秒时间戳
|
||||
/// </summary>
|
||||
/// <param name="dateTime"></param>
|
||||
/// <returns></returns>
|
||||
public static long GetUnixTimestampInMicroseconds(long ticks)
|
||||
{
|
||||
long us = ticks - UnixEpoch.Ticks;
|
||||
return us;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将秒级时间戳转换为时间
|
||||
/// </summary>
|
||||
/// <param name="timestamp"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ConvertFromUnixTimestampInSeconds(long timestamp)
|
||||
{
|
||||
return UnixEpoch.AddSeconds(timestamp).ToLocalTime();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将毫秒级时间戳转换为时间
|
||||
/// </summary>
|
||||
/// <param name="timestamp"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ConvertFromUnixTimestampInMilliseconds(long timestamp)
|
||||
{
|
||||
return UnixEpoch.AddMilliseconds(timestamp).ToLocalTime();
|
||||
}
|
||||
/// <summary>
|
||||
/// 将微秒级时间戳转换为时间
|
||||
/// </summary>
|
||||
/// <param name="timestamp"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime ConvertFromUnixTimestampInMicroseconds(long timestamp)
|
||||
{
|
||||
long us = timestamp + UnixEpoch.Ticks;
|
||||
return new DateTime(us);
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取当前时间的秒级时间戳
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static long GetCurrentUnixTimestampInSeconds()
|
||||
{
|
||||
return GetUnixTimestampInSeconds(DateTime.UtcNow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前时间的毫秒级时间戳
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static long GetCurrentUnixTimestampInMilliseconds()
|
||||
{
|
||||
return GetUnixTimestampInMilliseconds(DateTime.UtcNow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前时间的微秒级时间戳
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static long GetCurrentUnixTimestampInMicroseconds()
|
||||
{
|
||||
return GetUnixTimestampInMicroseconds(DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
using MAX.Core.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MAX.Core.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// 枚举操作
|
||||
/// </summary>
|
||||
public static partial class EnumHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取实例
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举类型</typeparam>
|
||||
/// <param name="member">成员名或值,范例:Enum1枚举有成员A=0,则传入"A"或"0"获取 Enum1.A</param>
|
||||
public static TEnum Parse<TEnum>(object member)
|
||||
{
|
||||
string value = member.SafeString();
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
if (typeof(TEnum).IsGenericType)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
throw new ArgumentNullException(nameof(member));
|
||||
}
|
||||
return (TEnum)Enum.Parse(Common.GetType<TEnum>(), value, true);
|
||||
}
|
||||
public static object Parse(Type enumObj, object member)
|
||||
{
|
||||
string value = member.SafeString();
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
if (enumObj.IsGenericType)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new ArgumentNullException(nameof(member));
|
||||
}
|
||||
return Enum.Parse(enumObj, value, true);
|
||||
}
|
||||
public static TEnum ParseByDescription<TEnum>(string description)
|
||||
{
|
||||
return (TEnum)ParseByDescription(Common.GetType<TEnum>(), description);
|
||||
}
|
||||
public static object ParseByDescription(Type enumObj, string description)
|
||||
{
|
||||
List<EnumItem> listitem = GetItems(enumObj);
|
||||
foreach (EnumItem item in listitem)
|
||||
{
|
||||
if (item.Description == description)
|
||||
{
|
||||
return Enum.Parse(enumObj, item.Numeric.ToString(), true);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取成员名
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举类型</typeparam>
|
||||
/// <param name="member">成员名、值、实例均可,范例:Enum1枚举有成员A=0,则传入Enum1.A或0,获取成员名"A"</param>
|
||||
public static string GetName<TEnum>(object member)
|
||||
{
|
||||
return GetName(Common.GetType<TEnum>(), member);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取成员名
|
||||
/// </summary>
|
||||
/// <param name="type">枚举类型</param>
|
||||
/// <param name="member">成员名、值、实例均可</param>
|
||||
public static string GetName(Type type, object member)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (member == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (member is string)
|
||||
{
|
||||
return member.ToString();
|
||||
}
|
||||
|
||||
if (type.GetTypeInfo().IsEnum == false)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return Enum.GetName(type, member);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取成员值
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举类型</typeparam>
|
||||
/// <param name="member">成员名、值、实例均可,范例:Enum1枚举有成员A=0,可传入"A"、0、Enum1.A,获取值0</param>
|
||||
public static int GetValue<TEnum>(object member)
|
||||
{
|
||||
return GetValue(Common.GetType<TEnum>(), member);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取成员值
|
||||
/// </summary>
|
||||
/// <param name="type">枚举类型</param>
|
||||
/// <param name="member">成员名、值、实例均可</param>
|
||||
public static int GetValue(Type type, object member)
|
||||
{
|
||||
string value = member.SafeString();
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(member));
|
||||
}
|
||||
|
||||
return (int)Enum.Parse(type, member.ToString(), true);
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取描述,使用System.ComponentModel.Description特性设置描述
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举类型</typeparam>
|
||||
/// <param name="member">成员名、值、实例均可</param>
|
||||
public static string GetDescription<TEnum>(object member)
|
||||
{
|
||||
return ReflectionHelper.GetDescription<TEnum>(GetName<TEnum>(member));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取描述,使用System.ComponentModel.Description特性设置描述
|
||||
/// </summary>
|
||||
/// <param name="type">枚举类型</param>
|
||||
/// <param name="member">成员名、值、实例均可</param>
|
||||
public static string GetDescription(Type type, object member)
|
||||
{
|
||||
return ReflectionHelper.GetDescription(type, GetName(type, member));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取项集合,文本设置为Description,值为Value
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举类型</typeparam>
|
||||
public static List<EnumItem> GetItems<TEnum>()
|
||||
{
|
||||
return GetItems(typeof(TEnum));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取项集合,文本设置为Description,值为Value
|
||||
/// </summary>
|
||||
/// <param name="type">枚举类型</param>
|
||||
public static List<EnumItem> GetItems(Type type)
|
||||
{
|
||||
type = Common.GetType(type);
|
||||
if (type.IsEnum == false)
|
||||
{
|
||||
throw new InvalidOperationException($"类型 {type} 不是枚举");
|
||||
}
|
||||
|
||||
List<EnumItem> result = [];
|
||||
foreach (FieldInfo field in type.GetFields())
|
||||
{
|
||||
AddItem(type, result, field);
|
||||
}
|
||||
|
||||
return result.OrderBy(t => t.Numeric).ToList();
|
||||
}
|
||||
|
||||
public static CommonItem GetItem(object obj)
|
||||
{
|
||||
CommonItem item = null;
|
||||
if (obj is Enum em)
|
||||
{
|
||||
//item.Text = em.get
|
||||
string text = em.ToString();
|
||||
int value = GetValue(obj.GetType(), obj);
|
||||
string description = em.ToDescription();
|
||||
item = new CommonItem(text, value, des: description);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加描述项
|
||||
/// </summary>
|
||||
private static void AddItem(Type type, ICollection<EnumItem> result, FieldInfo field)
|
||||
{
|
||||
if (!field.FieldType.IsEnum)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string text = field.Name;
|
||||
int value = GetValue(type, field.Name);
|
||||
string description = ReflectionHelper.GetDescription(field);
|
||||
result.Add(new EnumItem(text, value, description));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取名称集合
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举类型</typeparam>
|
||||
public static List<string> GetNames<TEnum>()
|
||||
{
|
||||
return GetNames(typeof(TEnum));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取名称集合
|
||||
/// </summary>
|
||||
/// <param name="type">枚举类型</param>
|
||||
public static List<string> GetNames(Type type)
|
||||
{
|
||||
type = Common.GetType(type);
|
||||
if (type.IsEnum == false)
|
||||
{
|
||||
throw new InvalidOperationException($"类型 {type} 不是枚举");
|
||||
}
|
||||
|
||||
List<string> result = [];
|
||||
foreach (FieldInfo field in type.GetFields())
|
||||
{
|
||||
if (!field.FieldType.IsEnum)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result.Add(field.Name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取名称集合
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">枚举类型</typeparam>
|
||||
public static List<string> GetDescriptions<TEnum>()
|
||||
{
|
||||
return GetDescriptions(typeof(TEnum));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取名称集合
|
||||
/// </summary>
|
||||
/// <param name="type">枚举类型</param>
|
||||
public static List<string> GetDescriptions(Type type)
|
||||
{
|
||||
type = Common.GetType(type);
|
||||
if (type.IsEnum == false)
|
||||
{
|
||||
throw new InvalidOperationException($"类型 {type} 不是枚举");
|
||||
}
|
||||
|
||||
List<string> result = [];
|
||||
foreach (FieldInfo field in type.GetFields())
|
||||
{
|
||||
if (!field.FieldType.IsEnum)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string description = ReflectionHelper.GetDescription(field);
|
||||
result.Add(description);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/// <summary>
|
||||
/// 是否单一标记
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsSingleFlag(Enum obj)
|
||||
{
|
||||
int intValue = GetValue(obj.GetType(), obj);
|
||||
return (intValue > 0) && ((intValue & (intValue - 1)) == 0);
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取第一个标记
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static T GetFirstFlag<T>(T value) where T : Enum
|
||||
{
|
||||
long v = System.Convert.ToInt64(value);
|
||||
long first = v & -v;
|
||||
return Enum.GetValues(typeof(T)).Cast<T>().First(flag => System.Convert.ToInt64(flag) == first);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Enum展示类型
|
||||
/// </summary>
|
||||
public enum EnumDisplayType
|
||||
{
|
||||
Text,
|
||||
Value,
|
||||
Description,
|
||||
CommonItem
|
||||
}
|
||||
public class EnumItem
|
||||
{
|
||||
public string Name { get; init; }
|
||||
public int Numeric { get; init; }
|
||||
public string Description { get; init; }
|
||||
|
||||
public EnumItem(string name, int numeric, string description)
|
||||
{
|
||||
Name = name;
|
||||
Numeric = numeric;
|
||||
Description = description;
|
||||
}
|
||||
}
|
||||
public class EnumItem<TEnum> : EnumItem where TEnum : struct, Enum
|
||||
{
|
||||
public TEnum Value { get; }
|
||||
|
||||
public EnumItem(TEnum value)
|
||||
: base(value.ToString(), System.Convert.ToInt32(value), GetDescription(value))
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
private static string GetDescription(TEnum value)
|
||||
{
|
||||
var field = typeof(TEnum).GetField(value.ToString())!;
|
||||
var attr = field.GetCustomAttribute<DescriptionAttribute>();
|
||||
return attr?.Description ?? string.Empty;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MAX.Core.Helpers;
|
||||
|
||||
public class EnvironmentHelper
|
||||
{
|
||||
private static string? _currentEnvironment;
|
||||
|
||||
// 获取当前环境
|
||||
public static string CurrentEnvironment
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_currentEnvironment == null)
|
||||
{
|
||||
_currentEnvironment = DetermineEnvironment();
|
||||
}
|
||||
return _currentEnvironment;
|
||||
}
|
||||
}
|
||||
public static bool IsTest => CurrentEnvironment.Equals("Test", StringComparison.OrdinalIgnoreCase);
|
||||
public static bool IsDevelopment => CurrentEnvironment.Equals("Development", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public static bool IsProduction => CurrentEnvironment.Equals("Production", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private static string DetermineEnvironment()
|
||||
{
|
||||
#if RELEASE
|
||||
// Release 构建:只允许生产环境,不允许其他环境
|
||||
return "Production";
|
||||
#else
|
||||
// 1. 检查环境变量
|
||||
var envVar = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ??
|
||||
Environment.GetEnvironmentVariable("ENVIRONMENT");
|
||||
|
||||
if (!string.IsNullOrEmpty(envVar))
|
||||
{
|
||||
return envVar;
|
||||
}
|
||||
|
||||
|
||||
// 检查当前机器名
|
||||
string machineName = Environment.MachineName.ToLower();
|
||||
if (machineName.Contains("dev") ||
|
||||
machineName.Contains("test"))
|
||||
{
|
||||
return machineName.Contains("TEST") ? "Test" : "Development";
|
||||
}
|
||||
|
||||
// 4. 默认返回 Production
|
||||
return "Production";
|
||||
#endif
|
||||
}
|
||||
|
||||
// 获取环境描述
|
||||
public static string EnvironmentDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsTest) return "测试环境";
|
||||
if (IsDevelopment) return "开发环境";
|
||||
if (IsProduction) return "生产环境";
|
||||
return $"未知环境 ({CurrentEnvironment})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MAX.Core.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// 文件和流操作
|
||||
/// </summary>
|
||||
public static partial class FileHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 流转换为字节流
|
||||
/// </summary>
|
||||
/// <param name="stream">流</param>
|
||||
public static async Task<byte[]> ToBytesAsync(Stream stream)
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
byte[] buffer = new byte[stream.Length];
|
||||
await stream.ReadAsync(buffer, 0, buffer.Length);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 流转换为字节流
|
||||
/// </summary>
|
||||
/// <param name="stream">流</param>
|
||||
public static byte[] ToBytes(Stream stream)
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
byte[] buffer = new byte[stream.Length];
|
||||
stream.Read(buffer, 0, buffer.Length);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字符串转换成字节数组
|
||||
/// </summary>
|
||||
/// <param name="data">数据,默认字符编码utf-8</param>
|
||||
public static byte[] ToBytes(string data)
|
||||
{
|
||||
return ToBytes(data, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字符串转换成字节数组
|
||||
/// </summary>
|
||||
/// <param name="data">数据</param>
|
||||
/// <param name="encoding">字符编码</param>
|
||||
public static byte[] ToBytes(string data, Encoding encoding)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(data))
|
||||
{
|
||||
return new byte[] { };
|
||||
}
|
||||
|
||||
return encoding.GetBytes(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将文件读取到字节流中
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件的绝对路径</param>
|
||||
public static byte[] Read(string filePath)
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
FileInfo fileInfo = new(filePath);
|
||||
using BinaryReader reader = new(fileInfo.Open(FileMode.Open));
|
||||
return reader.ReadBytes((int)fileInfo.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 流转换成字符串
|
||||
/// </summary>
|
||||
/// <param name="stream">流</param>
|
||||
/// <param name="encoding">字符编码</param>
|
||||
/// <param name="bufferSize">缓冲区大小</param>
|
||||
/// <param name="isCloseStream">读取完成是否释放流,默认为true</param>
|
||||
public static string ToString(Stream stream, Encoding encoding = null, int bufferSize = 1024 * 2, bool isCloseStream = true)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
encoding ??= Encoding.UTF8;
|
||||
|
||||
if (stream.CanRead == false)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
using StreamReader reader = new(stream, encoding, true, bufferSize, !isCloseStream);
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
string result = reader.ReadToEnd();
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 流转换成字符串
|
||||
/// </summary>
|
||||
/// <param name="stream">流</param>
|
||||
/// <param name="encoding">字符编码</param>
|
||||
/// <param name="bufferSize">缓冲区大小</param>
|
||||
/// <param name="isCloseStream">读取完成是否释放流,默认为true</param>
|
||||
public static async Task<string> ToStringAsync(Stream stream, Encoding encoding = null, int bufferSize = 1024 * 2, bool isCloseStream = true)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
encoding ??= Encoding.UTF8;
|
||||
|
||||
if (stream.CanRead == false)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
using StreamReader reader = new(stream, encoding, true, bufferSize, !isCloseStream);
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
string result = await reader.ReadToEndAsync();
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复制流并转换成字符串
|
||||
/// </summary>
|
||||
/// <param name="stream">流</param>
|
||||
/// <param name="encoding">字符编码</param>
|
||||
public static async Task<string> CopyToStringAsync(Stream stream, Encoding encoding = null)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
encoding ??= Encoding.UTF8;
|
||||
|
||||
if (stream.CanRead == false)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
using MemoryStream memoryStream = new();
|
||||
using StreamReader reader = new(memoryStream, encoding);
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
stream.CopyTo(memoryStream);
|
||||
if (memoryStream.CanSeek)
|
||||
{
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
string result = await reader.ReadToEndAsync();
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,45 @@
|
||||
namespace MAX.Core.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// 标识生成器
|
||||
/// </summary>
|
||||
public static partial class IdHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 标识
|
||||
/// </summary>
|
||||
private static string _id;
|
||||
|
||||
/// <summary>
|
||||
/// 设置标识
|
||||
/// </summary>
|
||||
/// <param name="id">Id</param>
|
||||
public static void SetId(string id)
|
||||
{
|
||||
_id = id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置标识
|
||||
/// </summary>
|
||||
public static void Reset()
|
||||
{
|
||||
_id = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用Guid创建标识,去掉分隔符
|
||||
/// </summary>
|
||||
public static string Guid()
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(_id) ? System.Guid.NewGuid().ToString("N") : _id;
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
///// 获取Guid
|
||||
///// </summary>
|
||||
//public static Guid GetGuid()
|
||||
//{
|
||||
// return string.IsNullOrWhiteSpace(_id) ? System.Guid.NewGuid() : _id.ToGuid();
|
||||
//}
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace MAX.Core.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// This is a generic parameters base class used for Dialog Parameters and Navigation Parameters.
|
||||
/// </summary>
|
||||
public abstract class ParametersBase : IEnumerable<KeyValuePair<string, object>>
|
||||
{
|
||||
private readonly List<KeyValuePair<string, object>> _entries = [];
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
protected ParametersBase()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a list of parameters.
|
||||
/// </summary>
|
||||
/// <param name="query">Query string to be parsed.</param>
|
||||
protected ParametersBase(string query)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
int num = query.Length;
|
||||
for (int i = ((query.Length > 0) && (query[0] == '?')) ? 1 : 0; i < num; i++)
|
||||
{
|
||||
int startIndex = i;
|
||||
int num4 = -1;
|
||||
while (i < num)
|
||||
{
|
||||
char ch = query[i];
|
||||
if (ch == '=')
|
||||
{
|
||||
if (num4 < 0)
|
||||
{
|
||||
num4 = i;
|
||||
}
|
||||
}
|
||||
else if (ch == '&')
|
||||
{
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
string key = null;
|
||||
string value;
|
||||
if (num4 >= 0)
|
||||
{
|
||||
key = query.Substring(startIndex, num4 - startIndex);
|
||||
value = query.Substring(num4 + 1, i - num4 - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = query.Substring(startIndex, i - startIndex);
|
||||
}
|
||||
|
||||
if (key != null)
|
||||
{
|
||||
Add(Uri.UnescapeDataString(key), Uri.UnescapeDataString(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches Parameter collection and returns value if Collection contains key.
|
||||
/// Otherwise returns null.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for the value to be returned.</param>
|
||||
/// <returns>The value of the parameter referenced by the key; otherwise <c>null</c>.</returns>
|
||||
public object this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (KeyValuePair<string, object> entry in _entries)
|
||||
{
|
||||
if (string.Compare(entry.Key, key, StringComparison.Ordinal) == 0)
|
||||
{
|
||||
return entry.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The count, or number, of parameters in collection.
|
||||
/// </summary>
|
||||
public int Count => _entries.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Returns an IEnumerable of the Keys in the collection.
|
||||
/// </summary>
|
||||
public IEnumerable<string> Keys =>
|
||||
_entries.Select(x => x.Key);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the key and value to the parameters collection.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to reference this value in the parameters collection.</param>
|
||||
/// <param name="value">The value of the parameter to store.</param>
|
||||
public void Add(string key, object value) =>
|
||||
_entries.Add(new KeyValuePair<string, object>(key, value));
|
||||
|
||||
|
||||
public void Remove(string key)
|
||||
{
|
||||
_entries.RemoveAll(x => x.Key == key);
|
||||
}
|
||||
|
||||
public void Update(string key, object value)
|
||||
{
|
||||
_entries.RemoveAll(x => x.Key == key);
|
||||
Add(key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks collection for presence of key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to check in the collection.</param>
|
||||
/// <returns><c>true</c> if key exists; else returns <c>false</c>.</returns>
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
return _entries.Any(x => string.Compare(x.Key, key, StringComparison.Ordinal) == 0);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerator for the KeyValuePairs in parameter collection.
|
||||
/// </summary>
|
||||
/// <returns>Enumerator.</returns>
|
||||
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() =>
|
||||
_entries.GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value of the member referenced by key.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to be returned.</typeparam>
|
||||
/// <param name="key">The key for the value to be returned.</param>
|
||||
/// <returns>Returns a matching parameter of <typeparamref name="T"/> if one exists in the Collection.</returns>
|
||||
public T GetValue<T>(string key) =>
|
||||
_entries.GetValue<T>(key);
|
||||
|
||||
/// <summary>
|
||||
/// Returns an IEnumerable of all parameters.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type for the values to be returned.</typeparam>
|
||||
/// <param name="key">The key for the values to be returned.</param>
|
||||
///<returns>Returns a IEnumerable of all the instances of type <typeparamref name="T"/>.</returns>
|
||||
public IEnumerable<T> GetValues<T>(string key) =>
|
||||
_entries.GetValues<T>(key);
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the parameter collection contains the value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type for the values to be returned.</typeparam>
|
||||
/// <param name="key">The key for the value to be returned.</param>
|
||||
/// <param name="value">Value of the returned parameter if it exists.</param>
|
||||
public bool TryGetValue<T>(string key, out T value) =>
|
||||
_entries.TryGetValue(key, out value);
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() =>
|
||||
GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Converts parameter collection to a parameter string.
|
||||
/// </summary>
|
||||
/// <returns>A string representation of the parameters.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder queryBuilder = new();
|
||||
|
||||
if (_entries.Count > 0)
|
||||
{
|
||||
queryBuilder.Append('?');
|
||||
bool first = true;
|
||||
|
||||
foreach (KeyValuePair<string, object> kvp in _entries)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
queryBuilder.Append('&');
|
||||
}
|
||||
else
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
|
||||
queryBuilder.Append(Uri.EscapeDataString(kvp.Key));
|
||||
queryBuilder.Append('=');
|
||||
queryBuilder.Append(Uri.EscapeDataString(kvp.Value != null ? kvp.Value.ToString() : ""));
|
||||
}
|
||||
}
|
||||
|
||||
return queryBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a collection of parameters to the local parameter list.
|
||||
/// </summary>
|
||||
/// <param name="parameters">An IEnumerable of KeyValuePairs to add to the current parameter list.</param>
|
||||
public void FromParameters(IEnumerable<KeyValuePair<string, object>> parameters) =>
|
||||
_entries.AddRange(parameters);
|
||||
}
|
||||
|
||||
|
||||
public static class ParametersExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Searches <paramref name="parameters"/> for <paramref name="key"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the parameter to return</typeparam>
|
||||
/// <param name="parameters">A collection of parameters to search</param>
|
||||
/// <param name="key">The key of the parameter to find</param>
|
||||
/// <returns>A matching value of <typeparamref name="T"/> if it exists</returns>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static T GetValue<T>(this IEnumerable<KeyValuePair<string, object>> parameters, string key) =>
|
||||
(T)GetValue(parameters, key, typeof(T));
|
||||
|
||||
/// <summary>
|
||||
/// Searches <paramref name="parameters"/> for value referenced by <paramref name="key"/>
|
||||
/// </summary>
|
||||
/// <param name="parameters">A collection of parameters to search</param>
|
||||
/// <param name="key">The key of the parameter to find</param>
|
||||
/// <param name="type">The type of the parameter to return</param>
|
||||
/// <returns>A matching value of <paramref name="type"/> if it exists</returns>
|
||||
/// <exception cref="InvalidCastException">Unable to convert the value of Type</exception>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static object GetValue(this IEnumerable<KeyValuePair<string, object>> parameters, string key, Type type)
|
||||
{
|
||||
foreach (KeyValuePair<string, object> kvp in parameters)
|
||||
{
|
||||
if (string.Compare(kvp.Key, key, StringComparison.Ordinal) == 0)
|
||||
{
|
||||
if (TryGetValueInternal(kvp, type, out object value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new InvalidCastException($"Unable to convert the value of Type '{kvp.Value.GetType().FullName}' to '{type.FullName}' for the key '{key}' ");
|
||||
}
|
||||
}
|
||||
|
||||
return GetDefault(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches <paramref name="parameters"/> for value referenced by <paramref name="key"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the parameter to return</typeparam>
|
||||
/// <param name="parameters">A collection of parameters to search</param>
|
||||
/// <param name="key">The key of the parameter to find</param>
|
||||
/// <param name="value">The value of parameter to return</param>
|
||||
/// <returns>Success if value is found; otherwise returns <c>false</c></returns>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static bool TryGetValue<T>(this IEnumerable<KeyValuePair<string, object>> parameters, string key, out T value)
|
||||
{
|
||||
Type type = typeof(T);
|
||||
|
||||
foreach (KeyValuePair<string, object> kvp in parameters)
|
||||
{
|
||||
if (string.Compare(kvp.Key, key, StringComparison.Ordinal) == 0)
|
||||
{
|
||||
bool success = TryGetValueInternal(kvp, typeof(T), out object valueAsObject);
|
||||
value = (T)valueAsObject;
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches <paramref name="parameters"/> for value referenced by <paramref name="key"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the parameter to return</typeparam>
|
||||
/// <param name="parameters">A collection of parameters to search</param>
|
||||
/// <param name="key">The key of the parameter to find</param>
|
||||
/// <returns>An IEnumerable{T} of all the values referenced by key</returns>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static IEnumerable<T> GetValues<T>(this IEnumerable<KeyValuePair<string, object>> parameters, string key)
|
||||
{
|
||||
List<T> values = [];
|
||||
Type type = typeof(T);
|
||||
|
||||
foreach (KeyValuePair<string, object> kvp in parameters)
|
||||
{
|
||||
if (string.Compare(kvp.Key, key, StringComparison.Ordinal) == 0)
|
||||
{
|
||||
TryGetValueInternal(kvp, type, out object value);
|
||||
values.Add((T)value);
|
||||
}
|
||||
}
|
||||
|
||||
return values.AsEnumerable();
|
||||
}
|
||||
|
||||
private static bool TryGetValueInternal(KeyValuePair<string, object> kvp, Type type, out object value)
|
||||
{
|
||||
value = GetDefault(type);
|
||||
bool success = false;
|
||||
if (kvp.Value == null)
|
||||
{
|
||||
success = true;
|
||||
}
|
||||
else if (kvp.Value.GetType() == type)
|
||||
{
|
||||
success = true;
|
||||
value = kvp.Value;
|
||||
}
|
||||
else if (type.IsAssignableFrom(kvp.Value.GetType()))
|
||||
{
|
||||
success = true;
|
||||
value = kvp.Value;
|
||||
}
|
||||
else if (type.IsEnum)
|
||||
{
|
||||
string valueAsString = kvp.Value.ToString();
|
||||
if (Enum.IsDefined(type, valueAsString))
|
||||
{
|
||||
success = true;
|
||||
value = Enum.Parse(type, valueAsString);
|
||||
}
|
||||
else if (int.TryParse(valueAsString, out int numericValue))
|
||||
{
|
||||
success = true;
|
||||
value = Enum.ToObject(type, numericValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (!success && type.GetInterface("System.IConvertible") != null)
|
||||
{
|
||||
success = true;
|
||||
value = System.Convert.ChangeType(kvp.Value, type);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if key exists in parameter collection
|
||||
/// </summary>
|
||||
/// <param name="parameters">IEnumerable to search</param>
|
||||
/// <param name="key">The key to search the <paramref name="parameters"/> for existence</param>
|
||||
/// <returns><c>true</c> if key exists; <c>false</c> otherwise</returns>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static bool ContainsKey(this IEnumerable<KeyValuePair<string, object>> parameters, string key) =>
|
||||
parameters.Any(x => string.Compare(x.Key, key, StringComparison.Ordinal) == 0);
|
||||
|
||||
private static object GetDefault(Type type)
|
||||
{
|
||||
if (type.IsValueType)
|
||||
{
|
||||
return Activator.CreateInstance(type);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,586 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MAX.Core.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// 反射操作
|
||||
/// </summary>
|
||||
public static class ReflectionHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取类型描述,使用DescriptionAttribute设置描述
|
||||
/// </summary>
|
||||
/// <typeparam name="T">类型</typeparam>
|
||||
public static string GetDescription<T>()
|
||||
{
|
||||
return GetDescription(Common.GetType<T>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取类型成员描述,使用DescriptionAttribute设置描述
|
||||
/// </summary>
|
||||
/// <typeparam name="T">类型</typeparam>
|
||||
/// <param name="memberName">成员名称</param>
|
||||
public static string GetDescription<T>(string memberName)
|
||||
{
|
||||
return GetDescription(Common.GetType<T>(), memberName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取类型成员描述,使用DescriptionAttribute设置描述
|
||||
/// </summary>
|
||||
/// <param name="type">类型</param>
|
||||
/// <param name="memberName">成员名称</param>
|
||||
public static string GetDescription(Type type, string memberName)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(memberName))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return GetDescription(type.GetTypeInfo().GetMember(memberName).FirstOrDefault());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取类型成员描述,使用DescriptionAttribute设置描述
|
||||
/// </summary>
|
||||
/// <param name="member">成员</param>
|
||||
public static string GetDescription(MemberInfo member)
|
||||
{
|
||||
if (member == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return member.GetCustomAttribute<DescriptionAttribute>() is DescriptionAttribute attribute ? attribute.Description : member.Name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取显示名称或描述,使用DisplayNameAttribute设置显示名称,使用DescriptionAttribute设置描述
|
||||
/// </summary>
|
||||
/// <typeparam name="T">类型</typeparam>
|
||||
public static string GetDisplayNameOrDescription<T>()
|
||||
{
|
||||
return GetDisplayNameOrDescription(Common.GetType<T>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取属性显示名称或描述,使用DisplayAttribute或DisplayNameAttribute设置显示名称,使用DescriptionAttribute设置描述
|
||||
/// </summary>
|
||||
public static string GetDisplayNameOrDescription(MemberInfo member)
|
||||
{
|
||||
return GetDescription(member);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类型列表
|
||||
/// </summary>
|
||||
/// <typeparam name="TFind">查找类型</typeparam>
|
||||
/// <param name="assemblies">待查找的程序集列表</param>
|
||||
public static List<Type> FindTypes<TFind>(params Assembly[] assemblies)
|
||||
{
|
||||
Type findType = typeof(TFind);
|
||||
return FindTypes(findType, assemblies);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类型列表
|
||||
/// </summary>
|
||||
/// <param name="findType">查找类型</param>
|
||||
/// <param name="assemblies">待查找的程序集列表</param>
|
||||
public static List<Type> FindTypes(Type findType, params Assembly[] assemblies)
|
||||
{
|
||||
List<Type> result = [];
|
||||
foreach (Assembly assembly in assemblies)
|
||||
{
|
||||
result.AddRange(GetTypes(findType, assembly));
|
||||
}
|
||||
|
||||
return result.Distinct().ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取类型列表
|
||||
/// </summary>
|
||||
private static List<Type> GetTypes(Type findType, Assembly assembly)
|
||||
{
|
||||
List<Type> result = [];
|
||||
if (assembly == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
Type[] types;
|
||||
try
|
||||
{
|
||||
types = assembly.GetTypes();
|
||||
}
|
||||
catch (ReflectionTypeLoadException ex)
|
||||
{
|
||||
Debug.WriteLine(ex);
|
||||
return result;
|
||||
}
|
||||
foreach (Type type in types)
|
||||
{
|
||||
AddType(result, findType, type);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加类型
|
||||
/// </summary>
|
||||
private static void AddType(List<Type> result, Type findType, Type type)
|
||||
{
|
||||
if (type.IsInterface || type.IsAbstract)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (findType.IsAssignableFrom(type) == false && MatchGeneric(findType, type) == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
result.Add(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 泛型匹配
|
||||
/// </summary>
|
||||
private static bool MatchGeneric(Type findType, Type type)
|
||||
{
|
||||
if (findType.IsGenericTypeDefinition == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Type definition = findType.GetGenericTypeDefinition();
|
||||
foreach (Type implementedInterface in type.FindInterfaces((filter, criteria) => true, null))
|
||||
{
|
||||
if (implementedInterface.IsGenericType == false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return definition.IsAssignableFrom(implementedInterface.GetGenericTypeDefinition());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取实现了接口的所有实例
|
||||
/// </summary>
|
||||
/// <typeparam name="TInterface">接口类型</typeparam>
|
||||
/// <param name="assemblies">待查找的程序集列表</param>
|
||||
public static List<TInterface> GetInstancesByInterface<TInterface>(params Assembly[] assemblies)
|
||||
{
|
||||
return FindTypes<TInterface>(assemblies)
|
||||
.Select(t => CreateInstance<TInterface>(t)).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动态创建实例
|
||||
/// </summary>
|
||||
/// <typeparam name="T">目标类型</typeparam>
|
||||
/// <param name="type">类型</param>
|
||||
/// <param name="parameters">传递给构造函数的参数</param>
|
||||
public static T CreateInstance<T>(Type type, params object[] parameters)
|
||||
{
|
||||
return Convert.To<T>(Activator.CreateInstance(type, parameters));
|
||||
}
|
||||
|
||||
public static Type GetType(string typeName)
|
||||
{
|
||||
|
||||
Type type = null;
|
||||
|
||||
Assembly[] assemblyArray = AppDomain.CurrentDomain.GetAssemblies();
|
||||
|
||||
int assemblyArrayLength = assemblyArray.Length;
|
||||
|
||||
for (int i = 0; i < assemblyArrayLength; ++i)
|
||||
{
|
||||
|
||||
type = assemblyArray[i].GetType(typeName);
|
||||
|
||||
if (type != null)
|
||||
{
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < assemblyArrayLength; ++i)
|
||||
{
|
||||
Type[] typeArray = assemblyArray[i].GetTypes();
|
||||
int typeArrayLength = typeArray.Length;
|
||||
for (int j = 0; j < typeArrayLength; ++j)
|
||||
{
|
||||
if (typeArray[j].Name.Equals(typeName))
|
||||
{
|
||||
return typeArray[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取程序集
|
||||
/// </summary>
|
||||
/// <param name="assemblyName">程序集名称</param>
|
||||
public static Assembly GetAssembly(string assemblyName)
|
||||
{
|
||||
return Assembly.Load(new AssemblyName(assemblyName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否布尔类型
|
||||
/// </summary>
|
||||
/// <param name="member">成员</param>
|
||||
public static bool IsBool(MemberInfo member)
|
||||
{
|
||||
if (member == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return member.MemberType switch
|
||||
{
|
||||
MemberTypes.TypeInfo => member.ToString() == "System.Boolean",
|
||||
MemberTypes.Property => IsBool((PropertyInfo)member),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否布尔类型
|
||||
/// </summary>
|
||||
private static bool IsBool(PropertyInfo property)
|
||||
{
|
||||
return property.PropertyType == typeof(bool) || property.PropertyType == typeof(bool?);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否枚举类型
|
||||
/// </summary>
|
||||
/// <param name="member">成员</param>
|
||||
public static bool IsEnum(MemberInfo member)
|
||||
{
|
||||
if (member == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return member.MemberType switch
|
||||
{
|
||||
MemberTypes.TypeInfo => ((TypeInfo)member).IsEnum,
|
||||
MemberTypes.Property => IsEnum((PropertyInfo)member),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否枚举类型
|
||||
/// </summary>
|
||||
private static bool IsEnum(PropertyInfo property)
|
||||
{
|
||||
if (property.PropertyType.GetTypeInfo().IsEnum)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Type value = Nullable.GetUnderlyingType(property.PropertyType);
|
||||
if (value == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return value.GetTypeInfo().IsEnum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否日期类型
|
||||
/// </summary>
|
||||
/// <param name="member">成员</param>
|
||||
public static bool IsDate(MemberInfo member)
|
||||
{
|
||||
if (member == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return member.MemberType switch
|
||||
{
|
||||
MemberTypes.TypeInfo => member.ToString() == "System.DateTime",
|
||||
MemberTypes.Property => IsDate((PropertyInfo)member),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否日期类型
|
||||
/// </summary>
|
||||
private static bool IsDate(PropertyInfo property)
|
||||
{
|
||||
if (property.PropertyType == typeof(DateTime))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(DateTime?))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否整型
|
||||
/// </summary>
|
||||
/// <param name="member">成员</param>
|
||||
public static bool IsInt(MemberInfo member)
|
||||
{
|
||||
if (member == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return member.MemberType switch
|
||||
{
|
||||
MemberTypes.TypeInfo => member.ToString() is "System.Int32" or "System.Int16" or "System.Int64",
|
||||
MemberTypes.Property => IsInt((PropertyInfo)member),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否整型
|
||||
/// </summary>
|
||||
private static bool IsInt(PropertyInfo property)
|
||||
{
|
||||
if (property.PropertyType == typeof(int))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(int?))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(short))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(short?))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(long))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(long?))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否数值类型
|
||||
/// </summary>
|
||||
/// <param name="member">成员</param>
|
||||
public static bool IsNumber(MemberInfo member)
|
||||
{
|
||||
if (member == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsInt(member))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return member.MemberType switch
|
||||
{
|
||||
MemberTypes.TypeInfo => member.ToString() is "System.Double" or "System.Decimal" or "System.Single",
|
||||
MemberTypes.Property => IsNumber((PropertyInfo)member),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否数值类型
|
||||
/// </summary>
|
||||
private static bool IsNumber(PropertyInfo property)
|
||||
{
|
||||
if (property.PropertyType == typeof(double))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(double?))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(decimal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(decimal?))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(float))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(float?))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否集合
|
||||
/// </summary>
|
||||
/// <param name="type">类型</param>
|
||||
public static bool IsCollection(Type type)
|
||||
{
|
||||
if (type.IsArray)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return IsGenericCollection(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否泛型集合
|
||||
/// </summary>
|
||||
/// <param name="type">类型</param>
|
||||
public static bool IsGenericCollection(Type type)
|
||||
{
|
||||
if (!type.IsGenericType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Type typeDefinition = type.GetGenericTypeDefinition();
|
||||
return typeDefinition == typeof(IEnumerable<>)
|
||||
|| typeDefinition == typeof(IReadOnlyCollection<>)
|
||||
|| typeDefinition == typeof(IReadOnlyList<>)
|
||||
|| typeDefinition == typeof(ICollection<>)
|
||||
|| typeDefinition == typeof(IList<>)
|
||||
|| typeDefinition == typeof(List<>);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从目录中获取所有程序集
|
||||
/// </summary>
|
||||
/// <param name="directoryPath">目录绝对路径</param>
|
||||
public static List<Assembly> GetAssemblies(string directoryPath)
|
||||
{
|
||||
return Directory.GetFiles(directoryPath, "*.*", SearchOption.AllDirectories).ToList()
|
||||
.Where(t => t.EndsWith(".exe") || t.EndsWith(".dll"))
|
||||
.Select(path => Assembly.Load(new AssemblyName(path))).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取公共属性列表
|
||||
/// </summary>
|
||||
/// <param name="instance">实例</param>
|
||||
public static List<CommonItem> GetPublicProperties(object instance)
|
||||
{
|
||||
PropertyInfo[] properties = instance.GetType().GetProperties();
|
||||
return properties.ToList().Select(t => new CommonItem(t.Name, t.GetValue(instance))).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取顶级基类
|
||||
/// </summary>
|
||||
/// <typeparam name="T">类型</typeparam>
|
||||
public static Type GetTopBaseType<T>()
|
||||
{
|
||||
return GetTopBaseType(typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取顶级基类
|
||||
/// </summary>
|
||||
/// <param name="type">类型</param>
|
||||
public static Type GetTopBaseType(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (type.IsInterface)
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
if (type.BaseType == typeof(object))
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
return GetTopBaseType(type.BaseType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取元素类型,如果是集合,返回集合的元素类型
|
||||
/// </summary>
|
||||
/// <param name="type">类型</param>
|
||||
public static Type GetElementType(Type type)
|
||||
{
|
||||
if (IsCollection(type) == false)
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
if (type.IsArray)
|
||||
{
|
||||
return type.GetElementType();
|
||||
}
|
||||
|
||||
Type[] genericArgumentsTypes = type.GetTypeInfo().GetGenericArguments();
|
||||
if (genericArgumentsTypes == null || genericArgumentsTypes.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("泛型类型参数不能为空");
|
||||
}
|
||||
|
||||
return genericArgumentsTypes[0];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MAX.Core.Helpers;
|
||||
|
||||
public class UriHelper
|
||||
{
|
||||
#region 对外公开方法
|
||||
/// <summary>
|
||||
/// 带有前缀的域名
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetSchemeDomain(string url)
|
||||
{
|
||||
Uri uri = new(url);
|
||||
return $"{uri.Scheme}://{uri.Authority}";
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取域名
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetDomain(string url)
|
||||
{
|
||||
Uri uri = new(url);
|
||||
return uri.Host;
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取 URL
|
||||
/// </summary>
|
||||
/// <param name="url">URL 地址</param>
|
||||
/// <returns></returns>
|
||||
public static string GetUrl(string url)
|
||||
{
|
||||
Uri uri = new(url);
|
||||
return uri.AbsolutePath;
|
||||
}
|
||||
/// <summary>
|
||||
/// URL 解码
|
||||
/// </summary>
|
||||
/// <param name="url">URL 地址</param>
|
||||
/// <returns></returns>
|
||||
public static string UrlDecode(string url)
|
||||
{
|
||||
return System.Web.HttpUtility.UrlDecode(url);
|
||||
}
|
||||
/// <summary>
|
||||
/// URL 解码
|
||||
/// </summary>
|
||||
/// <param name="url">URL 地址</param>
|
||||
/// <param name="encoding">编码格式</param>
|
||||
/// <returns></returns>
|
||||
public static string UrlDecode(string url, Encoding encoding)
|
||||
{
|
||||
return System.Web.HttpUtility.UrlDecode(url, encoding);
|
||||
}
|
||||
/// <summary>
|
||||
/// URL 编码
|
||||
/// </summary>
|
||||
/// <param name="url">URL 地址</param>
|
||||
/// <returns></returns>
|
||||
public static string UrlEncode(string url)
|
||||
{
|
||||
return System.Web.HttpUtility.UrlEncode(url);
|
||||
}
|
||||
/// <summary>
|
||||
/// URL 编码
|
||||
/// </summary>
|
||||
/// <param name="url">URL 地址</param>
|
||||
/// <param name="encoding">编码格式</param>
|
||||
/// <returns></returns>
|
||||
public static string UrlEncode(string url, Encoding encoding)
|
||||
{
|
||||
return System.Web.HttpUtility.UrlEncode(url, encoding);
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取参数列表
|
||||
/// </summary>
|
||||
/// <param name="url">Url 地址</param>
|
||||
/// <returns></returns>
|
||||
public static List<string> GetQueryList(string url)
|
||||
{
|
||||
return GetQueryList(url, Encoding.UTF8);
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取参数列表
|
||||
/// </summary>
|
||||
/// <param name="url">Url 地址</param>
|
||||
/// <param name="encoding">编码格式</param>
|
||||
/// <returns></returns>
|
||||
public static List<string> GetQueryList(string url, Encoding encoding)
|
||||
{
|
||||
Uri uri = new(url);
|
||||
string query = uri.PathAndQuery;
|
||||
if (query.IndexOf(".") > 0)
|
||||
{
|
||||
query = query.Substring(0, query.IndexOf("."));
|
||||
}
|
||||
|
||||
List<string> dataList = [];
|
||||
string[] queryDataList = query.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (queryDataList != null && queryDataList.Length > 0)
|
||||
{
|
||||
foreach (string queryData in queryDataList)
|
||||
{
|
||||
dataList.Add(System.Web.HttpUtility.UrlDecode(queryData, encoding));
|
||||
}
|
||||
}
|
||||
return dataList;
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取参数字典
|
||||
/// </summary>
|
||||
/// <param name="url">Url 地址</param>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<string, string> GetQueryDict(string url)
|
||||
{
|
||||
return GetQueryDict(url, Encoding.UTF8);
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取参数字典
|
||||
/// </summary>
|
||||
/// <param name="url">Url 地址</param>
|
||||
/// <param name="encoding">编码格式</param>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<string, string> GetQueryDict(string url, Encoding encoding)
|
||||
{
|
||||
Uri uri = new(url);
|
||||
string query = uri.Query.TrimStart(new char[] { '?' });
|
||||
|
||||
Dictionary<string, string> dataDict = [];
|
||||
string[] queryDataList = query.Split(new string[] { "&" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (queryDataList != null && queryDataList.Length > 0)
|
||||
{
|
||||
foreach (string queryData in queryDataList)
|
||||
{
|
||||
string[] keyValue = queryData.Split(new string[] { "=" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (!dataDict.ContainsKey(keyValue[0]))
|
||||
{
|
||||
dataDict.Add(keyValue[0].ToLower(), System.Web.HttpUtility.UrlDecode(keyValue[1], encoding));
|
||||
}
|
||||
}
|
||||
}
|
||||
return dataDict;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using MAX.Core.Autofac;
|
||||
using MAX.Core.DependencyInjection;
|
||||
|
||||
namespace MAX.Core.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// 启动器:实现 <see cref="IDependencyRegistrar"/>,由宿主项目统一发现并按 <see cref="Priority"/> 排序执行。
|
||||
/// </summary>
|
||||
public interface IStartup : IDependencyRegistrar, IPriority
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MAX.Core.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// 单例,需要在当前类中定义
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class SingleInstance<T> where T : class
|
||||
{
|
||||
private static readonly object lockObj = new();
|
||||
private static T mySelf;//= default(T);
|
||||
public static T Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (lockObj)
|
||||
{
|
||||
mySelf ??= InstanceCreator.CreateInstance<T>();
|
||||
}
|
||||
|
||||
return mySelf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class InstanceCreator
|
||||
{
|
||||
public static T CreateInstance<T>()
|
||||
{
|
||||
Type type = typeof(T);
|
||||
try
|
||||
{
|
||||
return (T)type.Assembly.CreateInstance(type.FullName, true, BindingFlags.NonPublic | BindingFlags.Instance, null, null, null, null);
|
||||
}
|
||||
catch (MissingMethodException ex)
|
||||
{
|
||||
throw new Exception($"{ex.Message}\n(单例模式下,构造函数必须为 private)");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
namespace MAX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 优先级(优先级越高越先执行)
|
||||
/// </summary>
|
||||
public interface IPriority
|
||||
{
|
||||
/// <summary>
|
||||
/// 优先级(优先级越高越先执行)
|
||||
/// </summary>
|
||||
int Priority { get; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 优先级
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// </remarks>
|
||||
public static class IntPriority
|
||||
{
|
||||
/// <summary>
|
||||
/// 最高优先级 (5000)
|
||||
/// </summary>
|
||||
public const int Highest = 5000;
|
||||
|
||||
/// <summary>
|
||||
/// 非常高优先级 (4000)
|
||||
/// </summary>
|
||||
public const int VeryHigh = 4000;
|
||||
|
||||
/// <summary>
|
||||
/// 较高优先级 (3000)
|
||||
/// </summary>
|
||||
public const int Higher = 3000;
|
||||
|
||||
/// <summary>
|
||||
/// 高优先级 (2000)
|
||||
/// </summary>
|
||||
public const int High = 2000;
|
||||
|
||||
/// <summary>
|
||||
/// 高于正常优先级 (1000)
|
||||
/// </summary>
|
||||
public const int AboveNormal = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// 正常优先级 (0)
|
||||
/// </summary>
|
||||
public const int Normal = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 低于正常优先级 (-1000)
|
||||
/// </summary>
|
||||
public const int BelowNormal = -1000;
|
||||
|
||||
/// <summary>
|
||||
/// 低优先级 (-2000)
|
||||
/// </summary>
|
||||
public const int Low = -2000;
|
||||
|
||||
/// <summary>
|
||||
/// 较低优先级 (-3000)
|
||||
/// </summary>
|
||||
public const int Lower = -3000;
|
||||
|
||||
/// <summary>
|
||||
/// 非常低优先级 (-4000)
|
||||
/// </summary>
|
||||
public const int VeryLow = -4000;
|
||||
|
||||
/// <summary>
|
||||
/// 最低优先级 (-5000)
|
||||
/// </summary>
|
||||
public const int Lowest = -5000;
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
namespace MAX.Core.Logger;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="ILoggerBase"/> that logs into a <see cref="TextWriter"/>.
|
||||
/// </summary>
|
||||
public class ConsoleLogger : ILoggerFacade
|
||||
{
|
||||
private readonly bool _debug;
|
||||
private readonly bool _console;
|
||||
|
||||
public bool IsDebugEnabled => true;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ConsoleLogger"/> that writes to
|
||||
/// the console output.
|
||||
/// </summary>
|
||||
public ConsoleLogger(bool debug = true, bool console = true)
|
||||
{
|
||||
_debug = debug;
|
||||
_console = console;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///// <summary>
|
||||
///// Disposes the associated <see cref="TextWriter"/>.
|
||||
///// </summary>
|
||||
///// <param name="disposing">When <see langword="true"/>, disposes the associated <see cref="TextWriter"/>.</param>
|
||||
//protected virtual void Dispose(bool disposing)
|
||||
//{
|
||||
// if (disposing)
|
||||
// {
|
||||
// writer?.Dispose();
|
||||
// }
|
||||
//}
|
||||
|
||||
///<summary>
|
||||
///Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
///</summary>
|
||||
/// <remarks>Calls <see cref="Dispose(bool)"/></remarks>.
|
||||
///<filterpriority>2</filterpriority>
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void Debug(Exception ex, string message)
|
||||
{
|
||||
Log(message, LogLevel.Debug);
|
||||
}
|
||||
|
||||
public void Debug(string message)
|
||||
{
|
||||
Log(message, LogLevel.Debug);
|
||||
}
|
||||
|
||||
public void Info(Exception ex, string message)
|
||||
{
|
||||
Log(message, LogLevel.Info);
|
||||
}
|
||||
|
||||
public void Info(string message)
|
||||
{
|
||||
Log(message, LogLevel.Info);
|
||||
}
|
||||
|
||||
public void Warn(Exception ex, string message)
|
||||
{
|
||||
Log(message, LogLevel.Warn);
|
||||
}
|
||||
|
||||
public void Warn(string message)
|
||||
{
|
||||
Log(message, LogLevel.Warn);
|
||||
}
|
||||
|
||||
public void Error(Exception ex, string message)
|
||||
{
|
||||
Log(message, LogLevel.Error);
|
||||
}
|
||||
|
||||
public void Error(string message)
|
||||
{
|
||||
Log(message, LogLevel.Error);
|
||||
}
|
||||
|
||||
public void Fatal(Exception ex, string message)
|
||||
{
|
||||
Log(message, LogLevel.Fatal);
|
||||
}
|
||||
|
||||
public void Fatal(string message)
|
||||
{
|
||||
Log(message, LogLevel.Fatal);
|
||||
}
|
||||
|
||||
public void Error(Exception ex)
|
||||
{
|
||||
Log(ex.Message, LogLevel.Error);
|
||||
}
|
||||
|
||||
public void Log(string message, LogLevel level)
|
||||
{
|
||||
string messageToLog = string.Format(CultureInfo.InvariantCulture, "{1}: {2}. Timestamp:{0:u}.", DateTime.Now,
|
||||
level.ToString().ToUpper(CultureInfo.InvariantCulture), message);
|
||||
//Trace.WriteLine(messageToLog);
|
||||
if (_debug)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(messageToLog);
|
||||
}
|
||||
if (_console)
|
||||
{
|
||||
Console.WriteLine(messageToLog);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using MAX.Core.Infrastructure;
|
||||
|
||||
namespace MAX.Core.Logger;
|
||||
|
||||
/// <summary>自定义日志记录器接口</summary>
|
||||
public interface ICustomLogger
|
||||
{
|
||||
/// <summary>记录日志信息</summary>
|
||||
/// <param name="group">日志分组</param>
|
||||
/// <param name="message">日志内容</param>
|
||||
void Log(string group, string message);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
|
||||
namespace MAX.Core.Logger;
|
||||
|
||||
/// <summary>
|
||||
/// 日志操作
|
||||
/// </summary>
|
||||
public interface ILoggerFacade
|
||||
{
|
||||
/// <summary>
|
||||
/// 调试级别是否启用
|
||||
/// </summary>
|
||||
bool IsDebugEnabled { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 调试
|
||||
/// </summary>
|
||||
void Debug(Exception ex, string message);
|
||||
/// <summary>
|
||||
/// 调试
|
||||
/// </summary>
|
||||
/// <param name="message">日志消息</param>
|
||||
void Debug(string message);
|
||||
/// <summary>
|
||||
/// 信息
|
||||
/// </summary>
|
||||
void Info(Exception ex, string message);
|
||||
/// <summary>
|
||||
/// 信息
|
||||
/// </summary>
|
||||
/// <param name="message">日志消息</param>
|
||||
void Info(string message);
|
||||
/// <summary>
|
||||
/// 警告
|
||||
/// </summary>
|
||||
void Warn(Exception ex, string message);
|
||||
/// <summary>
|
||||
/// 警告
|
||||
/// </summary>
|
||||
/// <param name="message">日志消息</param>
|
||||
void Warn(string message);
|
||||
/// <summary>
|
||||
/// 错误
|
||||
/// </summary>
|
||||
void Error(Exception ex, string message);
|
||||
/// <summary>
|
||||
/// 错误
|
||||
/// </summary>
|
||||
/// <param name="message">日志消息</param>
|
||||
void Error(string message);
|
||||
/// <summary>
|
||||
/// 致命错误
|
||||
/// </summary>
|
||||
void Fatal(Exception ex, string message);
|
||||
/// <summary>
|
||||
/// 致命错误
|
||||
/// </summary>
|
||||
/// <param name="message">日志消息</param>
|
||||
void Fatal(string message);
|
||||
void Error(Exception ex);
|
||||
|
||||
/// <summary>
|
||||
/// 写入日志
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="level"></param>
|
||||
void Log(string message, LogLevel level);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace MAX.Core.Logger;
|
||||
|
||||
/// <summary>
|
||||
/// 日志等级
|
||||
/// </summary>
|
||||
public enum LogLevel
|
||||
{
|
||||
Trace = 0,
|
||||
Debug = 1,
|
||||
Info = 2,
|
||||
Warn = 3,
|
||||
Error = 4,
|
||||
Fatal = 5,
|
||||
None = 6
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MediatR" Version="9.0.0" />
|
||||
<PackageReference Include="Autofac" Version="9.3.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,197 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace MAX.Core.Reflections;
|
||||
|
||||
public partial class AppTypeFinder : Finder
|
||||
{
|
||||
public static AppTypeFinder Instance { get; } = new AppTypeFinder();
|
||||
|
||||
/// <summary>
|
||||
/// 所有程序集
|
||||
/// </summary>
|
||||
public static List<Assembly> Assemblies => Instance.GetAssemblies();
|
||||
|
||||
public static Exception LoadDLL(string dllPath, out Assembly assembly)
|
||||
{
|
||||
return Instance.LoadAssemblyToAppDomain(dllPath, out assembly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 程序集是否匹配
|
||||
/// </summary>
|
||||
protected override bool Match(string assemblyName)
|
||||
{
|
||||
if (base.Match(assemblyName))
|
||||
{
|
||||
if (AvailablePrefix().Exists(assemblyName.StartsWith))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return Regex.IsMatch(assemblyName, "^(MAX\\.)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
protected virtual List<string> AvailablePrefix()
|
||||
{
|
||||
return ["MAX."];
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> GetAllTypes()
|
||||
{
|
||||
List<Type> types = [];
|
||||
foreach (Assembly item in Assemblies)
|
||||
{
|
||||
try
|
||||
{
|
||||
types.AddRange(item.GetTypes());
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找当前程序集中所有继承或实现了指定类型的非抽象类。
|
||||
/// </summary>
|
||||
/// <param name="targetType">
|
||||
/// 要匹配的目标类型,可以是:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>普通接口类型(例如 <c>IMyService</c>)</description></item>
|
||||
/// <item><description>开放泛型接口类型(例如 <c>IRepository<></c>)</description></item>
|
||||
/// <item><description>普通基类类型(例如 <c>BaseHandler</c>)</description></item>
|
||||
/// <item><description>开放泛型基类类型(例如 <c>BaseHandler<></c>)</description></item>
|
||||
/// </list>
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// 所有满足条件的具体类(非抽象类)的 <see cref="Type"/> 数组。
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// 当 <paramref name="targetType"/> 为 <c>null</c> 时抛出。
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// 此方法会遍历 <c>GetAllTypes()</c> 返回的所有类型,并筛选出:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>实现了指定接口的类(包括开放泛型接口的封闭实现)</description></item>
|
||||
/// <item><description>继承了指定基类的类(包括开放泛型基类的封闭实现)</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>示例:</para>
|
||||
/// <code>
|
||||
/// // 查找所有实现了 IEnumDisplayHandler<T> 的类
|
||||
/// var handlers = FindAllDerivedTypes(typeof(IEnumDisplayHandler<>));
|
||||
///
|
||||
/// // 查找所有实现了 IMyService 的类
|
||||
/// var services = FindAllDerivedTypes(typeof(IMyService));
|
||||
///
|
||||
/// // 查找所有继承 BaseHandler 的类
|
||||
/// var subclasses = FindAllDerivedTypes(typeof(BaseHandler));
|
||||
///
|
||||
/// // 查找所有继承开放泛型基类 BaseHandler<T> 的类
|
||||
/// var genericSubclasses = FindAllDerivedTypes(typeof(BaseHandler<>));
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public static Type[] FindAllDerivedTypes(Type targetType)
|
||||
{
|
||||
if (targetType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(targetType));
|
||||
}
|
||||
|
||||
return GetAllTypes().Where(t =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!t.IsClass || t.IsAbstract)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 开放泛型接口
|
||||
if (targetType.IsInterface && targetType.IsGenericTypeDefinition)
|
||||
{
|
||||
return t.GetInterfaces().Any(i =>
|
||||
i.IsGenericType && i.GetGenericTypeDefinition() == targetType);
|
||||
}
|
||||
|
||||
// 普通接口
|
||||
if (targetType.IsInterface)
|
||||
{
|
||||
return targetType.IsAssignableFrom(t);
|
||||
}
|
||||
|
||||
// 开放泛型基类
|
||||
if (targetType.IsGenericTypeDefinition)
|
||||
{
|
||||
return IsSubclassOfRawGeneric(t, targetType);
|
||||
}
|
||||
|
||||
// 普通基类
|
||||
return targetType.IsAssignableFrom(t) && t != targetType;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查指定类型是否继承自某个开放泛型基类。
|
||||
/// </summary>
|
||||
/// <param name="type">要检查的类型。</param>
|
||||
/// <param name="generic">开放泛型基类类型,例如 <c>BaseHandler<></c>。</param>
|
||||
/// <returns>
|
||||
/// 如果 <paramref name="type"/> 的继承链中存在指定的开放泛型基类,则返回 <c>true</c>;否则返回 <c>false</c>。
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// 此方法会沿着 <paramref name="type"/> 的继承链向上遍历,
|
||||
/// 并在每一级检查是否为指定的开放泛型基类定义。
|
||||
/// </remarks>
|
||||
private static bool IsSubclassOfRawGeneric(Type type, Type generic)
|
||||
{
|
||||
while (type != null && type != typeof(object))
|
||||
{
|
||||
Type cur = type.IsGenericType ? type.GetGenericTypeDefinition() : type;
|
||||
if (generic == cur)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
type = type.BaseType;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 泛型包装方法,查找所有继承或实现了指定类型 <typeparamref name="T"/> 的非抽象类。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">
|
||||
/// 目标类型,可以是接口、开放泛型接口、基类或开放泛型基类。
|
||||
/// </typeparam>
|
||||
/// <returns>
|
||||
/// 所有满足条件的具体类(非抽象类)的 <see cref="Type"/> 数组。
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// 调用示例:
|
||||
/// <code>
|
||||
/// var handlers = FindAllDerivedTypes<IEnumDisplayHandler<>>();
|
||||
/// var services = FindAllDerivedTypes<IMyService>();
|
||||
/// var subclasses = FindAllDerivedTypes<BaseHandler>();
|
||||
/// var genericSubclasses = FindAllDerivedTypes<BaseHandler<>>();
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public static Type[] FindAllDerivedTypes<T>() => FindAllDerivedTypes(typeof(T));
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using MAX.Core.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace MAX.Core.Reflections;
|
||||
|
||||
public class Finder
|
||||
{
|
||||
/// <summary>
|
||||
/// 跳过的程序集
|
||||
/// </summary>
|
||||
private const string SkipAssemblies = "^System|^Microsoft|^mscorlib|^netstandard|^Autofac|^AutoMapper|^EntityFramework|^Newtonsoft|^Castle|^NLog|^Pomelo|^AspectCore|^Xunit|^Nito|^Npgsql|^Exceptionless|^MySqlConnector|^Anonymously|^libuv|^api-ms|^clrcompression|^clretwrc|^clrjit|^coreclr|^dbgshim|^e_sqlite3|^hostfxr|^hostpolicy|^MessagePack|^mscordaccore|^mscordbi|^mscorrc|^sni|^sos|^SOS\\.NETCore|^sos_amd64|^SQLitePCLRaw|^StackExchange|^Swashbuckle|^WindowsBase|^ucrtbase|^DotNetCore\\.CAP|^MongoDB|^Confluent\\.Kafka|^librdkafka|^EasyCaching|^RabbitMQ|^Consul|^Dapper|^EnyimMemcachedCore|^Pipelines|^DnsClient|^IdentityModel|^zlib|^Runtime\\.|^Google|^Grpc|^Internal\\.|^Azure|^DevExpress\\.|^FreeSql\\.|^PresentationFramework|^PresentationCore|^Prism|^DirectWriteForwarder|^MAX\\.Common|^ReflectionHelperTypesPublicWrappers|^UIAutomation|^WindowsFormsIntegration";
|
||||
|
||||
/// <summary>
|
||||
/// 获取程序集列表
|
||||
/// </summary>
|
||||
public virtual List<Assembly> GetAssemblies()
|
||||
{
|
||||
LoadAssemblies(AppDomain.CurrentDomain.BaseDirectory);
|
||||
return GetAssembliesFromCurrentAppDomain();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载程序集到当前应用程序域
|
||||
/// </summary>
|
||||
/// <param name="path">目录绝对路径</param>
|
||||
protected void LoadAssemblies(string path)
|
||||
{
|
||||
foreach (string file in Directory.GetFiles(path, "*.dll"))
|
||||
{
|
||||
if (Match(Path.GetFileName(file)) == false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
LoadAssemblyToAppDomain(file, out _);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 程序集是否匹配
|
||||
/// </summary>
|
||||
protected virtual bool Match(string assemblyName)
|
||||
{
|
||||
return Regex.IsMatch(assemblyName, SkipAssemblies, RegexOptions.IgnoreCase | RegexOptions.Compiled) == false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将程序集添加当前应用程序域
|
||||
/// </summary>
|
||||
protected Exception LoadAssemblyToAppDomain(string file, out Assembly assembly)
|
||||
{
|
||||
try
|
||||
{
|
||||
//Debug.WriteLine($"Load Assembly : {Path.GetFileName(file)}");
|
||||
AssemblyName assemblyName = AssemblyName.GetAssemblyName(file);
|
||||
assembly = AppDomain.CurrentDomain.Load(assemblyName);
|
||||
return null;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
// 如果找不到文件,回退到 LoadFrom 方式
|
||||
try
|
||||
{
|
||||
assembly = Assembly.LoadFrom(file);
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
assembly = null;
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
assembly = null;
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从当前应用程序域获取程序集列表
|
||||
/// </summary>
|
||||
private List<Assembly> GetAssembliesFromCurrentAppDomain()
|
||||
{
|
||||
List<Assembly> result = [];
|
||||
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
if (Match(assembly))
|
||||
{
|
||||
result.Add(assembly);
|
||||
}
|
||||
}
|
||||
return result.Distinct().ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 程序集是否匹配
|
||||
/// </summary>
|
||||
private bool Match(Assembly assembly)
|
||||
{
|
||||
return !Regex.IsMatch(assembly.FullName, SkipAssemblies, RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类型列表
|
||||
/// </summary>
|
||||
/// <typeparam name="T">查找类型</typeparam>
|
||||
/// <param name="assemblies">在指定的程序集列表中查找</param>
|
||||
public List<Type> Find<T>(List<Assembly> assemblies = null)
|
||||
{
|
||||
return Find(typeof(T), assemblies);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找类型列表
|
||||
/// </summary>
|
||||
/// <param name="findType">查找类型</param>
|
||||
/// <param name="assemblies">在指定的程序集列表中查找</param>
|
||||
public List<Type> Find(Type findType, List<Assembly> assemblies = null)
|
||||
{
|
||||
assemblies ??= GetAssemblies();
|
||||
return ReflectionHelper.FindTypes(findType, assemblies.ToArray());
|
||||
}
|
||||
}
|
||||
Loaded 100 of 164 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user