Main页面
This commit is contained in:
1 parent
a003d90b0e
commit
f03e2b08f4
33 files changed
+54543
-34
No files matched your search
@@ -18,9 +18,10 @@ globs: "**/*.xaml"
|
||||
xmlns:hc="https://handyorg.github.io/handycontrol"
|
||||
xmlns:wpf="MAX.WPF"
|
||||
xmlns:s="clr-namespace:xxxx.xxx;assembly=xxx.xxx"
|
||||
d:DataContext="{d:DesignInstance Type=local:XxxViewModel}"
|
||||
Style="{DynamicResource UserControlStyle}"
|
||||
DataContext="{core:DISource Type=local:XxxViewModel}"
|
||||
d:DataContext="{d:DesignInstance Type=local:XxxViewModel}"
|
||||
d:Background="White"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800">
|
||||
```
|
||||
@@ -42,6 +43,14 @@ DataContext="{core:DISource Type=local:DialogViewModel}">
|
||||
- `d:DataContext="{d:DesignInstance Type=local:XxxViewModel}"` 提供设计时数据
|
||||
- `UserControlStyle` 与 `ViewModelBaseEx` 强相关,必须搭配使用
|
||||
|
||||
|
||||
|
||||
### 控件必须有设计数据
|
||||
|
||||
例如
|
||||
TextBlock > d:Text="TextBlock"
|
||||
ComboBox > d:ItemsSource="{d:SampleData ItemCount=5}"
|
||||
|
||||
## BindingProxy + CommandProxy(DataTemplate/ContextMenu 绑定)
|
||||
|
||||
**在 `DataTemplate`、`ContextMenu`、`DataGridTemplateColumn.CellTemplate` 中禁止使用 `RelativeSource AncestorType`,必须用 `BindingProxy` + `CommandProxy`。**
|
||||
|
||||
@@ -23,6 +23,14 @@ public sealed class UserApi(ApiTransport transport)
|
||||
return transport.GetRequiredAsync<UserInfo>("user/AddUser", Method.Post, request, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task DeleteAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(id);
|
||||
return transport.SendAsync<object>("user/DelUser", Method.Post,
|
||||
query: new Dictionary<string, string?> { ["id"] = id.ToString(System.Globalization.CultureInfo.InvariantCulture) },
|
||||
cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task<UserInfo> UpdateAsync(UserUpdateRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using PipeReviewReport.Resources.Fonts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Store.Management.Framework.Controls
|
||||
{
|
||||
public class IconText : TextBlock
|
||||
{
|
||||
static IconText()
|
||||
{
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(IconText), new FrameworkPropertyMetadata(typeof(IconText)));
|
||||
}
|
||||
|
||||
|
||||
public IconNameEnum Icon
|
||||
{
|
||||
get => (IconNameEnum)GetValue(IconNameProperty);
|
||||
set => SetValue(IconNameProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IconNameProperty = DependencyProperty.Register(
|
||||
nameof(Icon),
|
||||
typeof(IconNameEnum),
|
||||
typeof(IconText),
|
||||
new PropertyMetadata(IconNameEnum.Empty, OnIconChanged)
|
||||
);
|
||||
|
||||
private static void OnIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is IconText self)
|
||||
{
|
||||
self.Text = self.Icon.GetGlyphString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Store.Management.Framework
|
||||
{
|
||||
public class Navigation
|
||||
{
|
||||
public static string MainContext { get; } = nameof(MainContext);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,65 @@
|
||||
<#@ template debug="false" hostspecific="true" language="C#" #>
|
||||
<#@ assembly name="System.Core" #>
|
||||
<#@ import namespace="System.Linq" #>
|
||||
<#@ import namespace="System.IO" #>
|
||||
<#@ import namespace="System.Text" #>
|
||||
<#@ import namespace="System.Collections.Generic" #>
|
||||
<#@ import namespace="System.Text.RegularExpressions" #>
|
||||
<#@ output extension=".xaml" encoding="utf-8" #>
|
||||
|
||||
<#
|
||||
|
||||
StringBuilder FontString = new StringBuilder();
|
||||
FontString.AppendLine("<ResourceDictionary");
|
||||
FontString.AppendLine("\txmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"");
|
||||
FontString.AppendLine("\txmlns:PresentationOptions=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation/options\"");
|
||||
FontString.AppendLine("\txmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"");
|
||||
FontString.AppendLine("\txmlns:sys=\"clr-namespace:System;assembly=mscorlib\"");
|
||||
FontString.AppendLine("\t>");
|
||||
FontString.AppendLine("");
|
||||
FontString.AppendLine("");
|
||||
|
||||
string cssPath = this.Host.ResolvePath("iconfont.css");
|
||||
string svgPath = this.Host.ResolvePath("iconfont.js");
|
||||
|
||||
if (File.Exists(cssPath))
|
||||
{
|
||||
string cssContent = File.ReadAllText(cssPath, Encoding.UTF8);
|
||||
MatchCollection matchs = Regex.Matches(cssContent, "\\.icon-(?<name>.+?):before.+?\\{.+?content:.+?\"\\\\(?<value>e.+?)\";.+?\\}", RegexOptions.Singleline);
|
||||
foreach (Match match in matchs)
|
||||
{
|
||||
string name = match.Groups["name"].Value;
|
||||
//name = Regex.Replace(name,"(a-(\\d{4,}(.*)?_fluent_|SegoeFluentIconsRegular-))|(_regular_icon|$)","",RegexOptions.Singleline);
|
||||
string value = match.Groups["value"].Value;
|
||||
FontString.AppendLine(string.Format("\t<sys:String x:Key=\"txt-{0}\">&#x{1};</sys:String>", name, value));
|
||||
}
|
||||
}
|
||||
if (File.Exists(svgPath))
|
||||
{
|
||||
FontString.AppendLine("");
|
||||
FontString.AppendLine("");
|
||||
|
||||
string svgContent = File.ReadAllText(svgPath, Encoding.UTF8);
|
||||
MatchCollection matchs = Regex.Matches(svgContent, "<symbol.+?id=\"(?<name>.+?)\".+?>(?<value>.+?)</symbol>");
|
||||
foreach (Match match in matchs)
|
||||
{
|
||||
string name = match.Groups["name"].Value;
|
||||
string symbol = match.Groups["value"].Value;
|
||||
StringBuilder value =new StringBuilder();
|
||||
MatchCollection pathMatchs = Regex.Matches(symbol,"<path.+?d=\"(?<value>.+?)\"");
|
||||
foreach(Match pathMatch in pathMatchs)
|
||||
{
|
||||
value.AppendLine("\t\t"+pathMatch.Groups["value"].Value);
|
||||
}
|
||||
FontString.AppendLine(string.Format("\t<Geometry x:Key=\"{0}\" PresentationOptions:Freeze=\"True\">\n{1}\t</Geometry>", name, value));
|
||||
}
|
||||
}
|
||||
FontString.AppendLine("");
|
||||
FontString.AppendLine("");
|
||||
FontString.AppendLine("</ResourceDictionary>");
|
||||
string content = FontString.ToString();
|
||||
string savePath =Path.Combine(this.Host.ResolvePath(""),@"..\Themes\FontGeometry.xaml");
|
||||
//File.WriteAllText(savePath,content,Encoding.UTF8);
|
||||
#>
|
||||
<#=content#>
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,284 @@
|
||||
<#@ template debug="false" hostspecific="true" language="C#" #>
|
||||
<#@ assembly name="System.Core" #>
|
||||
|
||||
<#@ import namespace="System.Linq" #>
|
||||
<#@ import namespace="System.Text" #>
|
||||
<#@ import namespace="System.IO" #>
|
||||
<#@ import namespace="System.Collections.Generic" #>
|
||||
<#@ import namespace="System.Text.RegularExpressions" #>
|
||||
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
|
||||
|
||||
<#@ assembly name="$(ProjectDir)\Resources\DLLs\IconResourcesGenerator.dll" #>
|
||||
<#@ import namespace="IconResourcesGenerator" #>
|
||||
|
||||
<#@ output extension=".cs" encoding="utf-8" #>
|
||||
|
||||
<#
|
||||
ITextTemplatingEngineHost host = this.Host;
|
||||
|
||||
string jsPath = host.ResolvePath("iconfont.js");
|
||||
string cssPath = host.ResolvePath("iconfont.css");
|
||||
|
||||
string namespaceName = "PipeReviewReport.Resources.Fonts";
|
||||
|
||||
Generator generator = new Generator(jsPath, cssPath);
|
||||
Dictionary<string, string> glyphToKey = generator.ExecuteGlyphToKey();
|
||||
Dictionary<string, string> keyToGlyph = generator.ExecuteKeyToGlyph();
|
||||
(Dictionary<string, string> simpleNameToKey, Dictionary<string, string> keyToSimpleName) = generator.ExecuteSimpleNameToKey();
|
||||
|
||||
#>
|
||||
|
||||
using System;
|
||||
// using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace <#= namespaceName #>;
|
||||
|
||||
[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "<Pending>")]
|
||||
internal class __________________Temp__________________
|
||||
{
|
||||
[SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "<Pending>")]
|
||||
private void Test()
|
||||
{
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 图标名称枚举
|
||||
/// </summary>
|
||||
// [TypeConverter(typeof(EnumConverter))]
|
||||
public enum IconNameEnum
|
||||
{
|
||||
Empty,
|
||||
|
||||
<#
|
||||
foreach (var entry in simpleNameToKey)
|
||||
{
|
||||
#>
|
||||
/// <summary>
|
||||
/// <#= keyToGlyph[entry.Value] #>
|
||||
/// <br/>
|
||||
/// icon-<#= entry.Value #>
|
||||
/// </summary>
|
||||
<#= entry.Key #>,
|
||||
|
||||
<#
|
||||
}
|
||||
#>
|
||||
}
|
||||
|
||||
|
||||
public static class IconNameEnumExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据图标名称枚举获取 Key
|
||||
/// </summary>
|
||||
/// <param name="e">图标名称枚举</param>
|
||||
/// <returns>注册到资源中的 Key</returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public static string GetKey(this IconNameEnum e) {
|
||||
return e switch
|
||||
{
|
||||
IconNameEnum.Empty => null,
|
||||
<#
|
||||
foreach (var entry in simpleNameToKey)
|
||||
{
|
||||
#>
|
||||
IconNameEnum.<#= entry.Key #> => "icon-<#= entry.Value #>",
|
||||
<#
|
||||
}
|
||||
#>
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 根据图标名称获取 Glyph 枚举
|
||||
/// </summary>
|
||||
/// <param name="e">图标名称枚举</param>
|
||||
/// <returns>图标 Glyph 枚举</returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public static IconGlyphEnum GetGlyphEnum(this IconNameEnum e) {
|
||||
return e switch
|
||||
{
|
||||
IconNameEnum.Empty => IconGlyphEnum.Empty,
|
||||
<#
|
||||
foreach (var entry in simpleNameToKey)
|
||||
{
|
||||
#>
|
||||
IconNameEnum.<#= entry.Key #> => IconGlyphEnum.<#= keyToGlyph[entry.Value] #>,
|
||||
<#
|
||||
}
|
||||
#>
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据图标名称获取 Glyph 字符串
|
||||
/// </summary>
|
||||
/// <param name="e">图标名称枚举</param>
|
||||
/// <returns>图标 Glyph 字符串</returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public static string GetGlyphString(this IconNameEnum e) {
|
||||
return e switch
|
||||
{
|
||||
IconNameEnum.Empty => string.Empty,
|
||||
<#
|
||||
foreach (var entry in simpleNameToKey)
|
||||
{
|
||||
#>
|
||||
IconNameEnum.<#= entry.Key #> => "\u<#= keyToGlyph[entry.Value] #>",
|
||||
<#
|
||||
}
|
||||
#>
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
public static string GetImageSourceKey(this IconNameEnum e) {
|
||||
return GetKey(e) + "_Image";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 图标 Glyph 枚举
|
||||
/// </summary>
|
||||
// [TypeConverter(typeof(EnumConverter))]
|
||||
public enum IconGlyphEnum
|
||||
{
|
||||
Empty,
|
||||
|
||||
<#
|
||||
foreach (var entry in glyphToKey)
|
||||
{
|
||||
#>
|
||||
/// <summary>
|
||||
/// <#= keyToSimpleName[entry.Value] #>
|
||||
/// <br/>
|
||||
/// icon-<#= entry.Value #>
|
||||
/// </summary>
|
||||
<#= entry.Key #>,
|
||||
|
||||
<#
|
||||
}
|
||||
#>
|
||||
}
|
||||
|
||||
public static class IconGlyphEnumExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据 Glyph 枚举获取 Key
|
||||
/// </summary>
|
||||
/// <param name="e">Glyph 枚举</param>
|
||||
/// <returns>注册到资源中的 Key</returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public static string GetKey(this IconGlyphEnum e)
|
||||
{
|
||||
return e switch
|
||||
{
|
||||
IconGlyphEnum.Empty => null,
|
||||
<#
|
||||
foreach (var entry in glyphToKey)
|
||||
{
|
||||
#>
|
||||
IconGlyphEnum.<#= entry.Key #> => "icon-<#= entry.Value #>",
|
||||
<#
|
||||
}
|
||||
#>
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据图标 Glyph 枚举获取图标名称枚举
|
||||
/// </summary>
|
||||
/// <param name="e">图标 Glyph 枚举</param>
|
||||
/// <returns>图标名称枚举</returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public static IconNameEnum GetNameEnum(this IconGlyphEnum e) {
|
||||
return e switch
|
||||
{
|
||||
IconGlyphEnum.Empty => IconNameEnum.Empty,
|
||||
<#
|
||||
foreach (var entry in glyphToKey)
|
||||
{
|
||||
#>
|
||||
IconGlyphEnum.<#= entry.Key #> => IconNameEnum.<#= keyToSimpleName[entry.Value] #>,
|
||||
<#
|
||||
}
|
||||
#>
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据图标 Glyph 枚举获取 Glyph 字符串
|
||||
/// </summary>
|
||||
/// <param name="e">图标 Glyph 枚举</param>
|
||||
/// <returns>图标 Glyph 字符串</returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public static string GetGlyphString(this IconGlyphEnum e) {
|
||||
return e switch
|
||||
{
|
||||
IconGlyphEnum.Empty => string.Empty,
|
||||
<#
|
||||
foreach (var entry in glyphToKey)
|
||||
{
|
||||
#>
|
||||
IconGlyphEnum.<#= entry.Key #> => "\u<#= keyToGlyph[entry.Value] #>",
|
||||
<#
|
||||
}
|
||||
#>
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
public static string GetImageSourceKey(this IconGlyphEnum e) {
|
||||
return GetKey(e) + "_Image";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class IconEnumNameToGlyphConverter : IValueConverter {
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (value is IconNameEnum nameEnum) {
|
||||
return nameEnum.GetGlyphEnum();
|
||||
}
|
||||
return DependencyProperty.UnsetValue;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (value is IconGlyphEnum glyphEnum) {
|
||||
return glyphEnum.GetNameEnum();
|
||||
}
|
||||
return DependencyProperty.UnsetValue;
|
||||
}
|
||||
}
|
||||
|
||||
public class IconGlyphNameToNameConverter : IValueConverter {
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (value is IconGlyphEnum glyphEnum) {
|
||||
return glyphEnum.GetNameEnum();
|
||||
}
|
||||
return DependencyProperty.UnsetValue;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (value is IconNameEnum nameEnum) {
|
||||
return nameEnum.GetGlyphEnum();
|
||||
}
|
||||
return DependencyProperty.UnsetValue;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1 @@
|
||||
window._iconfont_svg_string_5223831='<svg><symbol id="icon-fapiao" viewBox="0 0 1024 1024"><path d="M170.666667 868.693333l98.346666-94.72a21.333333 21.333333 0 0 1 30.72 1.173334l98.176 110.122666 98.176-110.122666a21.333333 21.333333 0 0 1 31.829334 0l98.176 110.122666 98.176-110.122666a21.333333 21.333333 0 0 1 30.72-1.173334L853.333333 868.693333V128H170.666667v740.693333z m-42.666667 49.386667V106.709333C128 94.890667 137.706667 85.333333 149.333333 85.333333h725.333334c11.776 0 21.333333 9.642667 21.333333 21.184v811.541334a21.333333 21.333333 0 0 1-36.117333 16.149333l-118.506667-114.133333-99.349333 111.445333a21.333333 21.333333 0 0 1-31.850667 0L512 821.397333l-98.176 110.122667a21.333333 21.333333 0 0 1-31.850667 0l-99.349333-111.445333-118.485333 114.133333A21.333333 21.333333 0 0 1 128 918.08zM298.666667 320c0-11.776 9.493333-21.333333 21.184-21.333333h341.632c11.690667 0 21.184 9.472 21.184 21.333333 0 11.776-9.493333 21.333333-21.184 21.333333H319.850667A21.205333 21.205333 0 0 1 298.666667 320z m0 128c0-11.776 9.6-21.333333 21.482666-21.333333h255.701334c11.861333 0 21.482667 9.472 21.482666 21.333333 0 11.776-9.6 21.333333-21.482666 21.333333H320.149333A21.354667 21.354667 0 0 1 298.666667 448z m0 128c0-11.776 9.6-21.333333 21.482666-21.333333h255.701334c11.861333 0 21.482667 9.472 21.482666 21.333333 0 11.776-9.6 21.333333-21.482666 21.333333H320.149333A21.354667 21.354667 0 0 1 298.666667 576z" fill="#3D3D3D" ></path></symbol><symbol id="icon-calendar_work_week" viewBox="0 0 1024 1024"><path d="M330.666667 298.666667a32 32 0 0 0-32 32v170.666666c0 17.664 14.336 32 32 32h362.666666a32 32 0 0 0 32-32v-170.666666a32 32 0 0 0-32-32h-362.666666z m32 170.666666V362.666667h298.666666V469.333333h-298.666666z" ></path><path d="M757.333333 896A138.666667 138.666667 0 0 0 896 757.333333V266.666667A138.666667 138.666667 0 0 0 757.333333 128H266.666667A138.666667 138.666667 0 0 0 128 266.666667v490.666666A138.666667 138.666667 0 0 0 266.666667 896h490.666666zM832 266.666667v490.666666a74.666667 74.666667 0 0 1-74.666667 74.666667H266.666667a74.666667 74.666667 0 0 1-74.666667-74.666667V266.666667c0-41.216 33.450667-74.666667 74.666667-74.666667h490.666666c41.216 0 74.666667 33.450667 74.666667 74.666667z" ></path></symbol><symbol id="icon-document_endnote" viewBox="0 0 1024 1024"><path d="M234.666667 181.333333a32 32 0 0 1 32-32H512V341.333333a85.333333 85.333333 0 0 0 85.333333 85.333334h192v416a32 32 0 0 1-32 32H554.666667V938.666667h202.666666A96 96 0 0 0 853.333333 842.666667V412.330667c0-19.797333-7.850667-38.784-21.888-52.778667l-252.330666-252.330667A74.666667 74.666667 0 0 0 526.336 85.333333H266.666667A96 96 0 0 0 170.666667 181.333333V512h64V181.333333z m341.333333 160V194.56l168.106667 168.106667H597.333333a21.333333 21.333333 0 0 1-21.333333-21.333334zM192 586.666667a32 32 0 0 1-32 32H106.666667v298.666666h53.333333a32 32 0 0 1 0 64H106.666667A64 64 0 0 1 42.666667 917.333333v-298.666666A64 64 0 0 1 106.666667 554.666667h53.333333a32 32 0 0 1 32 32z" ></path><path d="M394.666667 618.666667a32 32 0 0 1 0-64h53.333333a64 64 0 0 1 64 64v298.666666a64 64 0 0 1-64 64H394.666667a32 32 0 0 1 0-64h53.333333v-298.666666H394.666667zM277.333333 714.666667a32 32 0 0 1 32 32V896a32 32 0 0 1-64 0v-149.333333a32 32 0 0 1 32-32zM277.333333 682.666667a32 32 0 1 0 0-64 32 32 0 0 0 0 64z" ></path></symbol><symbol id="icon-leaf_three" viewBox="0 0 1024 1024"><path d="M810.666667 320c0 22.186667-3.072 43.648-8.832 64h69.12C908.373333 384 938.666667 414.293333 938.666667 451.669333V661.333333a277.333333 277.333333 0 0 1-449.493334 217.429334L438.613333 929.28a32 32 0 1 1-45.226666-45.226667l50.517333-50.56a277.077333 277.077333 0 0 1-40.704-70.613333A256 256 0 0 1 96 512V320.597333C96 284.928 124.928 256 160.597333 256h189.568A234.752 234.752 0 0 1 576 85.333333h173.226667c33.92 0 61.44 27.52 61.44 61.44V320zM414.805333 263.765333a256.554667 256.554667 0 0 1 165.504 132.266667A277.290667 277.290667 0 0 1 661.333333 384h72.96c7.978667-19.754667 12.373333-41.386667 12.373334-64v-170.666667h-170.666667a170.752 170.752 0 0 0-161.194667 114.432z m120.021334 569.344A213.333333 213.333333 0 0 0 874.666667 661.333333v-209.664a3.669333 3.669333 0 0 0-3.669334-3.669333H661.333333a213.333333 213.333333 0 0 0-171.776 339.84l181.162667-181.12a32 32 0 1 1 45.226667 45.226667l-181.12 181.162666zM384 661.333333c0-25.258667 3.413333-49.749333 9.728-73.002666L276.053333 470.613333a32 32 0 1 1 45.226667-45.226666l98.901333 98.858666a278.698667 278.698667 0 0 1 101.333334-102.485333A192 192 0 0 0 352 320H160.384l-0.213333 0.170667a0.938667 0.938667 0 0 0-0.128 0.213333l-0.042667 0.213333V512a192 192 0 0 0 226.816 188.842667A279.765333 279.765333 0 0 1 384 661.333333z" ></path></symbol><symbol id="icon-re_order_dots_horizontal" viewBox="0 0 1024 1024"><path d="M298.666667 661.333333a64 64 0 1 1-128 0 64 64 0 0 1 128 0z m0-298.666666a64 64 0 1 1-128 0 64 64 0 0 1 128 0z m298.666666 298.666666a64 64 0 1 1-128 0 64 64 0 0 1 128 Line truncated
|
||||
File diff suppressed because it is too large.
Load diff
Binary file not shown.
@@ -2,13 +2,22 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:f="clr-namespace:Store.Management.Framework.Controls">
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Fonts/FontGeometry.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/MAX.Common.WPF;component/Resources/Generic.xaml" />
|
||||
|
||||
<ResourceDictionary Source="/Store.Management.Framework;component/Controls/DataGridEx/DataGridEx.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<FontFamily x:Key="IconPack">Fonts/iconfont.ttf#iconfont</FontFamily>
|
||||
|
||||
|
||||
<Style x:Key="TextBlockIcon" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontFamily" Value="{StaticResource IconPack}" />
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="FontSize" Value="18" />
|
||||
</Style>
|
||||
|
||||
<Style BasedOn="{StaticResource TextBlockIcon}" TargetType="{x:Type f:IconText}" />
|
||||
<Style
|
||||
x:Key="WindowBase"
|
||||
BasedOn="{StaticResource WindowWin10}"
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
using MAX.Common.WPF.Converters;
|
||||
using MAX.Common.WPF.Utilities;
|
||||
using MAX.Core;
|
||||
using MAX.Core.Autofac;
|
||||
using MAX.Framework.Interfaces;
|
||||
using Store.Management.Api;
|
||||
using Store.Management.Framework.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -28,6 +30,31 @@ namespace Store.Management.Framework
|
||||
BaseUrl = new Uri("http://localhost:14312/api/")
|
||||
});
|
||||
builder.Register(_ => client).SingleInstance().PreserveExistingDefaults();
|
||||
|
||||
DISource.Resolver = Resolve;
|
||||
}
|
||||
protected virtual object? Resolve(Type? type, string? name)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (IocManager.Container is not null)
|
||||
{
|
||||
if (name != null)
|
||||
{
|
||||
if (IocManager.Container.IsRegisteredWithName(name, type))
|
||||
{
|
||||
return IocManager.Container.ResolveNamed(name, type);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return IocManager.Resolve(type);
|
||||
}
|
||||
|
||||
return Activator.CreateInstance(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,10 @@
|
||||
<Content Remove="C:\Users\chaih\.nuget\packages\handycontrol\3.6.2.1\contentFiles\any\net8.0-windows7.0\LangProvider.tt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Resources\Fonts\iconfont.ttf" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HandyControl" Version="3.6.2.1" />
|
||||
<PackageReference Include="MAX.Common.WPF" Version="1.4.23" />
|
||||
@@ -22,4 +26,31 @@
|
||||
<Folder Include="DependencyInjection\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Fonts\iconfont.ttf" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Resources\Fonts\FontGeometry.tt">
|
||||
<Generator>TextTemplatingFileGenerator</Generator>
|
||||
<LastGenOutput>FontGeometry.xaml</LastGenOutput>
|
||||
</None>
|
||||
<None Update="Resources\Fonts\IconEnum.tt">
|
||||
<Generator>TextTemplatingFileGenerator</Generator>
|
||||
<LastGenOutput>IconEnum.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Resources\Fonts\IconEnum.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>IconEnum.tt</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -5,9 +5,7 @@
|
||||
xmlns:mf="clr-namespace:Store.Management.Framework;assembly=Store.Management.Framework">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<SolidColorBrush x:Key="PrimaryBrush" Color="{DynamicResource PrimaryColor}" />
|
||||
<SolidColorBrush x:Key="TextIconBrush" Color="{DynamicResource TextIconColor}" />
|
||||
<SolidColorBrush x:Key="BackgroundBrush" Color="{DynamicResource BackgroundColor}" />
|
||||
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/Store.Management.Framework;component/Resources/HandyControlBasic.xaml" />
|
||||
|
||||
@@ -7,11 +7,13 @@ using Store.Management.Notifications;
|
||||
using Store.Management.Services;
|
||||
using Store.Management.ViewModels;
|
||||
using Store.Management.Views;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
namespace Store.Management;
|
||||
public partial class App : FrameworkApplication
|
||||
{
|
||||
protected override Window CreateShell()
|
||||
private AppSplashScreen _appSplashScreen;
|
||||
protected override Window CreateShell()
|
||||
{
|
||||
if (Environment.GetCommandLineArgs().Contains("--grid-demo"))
|
||||
{
|
||||
@@ -25,10 +27,33 @@ public partial class App : FrameworkApplication
|
||||
}
|
||||
return Container.Resolve<MainWindow>();
|
||||
}
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
_appSplashScreen = new()
|
||||
{
|
||||
CloseCallBack = () =>
|
||||
{
|
||||
base.OnStartup(e);
|
||||
},
|
||||
};
|
||||
_appSplashScreen.ShowDialog();
|
||||
}
|
||||
protected override async void OnInitialized()
|
||||
{
|
||||
try
|
||||
{
|
||||
//await LoadModules();
|
||||
base.OnInitialized();
|
||||
|
||||
_appSplashScreen.Close();
|
||||
_appSplashScreen = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Fatal(ex);
|
||||
Debug.WriteLine(ex);
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
//base.OnInitialized();
|
||||
//// 登录弹窗关闭与主窗口创建之间不触发 WPF 的最后窗口退出。
|
||||
//ShutdownMode = ShutdownMode.OnExplicitShutdown;
|
||||
@@ -51,11 +76,18 @@ public partial class App : FrameworkApplication
|
||||
{
|
||||
|
||||
containerRegistry.RegisterDialogWindow<Framework.Controls.DialogWindow>();
|
||||
containerRegistry.RegisterDialog<UserEditorDialog, UserEditorDialogViewModel>();
|
||||
|
||||
containerRegistry.RegisterDialog<DataGridTestView, DataGridTestViewModel>();
|
||||
|
||||
ViewModelLocationProvider.Register<MainWindow, MainWindowViewModel>();
|
||||
containerRegistry.RegisterDialog<LoginWindow, LoginViewModel>();
|
||||
|
||||
|
||||
containerRegistry.RegisterForNavigation<UserManagementPage, UserManagementPageViewModel>(nameof(UserManagementPage));
|
||||
containerRegistry.RegisterForNavigation<DataGridTestView, DataGridTestViewModel>(nameof(DataGridTestView));
|
||||
|
||||
|
||||
// registry.RegisterSingleton<SingletonProbe>();
|
||||
// registry.Register<TransientProbe>();
|
||||
// registry.RegisterSingleton<NotificationExampleState>();
|
||||
|
||||
@@ -1,31 +1,45 @@
|
||||
<f:WindowBase
|
||||
<f:WindowBase
|
||||
x:Class="Store.Management.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:f="Store.Management.Framework"
|
||||
xmlns:views="clr-namespace:Store.Management.Views"
|
||||
xmlns:local="clr-namespace:Store.Management.Views"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
Title="{Binding Title}"
|
||||
Width="960"
|
||||
Width="1140"
|
||||
Height="720"
|
||||
MinWidth="720"
|
||||
MinWidth="900"
|
||||
MinHeight="560"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
<DockPanel>
|
||||
<!-- 控件测试入口 -->
|
||||
<Button
|
||||
Margin="16,12"
|
||||
Padding="16,6"
|
||||
HorizontalAlignment="Left"
|
||||
Command="{Binding OpenDataGridTestCommand, Mode=OneWay}"
|
||||
Content="打开 DataGrid 测试"
|
||||
DockPanel.Dock="Top" />
|
||||
<Button
|
||||
Margin="16,12"
|
||||
Padding="16,6"
|
||||
HorizontalAlignment="Left"
|
||||
Command="{Binding OpenLoginCommand, Mode=OneWay}"
|
||||
Content="登录 测试"
|
||||
DockPanel.Dock="Top" />
|
||||
<views:FrameworkTestPage />
|
||||
</DockPanel>
|
||||
</f:WindowBase>
|
||||
<Border>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="20" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="180" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border
|
||||
Grid.Column="0"
|
||||
BorderBrush="{StaticResource SecondaryBorderBrush}"
|
||||
BorderThickness="0,0,1,0">
|
||||
<local:LeftNavView />
|
||||
</Border>
|
||||
<!-- 右侧页面标题与内容 -->
|
||||
<DockPanel Grid.Column="1">
|
||||
<TextBlock
|
||||
Margin="16,16,16,0"
|
||||
DockPanel.Dock="Top"
|
||||
Text="{Binding SelectedItem.Header, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}" />
|
||||
<ContentControl x:Name="PART_SelectedContentHost" prism:RegionManager.RegionName="{Binding Source={x:Static f:Navigation.MainContext}}" />
|
||||
</DockPanel>
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
BorderBrush="{StaticResource SecondaryBorderBrush}"
|
||||
BorderThickness="0,1,0,0" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</f:WindowBase>
|
||||
@@ -0,0 +1,68 @@
|
||||
|
||||
using MAX.Common.Helpers;
|
||||
using PipeReviewReport.Resources.Fonts;
|
||||
using Prism.Commands;
|
||||
using Prism.Mvvm;
|
||||
using Prism.Regions;
|
||||
using Store.Management.Framework;
|
||||
using Store.Management.Views;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Store.Management.ViewModels
|
||||
{
|
||||
internal class LeftNavViewModel : BindableBase
|
||||
{
|
||||
private readonly IRegionManager _regionManager;
|
||||
|
||||
public ObservableCollection<NavItemViewModel> NavItems { get; } = [];
|
||||
public LeftNavViewModel(IRegionManager regionManager)
|
||||
{
|
||||
NavItems.Add(new NavItemViewModel
|
||||
{
|
||||
Code = "1",
|
||||
Title = "角色管理",
|
||||
Icon = IconNameEnum.Person,
|
||||
Url=nameof(DataGridTestView)
|
||||
});
|
||||
NavItems.Add(new NavItemViewModel
|
||||
{
|
||||
Code = "1",
|
||||
Title = "商铺管理",
|
||||
Icon = IconNameEnum.BuildingShop,
|
||||
IsSelected = true,
|
||||
Url=nameof(UserManagementPage)
|
||||
});
|
||||
_regionManager = regionManager;
|
||||
}
|
||||
|
||||
public ICommand NavItemCommand => new AsyncDelegateCommand<NavItemViewModel>(NavItem);
|
||||
|
||||
private async Task NavItem(NavItemViewModel model)
|
||||
{
|
||||
NavItems.ForEach(a => a.IsSelected = false);
|
||||
model.IsSelected = true;
|
||||
|
||||
_regionManager.RequestNavigate(Navigation.MainContext, model.Url);
|
||||
}
|
||||
}
|
||||
|
||||
public class NavItemViewModel : BindableBase
|
||||
{
|
||||
public string Code { get; set; }
|
||||
public string Title { get; set; }
|
||||
public IconNameEnum Icon { get; set; }
|
||||
public string Url { get; set; }
|
||||
private bool _isSelected;
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set => SetProperty(ref _isSelected, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,9 +52,11 @@ public sealed class LoginViewModel(ILoginService loginService) : ConfirmDialogVi
|
||||
|
||||
// 服务端要求 UTF-8 密码的 32 位大写 MD5,保留首尾空格。
|
||||
_passwordHash = string.IsNullOrEmpty(password)
|
||||
? "" : Convert.ToHexString(MD5.HashData(Encoding.UTF8.GetBytes(password)));
|
||||
? "" : HashPassword(password);
|
||||
}
|
||||
|
||||
internal static string HashPassword(string password) => Convert.ToHexString(MD5.HashData(Encoding.UTF8.GetBytes(password)));
|
||||
|
||||
public override ICommand ConfirmCommand => LoginCommand;
|
||||
|
||||
public override void OnDialogOpened(IDialogParameters parameters)
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
using Prism.Commands;
|
||||
using Prism.Services.Dialogs;
|
||||
using Store.Management.Api;
|
||||
using Store.Management.Api.Transport;
|
||||
using Store.Management.Api.Users;
|
||||
using Store.Management.Framework.Controls;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Store.Management.ViewModels;
|
||||
|
||||
public sealed class UserEditorDialogViewModel(StoreApiClient client) : ConfirmDialogViewModel
|
||||
{
|
||||
public const int UserNameLimit = 20;
|
||||
public const int PasswordLimit = 128;
|
||||
public const int NickNameLimit = 50;
|
||||
public const int MobileLimit = 30;
|
||||
public const int CompanyLimit = 100;
|
||||
public const int NotesLimit = 255;
|
||||
|
||||
private UserInfo? user;
|
||||
private string passwordHash = "";
|
||||
public bool IsCreate => user == null;
|
||||
public string UserName { get; set => SetProperty(ref field, value); } = "";
|
||||
public UserEditRequest Draft { get; private set => SetProperty(ref field, value); } = new();
|
||||
|
||||
public override void OnDialogOpened(IDialogParameters parameters)
|
||||
{
|
||||
user = parameters.GetValue<UserInfo?>("user");
|
||||
UserName = user?.UserName ?? "";
|
||||
Draft = user == null ? new UserCreateRequest() : new UserUpdateRequest
|
||||
{
|
||||
Id = user.Id, NickName = user.NickName, Mobile = user.Mobile,
|
||||
CompanyName = user.CompanyName, Notes = user.Notes
|
||||
};
|
||||
Title = IsCreate ? "创建用户" : "编辑用户";
|
||||
RaisePropertyChanged(nameof(IsCreate));
|
||||
}
|
||||
|
||||
public void UpdatePassword(string password)
|
||||
{
|
||||
passwordHash = password.Length >= 6 && !string.IsNullOrWhiteSpace(password)
|
||||
? LoginViewModel.HashPassword(password) : "";
|
||||
}
|
||||
|
||||
public override bool CanCloseDialog() => !IsLoading;
|
||||
public override ICommand ConfirmCommand => field ??= new AsyncDelegateCommand(SaveAsync);
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
if (Draft is UserCreateRequest create)
|
||||
{
|
||||
create.UserName = UserName.Trim();
|
||||
if (create.UserName.Length == 0 || passwordHash.Length == 0)
|
||||
{
|
||||
ErrorMsg = "请输入用户名和至少 6 位的初始密码。";
|
||||
return;
|
||||
}
|
||||
create.Password = passwordHash;
|
||||
}
|
||||
|
||||
IsLoading = true;
|
||||
ErrorMsg = "";
|
||||
try
|
||||
{
|
||||
if (Draft is UserCreateRequest newUser)
|
||||
{
|
||||
await client.Users.CreateAsync(newUser);
|
||||
}
|
||||
else
|
||||
{
|
||||
await client.Users.UpdateAsync((UserUpdateRequest)Draft);
|
||||
}
|
||||
}
|
||||
catch (ApiException ex)
|
||||
{
|
||||
ErrorMsg = ex.Message;
|
||||
return;
|
||||
}
|
||||
catch (ValidationException ex)
|
||||
{
|
||||
ErrorMsg = ex.Message;
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
OnRequestClose(new DialogResult(ButtonResult.OK));
|
||||
}
|
||||
|
||||
public override void OnDialogClosed()
|
||||
{
|
||||
passwordHash = "";
|
||||
if (Draft is UserCreateRequest create)
|
||||
{
|
||||
create.Password = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
using Prism.Commands;
|
||||
using Prism.Mvvm;
|
||||
using Prism.Services.Dialogs;
|
||||
using Store.Management.Api;
|
||||
using Store.Management.Api.Models;
|
||||
using Store.Management.Api.Transport;
|
||||
using Store.Management.Api.Users;
|
||||
using Store.Management.Framework.Controls;
|
||||
using Store.Management.Framework.ViewModels;
|
||||
using Store.Management.Views;
|
||||
using System.Windows;
|
||||
|
||||
namespace Store.Management.ViewModels;
|
||||
|
||||
public sealed class UserManagementPageViewModel(StoreApiClient client, IDialogService dialogs) : BindableBase
|
||||
{
|
||||
public const int KeywordLimit = 100;
|
||||
private string appliedKeyword = "";
|
||||
private int pageSize = 50;
|
||||
|
||||
public string Keyword { get; set; } = "";
|
||||
public LoadingStatus Status { get; } = new();
|
||||
public int Page { get; private set => SetProperty(ref field, value); } = 1;
|
||||
public int Total { get; private set => SetProperty(ref field, value); }
|
||||
public IReadOnlyList<UserInfo> Users { get; private set => SetProperty(ref field, value); } = [];
|
||||
public UserInfo? SelectedUser { get; set => SetProperty(ref field, value); }
|
||||
|
||||
public AsyncDelegateCommand SearchCommand => field ??= new(() => LoadAsync(Keyword.Trim(), 1, pageSize));
|
||||
public AsyncDelegateCommand<DataGridPageRequest> PageCommand => field ??= new(
|
||||
request => LoadAsync(appliedKeyword, request.PageIndex, request.PageSize), _ => !Status.IsLoading);
|
||||
public AsyncDelegateCommand CreateCommand => field ??= new(() => OpenEditorAsync(null));
|
||||
public AsyncDelegateCommand EditCommand => field ??= new AsyncDelegateCommand(() => OpenEditorAsync(SelectedUser),
|
||||
() => SelectedUser != null).ObservesProperty(() => SelectedUser);
|
||||
public AsyncDelegateCommand DeleteCommand => field ??= new AsyncDelegateCommand(DeleteAsync,
|
||||
() => SelectedUser != null).ObservesProperty(() => SelectedUser);
|
||||
|
||||
public AsyncDelegateCommand LoginCommand => field ??= new(LoginAsync);
|
||||
|
||||
private async Task LoginAsync()
|
||||
{
|
||||
ButtonResult result = ButtonResult.None;
|
||||
dialogs.ShowDialog(nameof(LoginWindow), new DialogParameters(), response => result = response.Result);
|
||||
if (result == ButtonResult.OK)
|
||||
{
|
||||
await LoadAsync(Keyword.Trim(), 1, pageSize);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadAsync(string keyword, int page, int size)
|
||||
{
|
||||
if (Status.IsLoading)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Status.Initialize("正在查询用户…");
|
||||
try
|
||||
{
|
||||
UserSearchRequest request = new() { Keyword = keyword, Page = page, PageSize = size };
|
||||
PageResult<UserInfo> result = await client.Users.SearchAsync(request);
|
||||
// 删除末页最后一条记录后,按服务端总数回到有效页。
|
||||
int lastPage = (int)Math.Clamp((result.Total - 1) / size + 1, 1, page);
|
||||
if (page > lastPage)
|
||||
{
|
||||
request.Page = lastPage;
|
||||
result = await client.Users.SearchAsync(request);
|
||||
}
|
||||
appliedKeyword = keyword;
|
||||
pageSize = size;
|
||||
SelectedUser = null;
|
||||
Users = result.Items;
|
||||
Total = (int)Math.Min(result.Total, int.MaxValue);
|
||||
Page = request.Page;
|
||||
}
|
||||
catch (ApiException ex)
|
||||
{
|
||||
SelectedUser = null;
|
||||
Users = [];
|
||||
Total = 0;
|
||||
Page = 1;
|
||||
appliedKeyword = keyword;
|
||||
pageSize = size;
|
||||
Status.Error(ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Status.Done();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OpenEditorAsync(UserInfo? user)
|
||||
{
|
||||
ButtonResult result = ButtonResult.None;
|
||||
dialogs.ShowDialog(nameof(UserEditorDialog), new DialogParameters { { "user", user } },
|
||||
response => result = response.Result);
|
||||
if (result == ButtonResult.OK)
|
||||
{
|
||||
await LoadAsync(appliedKeyword, user == null ? 1 : Page, pageSize);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteAsync()
|
||||
{
|
||||
UserInfo user = SelectedUser!;
|
||||
if (MessageBox.Show($"确定删除用户“{user.UserName}”?", "删除用户", MessageBoxButton.OKCancel,
|
||||
MessageBoxImage.Warning, MessageBoxResult.Cancel) != MessageBoxResult.OK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Status.Initialize("正在删除用户…");
|
||||
try
|
||||
{
|
||||
await client.Users.DeleteAsync(user.Id);
|
||||
}
|
||||
catch (ApiException ex)
|
||||
{
|
||||
Status.Error(ex.Message);
|
||||
return;
|
||||
}
|
||||
Status.Done();
|
||||
await LoadAsync(appliedKeyword, Page, pageSize);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<Window
|
||||
x:Class="Store.Management.Views.AppSplashScreen"
|
||||
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:Store.Management.Views"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Title="AppSplashScreen"
|
||||
Width="990"
|
||||
Height="570"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent"
|
||||
ResizeMode="NoResize"
|
||||
ShowInTaskbar="False"
|
||||
Topmost="True"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
WindowStyle="None"
|
||||
mc:Ignorable="d">
|
||||
<Border>
|
||||
<!--<Viewbox>
|
||||
<TextBlock Foreground="#dedede" Text="启动" />
|
||||
</Viewbox>-->
|
||||
</Border>
|
||||
</Window>
|
||||
@@ -0,0 +1,136 @@
|
||||
using Store.Management.Framework.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
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.Shapes;
|
||||
|
||||
namespace Store.Management.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// AppSplashScreen.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class AppSplashScreen : Window
|
||||
{
|
||||
public Action CloseCallBack { get; set; }
|
||||
public AppSplashScreen()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += StartView_Loaded;
|
||||
Unloaded += StartView_Unloaded;
|
||||
}
|
||||
|
||||
|
||||
protected override void OnSourceInitialized(EventArgs e)
|
||||
{
|
||||
base.OnSourceInitialized(e);
|
||||
Activate();
|
||||
}
|
||||
|
||||
private void StartView_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Activate();
|
||||
}
|
||||
|
||||
private void StartView_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void OnMouseDown(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseDown(e);
|
||||
if (e.ChangedButton == MouseButton.Left)
|
||||
{
|
||||
DragMove();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
protected override async void OnInitialized(EventArgs e)
|
||||
{
|
||||
base.OnInitialized(e);
|
||||
try
|
||||
{
|
||||
|
||||
await LoadApp();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadApp()
|
||||
{
|
||||
SetProgress(0);
|
||||
Exception exception = null;
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadDLLs();
|
||||
SetProgress(15);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
}
|
||||
});
|
||||
|
||||
if (exception != null)
|
||||
{
|
||||
Debug.WriteLine(exception);
|
||||
SetError(exception);
|
||||
}
|
||||
|
||||
|
||||
SetProgress(75);
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
CloseCallBack?.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SetError(ex);
|
||||
Close();
|
||||
}
|
||||
finally
|
||||
{
|
||||
SetProgress(100);
|
||||
}
|
||||
});
|
||||
}
|
||||
private void LoadDLLs()
|
||||
{
|
||||
}
|
||||
protected override void OnClosing(CancelEventArgs e)
|
||||
{
|
||||
base.OnClosing(e);
|
||||
}
|
||||
|
||||
public void SetProgress(double progress)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
//Progress.Value = progress;
|
||||
});
|
||||
}
|
||||
private void SetError(Exception ex)
|
||||
{
|
||||
ApplicationLogService.WriteFatal(ex, "AppSplashScreen");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<UserControl
|
||||
x:Class="Store.Management.Views.LeftNavView"
|
||||
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:f="Store.Management.Framework"
|
||||
xmlns:local="clr-namespace:Store.Management.Views"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:Store.Management.ViewModels"
|
||||
xmlns:wpf="MAX.WPF"
|
||||
d:Background="White"
|
||||
d:DataContext="{d:DesignInstance Type=vm:LeftNavViewModel}"
|
||||
d:DesignHeight="450"
|
||||
d:Width="255"
|
||||
Background="White"
|
||||
DataContext="{f:DISource Type=vm:LeftNavViewModel}"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<wpf:CommandProxy x:Key="NavItemCommand" Command="{Binding NavItemCommand}" />
|
||||
<Style
|
||||
x:Key="NavItemButton"
|
||||
BasedOn="{StaticResource ButtonCustom}"
|
||||
TargetType="Button">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryBrush}" />
|
||||
</Trigger>
|
||||
<DataTrigger Binding="{Binding IsSelected}" Value="True">
|
||||
<Setter Property="Foreground" Value="{StaticResource PrimaryBrush}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<DataTemplate x:Key="NavItemTemplate" DataType="{x:Type vm:NavItemViewModel}">
|
||||
<Button
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Command="{StaticResource NavItemCommand}"
|
||||
CommandParameter="{Binding}"
|
||||
Style="{StaticResource NavItemButton}">
|
||||
<Button.Content>
|
||||
<Border
|
||||
Height="45"
|
||||
Background="Transparent"
|
||||
BorderBrush="{StaticResource SecondaryBorderBrush}"
|
||||
BorderThickness="0,0,0,1"
|
||||
Cursor="Hand">
|
||||
<Grid>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<f:IconText
|
||||
Margin="10,0,10,0"
|
||||
FontSize="18"
|
||||
Icon="{Binding Icon}" />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
Text="{Binding Title}" />
|
||||
</StackPanel>
|
||||
<Border
|
||||
Margin="0,0,10,0"
|
||||
HorizontalAlignment="Right"
|
||||
Visibility="{Binding IsSelected, Converter={StaticResource Boolean2VisibilityConverter}}">
|
||||
<f:IconText Icon="ChevronRight" />
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
</Border>
|
||||
</Button.Content>
|
||||
</Button>
|
||||
|
||||
</DataTemplate>
|
||||
</UserControl.Resources>
|
||||
<ItemsControl
|
||||
d:ItemsSource="{d:SampleData ItemCount=10}"
|
||||
ItemTemplate="{StaticResource NavItemTemplate}"
|
||||
ItemsSource="{Binding NavItems}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<wpf:SpacingStackPanel Orientation="Vertical" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
|
||||
</ItemsControl>
|
||||
|
||||
</UserControl>
|
||||
@@ -0,0 +1,28 @@
|
||||
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 Store.Management.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// LeftNavView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class LeftNavView : UserControl
|
||||
{
|
||||
public LeftNavView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
Height="620"
|
||||
MinWidth="440"
|
||||
MinHeight="540"
|
||||
d:Background="White"
|
||||
d:DataContext="{d:DesignInstance Type=vm:LoginViewModel}"
|
||||
Background="{DynamicResource BackgroundBrush}"
|
||||
Foreground="{DynamicResource PrimaryTextBrush}"
|
||||
@@ -21,6 +22,7 @@
|
||||
<StackPanel MaxWidth="360">
|
||||
<!-- 系统标识与登录说明 -->
|
||||
<TextBlock
|
||||
d:Text="TextBlock"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PrimaryBrush}"
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<UserControl x:Class="Store.Management.Views.UserEditorDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:wpf="MAX.WPF"
|
||||
xmlns:vm="clr-namespace:Store.Management.ViewModels" Width="480" Height="460">
|
||||
<UserControl.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="ShowWhenTrue"/>
|
||||
<Style TargetType="wpf:TitleContent" BasedOn="{StaticResource {x:Type wpf:TitleContent}}">
|
||||
<Setter Property="Placement" Value="Left"/>
|
||||
<Setter Property="TitleWidth" Value="80"/>
|
||||
<Setter Property="TitleVerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
<DockPanel Margin="24" IsEnabled="{Binding EnableConfirm, Mode=OneWay}">
|
||||
<!-- 保存结果与操作 -->
|
||||
<wpf:SpacingStackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right" HorizontalSpacing="8">
|
||||
<Button Content="取消" Command="{Binding CancelCommand}"/>
|
||||
<Button Content="保存" Command="{Binding ConfirmCommand}"/>
|
||||
</wpf:SpacingStackPanel>
|
||||
<!-- 用户资料 -->
|
||||
<wpf:SpacingStackPanel VerticalSpacing="10" Orientation="Vertical">
|
||||
<wpf:TitleContent Title="用户名">
|
||||
<TextBox AutomationProperties.Name="用户名" IsEnabled="{Binding IsCreate, Mode=OneWay}"
|
||||
MaxLength="{x:Static vm:UserEditorDialogViewModel.UserNameLimit}"
|
||||
Text="{Binding UserName, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</wpf:TitleContent>
|
||||
<wpf:TitleContent Title="初始密码" Visibility="{Binding IsCreate, Mode=OneWay, Converter={StaticResource ShowWhenTrue}}">
|
||||
<PasswordBox AutomationProperties.Name="初始密码" MaxLength="{x:Static vm:UserEditorDialogViewModel.PasswordLimit}"
|
||||
PasswordChanged="OnPasswordChanged"/>
|
||||
</wpf:TitleContent>
|
||||
<wpf:TitleContent Title="昵称">
|
||||
<TextBox AutomationProperties.Name="昵称"
|
||||
MaxLength="{x:Static vm:UserEditorDialogViewModel.NickNameLimit}"
|
||||
Text="{Binding Draft.NickName, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</wpf:TitleContent>
|
||||
<wpf:TitleContent Title="手机号">
|
||||
<TextBox AutomationProperties.Name="手机号"
|
||||
MaxLength="{x:Static vm:UserEditorDialogViewModel.MobileLimit}"
|
||||
Text="{Binding Draft.Mobile, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</wpf:TitleContent>
|
||||
<wpf:TitleContent Title="公司名称">
|
||||
<TextBox AutomationProperties.Name="公司名称"
|
||||
MaxLength="{x:Static vm:UserEditorDialogViewModel.CompanyLimit}"
|
||||
Text="{Binding Draft.CompanyName, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</wpf:TitleContent>
|
||||
<wpf:TitleContent Title="备注">
|
||||
<TextBox AutomationProperties.Name="备注"
|
||||
MaxLength="{x:Static vm:UserEditorDialogViewModel.NotesLimit}"
|
||||
Text="{Binding Draft.Notes, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</wpf:TitleContent>
|
||||
<TextBlock Text="{Binding ErrorMsg, Mode=OneWay}" Foreground="{DynamicResource DangerBrush}" TextWrapping="Wrap"/>
|
||||
</wpf:SpacingStackPanel>
|
||||
</DockPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,15 @@
|
||||
using Store.Management.ViewModels;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Store.Management.Views;
|
||||
|
||||
public partial class UserEditorDialog : UserControl
|
||||
{
|
||||
public UserEditorDialog() => InitializeComponent();
|
||||
|
||||
private void OnPasswordChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
((UserEditorDialogViewModel)DataContext).UpdatePassword(((PasswordBox)sender).Password);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<UserControl
|
||||
x:Class="Store.Management.Views.UserManagementPage"
|
||||
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:f="Store.Management.Framework"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:Store.Management.ViewModels"
|
||||
xmlns:wpf="MAX.WPF"
|
||||
d:Background="White"
|
||||
d:DesignHeight="760"
|
||||
d:DesignWidth="1280"
|
||||
Loaded="OnLoaded"
|
||||
mc:Ignorable="d">
|
||||
<DockPanel Margin="16">
|
||||
<!-- 查询及用户操作 -->
|
||||
<wpf:SpacingStackPanel
|
||||
Margin="0,0,0,12"
|
||||
DockPanel.Dock="Top"
|
||||
HorizontalSpacing="8"
|
||||
Orientation="Horizontal">
|
||||
<wpf:SpacingStackPanel.Style>
|
||||
<Style TargetType="wpf:SpacingStackPanel">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Status.IsLoading, Mode=OneWay}" Value="True">
|
||||
<Setter Property="IsEnabled" Value="False" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</wpf:SpacingStackPanel.Style>
|
||||
<Button Command="{Binding LoginCommand}" Content="登录" />
|
||||
<wpf:TitleContent
|
||||
Title="关键词"
|
||||
Placement="Left"
|
||||
TitleVerticalAlignment="Center">
|
||||
<TextBox
|
||||
Width="175"
|
||||
AutomationProperties.Name="用户查询关键词"
|
||||
MaxLength="{x:Static vm:UserManagementPageViewModel.KeywordLimit}"
|
||||
Text="{Binding Keyword, UpdateSourceTrigger=PropertyChanged}">
|
||||
<TextBox.InputBindings>
|
||||
<KeyBinding Key="Enter" Command="{Binding SearchCommand}" />
|
||||
</TextBox.InputBindings>
|
||||
</TextBox>
|
||||
</wpf:TitleContent>
|
||||
<Button Command="{Binding SearchCommand}" Content="查询" />
|
||||
<Button Command="{Binding CreateCommand}" Content="创建用户" />
|
||||
<Button Command="{Binding EditCommand}" Content="编辑" />
|
||||
<Button Command="{Binding DeleteCommand}" Content="删除" />
|
||||
</wpf:SpacingStackPanel>
|
||||
<TextBlock
|
||||
DockPanel.Dock="Bottom"
|
||||
Foreground="{DynamicResource DangerBrush}"
|
||||
Text="{Binding Status.ErrorMessage, Mode=OneWay}"
|
||||
TextWrapping="Wrap" />
|
||||
<!-- 用户列表与服务端分页 -->
|
||||
<f:DataGridEx
|
||||
AutoGenerateColumns="False"
|
||||
CanUserSortColumns="False"
|
||||
IsLoading="{Binding Status.IsLoading, Mode=OneWay}"
|
||||
IsReadOnly="True"
|
||||
ItemsSource="{Binding Users, Mode=OneWay}"
|
||||
LoadingText="{Binding Status.Message, Mode=OneWay}"
|
||||
NoDataTips="没有符合条件的用户"
|
||||
PageChangedCommand="{Binding PageCommand}"
|
||||
PageSize="50"
|
||||
SelectedItem="{Binding SelectedUser, UpdateSourceTrigger=PropertyChanged}"
|
||||
SelectionMode="Single"
|
||||
ShowCheckBox="False"
|
||||
ShowPagination="True"
|
||||
ShowToolbar="False"
|
||||
TotalItemCount="{Binding Total, Mode=OneWay}"
|
||||
PageIndex="{Binding Page, Mode=OneWay}">
|
||||
<f:DataGridEx.Columns>
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding UserName}"
|
||||
Header="用户名" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding NickName}"
|
||||
Header="昵称" />
|
||||
<DataGridTextColumn
|
||||
Width="125"
|
||||
Binding="{Binding Mobile}"
|
||||
Header="手机号" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding CompanyName}"
|
||||
Header="公司名称" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Binding="{Binding Notes}"
|
||||
Header="备注" />
|
||||
</f:DataGridEx.Columns>
|
||||
</f:DataGridEx>
|
||||
</DockPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,16 @@
|
||||
using Store.Management.Framework.Controls;
|
||||
using Store.Management.ViewModels;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Store.Management.Views;
|
||||
|
||||
public partial class UserManagementPage : UserControl
|
||||
{
|
||||
public UserManagementPage() => InitializeComponent();
|
||||
|
||||
private async void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
//await ((UserManagementPageViewModel)DataContext).SearchCommand.Execute();
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,13 @@ handler.Json = """{"code":200,"data":{"total":1,"items":[{"id":3,"userName":"ali
|
||||
var page = await api.Users.SearchAsync(new UserSearchRequest { Page = 2, PageSize = 10 });
|
||||
Check(page.Total == 1 && page.Items[0].UserName == "alice", "分页解析失败");
|
||||
Check(handler.Authorization == "Bearer session-one" && handler.Body!.Contains("\"page\":2"), "Token 或分页请求体错误");
|
||||
handler.Json = """{"code":200,"data":null}""";
|
||||
await api.Users.DeleteAsync(3);
|
||||
Check(handler.Path.EndsWith("/user/DelUser") && handler.Query == "?id=3" && handler.Method == HttpMethod.Post,
|
||||
"删除用户必须 POST 并通过 query 指定 ID");
|
||||
var count = handler.Count;
|
||||
try { await api.Users.DeleteAsync(0); throw new Exception("未校验用户 ID"); }
|
||||
catch (ArgumentOutOfRangeException) { Check(handler.Count == count, "无效 ID 不应发送"); }
|
||||
try { await api.Users.SearchAsync(new UserSearchRequest { Page = 0 }); throw new Exception("未校验分页"); }
|
||||
catch (ValidationException) { Check(handler.Count == count, "无效参数不应发送"); }
|
||||
handler.Json = """{"code":2001,"msg":"业务失败","traceId":"trace-1","data":null}""";
|
||||
@@ -153,6 +159,8 @@ sealed class StubHandler : HttpMessageHandler
|
||||
public HttpStatusCode Status { get; set; } = HttpStatusCode.OK;
|
||||
public int Count { get; private set; }
|
||||
public string Path { get; private set; } = "";
|
||||
public string? Query { get; private set; }
|
||||
public HttpMethod? Method { get; private set; }
|
||||
public string? Authorization { get; private set; }
|
||||
public string? Body { get; private set; }
|
||||
public Func<CancellationToken, Task>? BeforeResponse { get; set; }
|
||||
@@ -160,6 +168,8 @@ sealed class StubHandler : HttpMessageHandler
|
||||
{
|
||||
Count++;
|
||||
Path = request.RequestUri!.AbsolutePath;
|
||||
Query = request.RequestUri.Query;
|
||||
Method = request.Method;
|
||||
Authorization = request.Headers.Authorization?.ToString();
|
||||
Body = request.Content is null ? null : await request.Content.ReadAsStringAsync(cancellationToken);
|
||||
HttpStatusCode status = Status;
|
||||
|
||||
Reference in new issue
Block a user