提交用户部分代码
This commit is contained in:
1 parent
9b9e4cbd82
commit
127fc22de5
26 files changed
+1110
-206
No files matched your search
@@ -20,9 +20,13 @@ public sealed class UserApi(ApiTransport transport)
|
||||
public Task<UserInfo> CreateAsync(UserCreateRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
return transport.GetRequiredAsync<UserInfo>("user/AddUser", Method.Post, request, cancellationToken: cancellationToken);
|
||||
string route = request.GroupId is null or UserGroup.MainAccount ? "user/AddUser" : "user/AddSubUser";
|
||||
return transport.GetRequiredAsync<UserInfo>(route, Method.Post, request, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task ChangePasswordAsync(ChangePasswordRequest request, CancellationToken cancellationToken = default)
|
||||
=> transport.SendAsync<object>("user/ChangePassword", Method.Post, request, cancellationToken: cancellationToken);
|
||||
|
||||
public Task DeleteAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(id);
|
||||
@@ -31,6 +35,9 @@ public sealed class UserApi(ApiTransport transport)
|
||||
cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task<UserInfo> UpdateBillingAsync(UserBillingUpdateRequest request, CancellationToken cancellationToken = default)
|
||||
=> transport.GetRequiredAsync<UserInfo>("user/UpdateUser", Method.Post, request, cancellationToken: cancellationToken);
|
||||
|
||||
public Task<UserInfo> UpdateAsync(UserUpdateRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Store.Management.Api.Users
|
||||
{
|
||||
public enum UserGroup : byte
|
||||
{
|
||||
/// <summary>主号。</summary>
|
||||
[Description("主号")] MainAccount = 3,
|
||||
/// <summary>客服。</summary>
|
||||
[Description("客服")] CustomerService = 4,
|
||||
/// <summary>加盟(旧系统分组 5)。</summary>
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
using Store.Management.Api.Models;
|
||||
|
||||
namespace Store.Management.Api.Users;
|
||||
@@ -12,6 +13,10 @@ public sealed class UserSearchRequest : PageRequest
|
||||
|
||||
public class UserEditRequest
|
||||
{
|
||||
public UserGroup? GroupId { get; set; }
|
||||
public UserAdminLevel? SaLevel { get; set; }
|
||||
[StringLength(50)] public string? OemId { get; set; }
|
||||
[StringLength(1000)] public string? Authority { get; set; }
|
||||
[StringLength(50)] public string? NickName { get; set; }
|
||||
[StringLength(30)] public string? Mobile { get; set; }
|
||||
[StringLength(100)] public string? CompanyName { get; set; }
|
||||
@@ -25,11 +30,34 @@ public sealed class UserCreateRequest : UserEditRequest
|
||||
[StringLength(20)] public string? ParentUserName { get; set; }
|
||||
}
|
||||
|
||||
public sealed record ChangePasswordRequest(string UserName, string NewPassword, string? OldPassword = null);
|
||||
|
||||
public enum UserAdminLevel : short
|
||||
{
|
||||
[System.ComponentModel.Description("普通用户")] Normal = 0,
|
||||
[System.ComponentModel.Description("超级管理员")] SuperAdmin = 1
|
||||
}
|
||||
|
||||
public sealed class UserUpdateRequest : UserEditRequest
|
||||
{
|
||||
[Range(1, int.MaxValue)] public int Id { get; set; }
|
||||
}
|
||||
|
||||
public sealed class UserBillingUpdateRequest
|
||||
{
|
||||
[Range(1, int.MaxValue)] public int Id { get; init; }
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public int? ShopPointsPerMonth { get; set; }
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public decimal? PostagePerOrder { get; set; }
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public decimal? RefundPostagePerOrder { get; set; }
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public decimal? GiftOemAmountPerOrder { get; set; }
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public decimal? SyncLogisticsPerOrder { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>管理端当前使用的用户资料字段;不接收密码。</summary>
|
||||
public sealed class UserInfo
|
||||
{
|
||||
@@ -41,6 +69,16 @@ public sealed class UserInfo
|
||||
public string? Notes { get; init; }
|
||||
public string? ParentUserName { get; init; }
|
||||
public int GroupId { get; init; }
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public UserGroup? Group => GroupId is >= byte.MinValue and <= byte.MaxValue ? (UserGroup)GroupId : null;
|
||||
public UserAdminLevel? SaLevel { get; init; }
|
||||
public string? OemId { get; init; }
|
||||
public string? Authority { get; init; }
|
||||
public int ShopPointsPerMonth { get; init; }
|
||||
public decimal? PostagePerOrder { get; init; }
|
||||
public decimal? RefundPostagePerOrder { get; init; }
|
||||
public decimal? GiftOemAmountPerOrder { get; init; }
|
||||
public decimal? SyncLogisticsPerOrder { get; init; }
|
||||
public decimal Points { get; init; }
|
||||
public decimal? Balance { get; init; }
|
||||
}
|
||||
@@ -24,7 +24,7 @@ public sealed class FolderConfig : IFolderConfig
|
||||
/// <summary>当前用户的数据根目录:%LOCALAPPDATA%\Store.Management。</summary>
|
||||
public static string AppFolder => Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), AppName);
|
||||
|
||||
//, MAX.Core.Cryptography.MD5Helper.Md5_32(ExeFolder)
|
||||
public static string ConfigFolder => Path.Combine(AppFolder, "Config");
|
||||
public static string DataFolder => Path.Combine(AppFolder, "Data");
|
||||
public static string LoggingFolder => Path.Combine(AppFolder, "Logs");
|
||||
|
||||
@@ -602,7 +602,6 @@
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalContentAlignment="Center"
|
||||
AutomationProperties.Name="全选行"
|
||||
Command="{x:Static ext:DataGridEx.ToggleSelectAllCommand}"
|
||||
CommandTarget="{Binding RelativeSource={RelativeSource AncestorType={x:Type ext:DataGridEx}}}"
|
||||
IsChecked="{Binding AreAllRowsSelected, RelativeSource={RelativeSource AncestorType={x:Type ext:DataGridEx}}, Mode=OneWay}">
|
||||
@@ -992,7 +991,6 @@
|
||||
<CheckBox
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="选择行"
|
||||
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=DataGridRow}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Style="{StaticResource DataGridExCheckBoxStyle}" />
|
||||
</Border>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Store.Management.Framework.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Title="DialogWindow"
|
||||
Title="{Binding Title}"
|
||||
Width="800"
|
||||
Height="450"
|
||||
mc:Ignorable="d">
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
using MAX.Common.Helpers;
|
||||
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
|
||||
{
|
||||
internal class HorizontalIconText : Control
|
||||
{
|
||||
|
||||
public string Icon
|
||||
{
|
||||
get => (string)GetValue(IconProperty);
|
||||
set => SetValue(IconProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
|
||||
nameof(Icon),
|
||||
typeof(string),
|
||||
typeof(HorizontalIconText),
|
||||
new PropertyMetadata(null, OnOnIconChanged)
|
||||
);
|
||||
|
||||
public bool ShowIcon
|
||||
{
|
||||
get => (bool)GetValue(ShowIconProperty);
|
||||
private set => SetValue(ShowIconPropertyKey, value);
|
||||
}
|
||||
|
||||
private static readonly DependencyPropertyKey ShowIconPropertyKey = DependencyProperty.RegisterReadOnly(
|
||||
nameof(ShowIcon),
|
||||
typeof(bool),
|
||||
typeof(HorizontalIconText),
|
||||
new PropertyMetadata(false)
|
||||
);
|
||||
|
||||
public static readonly DependencyProperty ShowIconProperty = ShowIconPropertyKey.DependencyProperty;
|
||||
|
||||
|
||||
|
||||
|
||||
public bool ShowSpace
|
||||
{
|
||||
get => (bool)GetValue(ShowSpaceProperty);
|
||||
private set => SetValue(ShowSpacePropertyKey, value);
|
||||
}
|
||||
|
||||
private static readonly DependencyPropertyKey ShowSpacePropertyKey = DependencyProperty.RegisterReadOnly(
|
||||
nameof(ShowSpace),
|
||||
typeof(bool),
|
||||
typeof(HorizontalIconText),
|
||||
new PropertyMetadata(true)
|
||||
);
|
||||
|
||||
public static readonly DependencyProperty ShowSpaceProperty = ShowSpacePropertyKey.DependencyProperty;
|
||||
|
||||
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => (string)GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
|
||||
nameof(Text),
|
||||
typeof(string),
|
||||
typeof(HorizontalIconText),
|
||||
new PropertyMetadata(string.Empty)
|
||||
);
|
||||
|
||||
|
||||
|
||||
public double IconSize
|
||||
{
|
||||
get => (double)GetValue(IconSizeProperty);
|
||||
set => SetValue(IconSizeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IconSizeProperty = DependencyProperty.Register(
|
||||
nameof(IconSize),
|
||||
typeof(double),
|
||||
typeof(HorizontalIconText),
|
||||
new PropertyMetadata(14d)
|
||||
);
|
||||
|
||||
|
||||
|
||||
public double Spacing
|
||||
{
|
||||
get => (double)GetValue(SpacingProperty);
|
||||
set => SetValue(SpacingProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty SpacingProperty = DependencyProperty.Register(
|
||||
nameof(Spacing),
|
||||
typeof(double),
|
||||
typeof(HorizontalIconText),
|
||||
new PropertyMetadata(4d)
|
||||
);
|
||||
|
||||
|
||||
private static void OnOnIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is HorizontalIconText view)
|
||||
{
|
||||
view.ShowIcon = view.Icon.IsNotBlank();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
base.OnPropertyChanged(e);
|
||||
if (e.Property == ShowSpaceProperty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ShowSpace = ShowIcon && Text.IsNotBlank();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@ internal static class ViewLifecycle
|
||||
if (ReferenceEquals(previous, next)) return;
|
||||
view.SetValue(LoadedViewModelProperty, next);
|
||||
previous?.OnUnloaded();
|
||||
next?.OnLoaded();
|
||||
next?.NotifyLoaded();
|
||||
}
|
||||
|
||||
private static void OnUnloaded(object sender, RoutedEventArgs e)
|
||||
|
||||
@@ -5,6 +5,20 @@ namespace Store.Management.Framework.Controls;
|
||||
|
||||
public abstract class ViewModelBase : BindableBase
|
||||
{
|
||||
private bool hasLoaded;
|
||||
|
||||
internal void NotifyLoaded()
|
||||
{
|
||||
if (!hasLoaded)
|
||||
{
|
||||
hasLoaded = true;
|
||||
OnLoadedOnce();
|
||||
}
|
||||
OnLoaded();
|
||||
}
|
||||
|
||||
/// <summary>当前实例首次加载时调用,先于 OnLoaded。</summary>
|
||||
protected virtual void OnLoadedOnce() { }
|
||||
protected internal virtual void OnLoaded() { }
|
||||
protected internal virtual void OnUnloaded() { }
|
||||
protected internal virtual void OnActivated() { }
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
<ResourceDictionary
|
||||
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="clr-namespace:Store.Management.Framework.Controls"
|
||||
xmlns:wpf="MAX.WPF">
|
||||
xmlns:hc="https://handyorg.github.io/handycontrol"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:wpf="MAX.WPF"
|
||||
mc:Ignorable="d">
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Fonts/FontGeometry.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/MAX.Common.WPF;component/Resources/Generic.xaml" />
|
||||
@@ -37,6 +41,180 @@
|
||||
<Style BasedOn="{StaticResource WindowBase}" TargetType="f:WindowBase" />
|
||||
<Style BasedOn="{StaticResource DialogWindowStyle}" TargetType="f:DialogWindow" />
|
||||
|
||||
|
||||
<!--<Style x:Key="HorizontalSpacingPanel" TargetType="hc:UniformSpacingPanel">
|
||||
<Setter Property="Orientation" Value="Horizontal" />
|
||||
<Setter Property="HorizontalSpacing" Value="2" />
|
||||
<Setter Property="ItemVerticalAlignment" Value="Center" />
|
||||
</Style>-->
|
||||
|
||||
<!--<Style x:Key="HorizontalSpacingPanel2" TargetType="hc:UniformSpacingPanel">
|
||||
<Setter Property="Orientation" Value="Horizontal" />
|
||||
<Setter Property="HorizontalSpacing" Value="4" />
|
||||
<Setter Property="ItemVerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="VerticalSpacingPanel" TargetType="hc:UniformSpacingPanel">
|
||||
<Setter Property="Orientation" Value="Vertical" />
|
||||
<Setter Property="VerticalSpacing" Value="2" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="VerticalSpacingPanel2" TargetType="hc:UniformSpacingPanel">
|
||||
<Setter Property="Orientation" Value="Vertical" />
|
||||
<Setter Property="VerticalSpacing" Value="4" />
|
||||
</Style>-->
|
||||
|
||||
|
||||
<Style x:Key="HorizontalSpacingPanel" TargetType="wpf:SpacingStackPanel">
|
||||
<Setter Property="Orientation" Value="Horizontal" />
|
||||
<Setter Property="HorizontalSpacing" Value="2" />
|
||||
<Setter Property="ItemVerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="HorizontalSpacingPanel4" TargetType="wpf:SpacingStackPanel">
|
||||
<Setter Property="Orientation" Value="Horizontal" />
|
||||
<Setter Property="HorizontalSpacing" Value="4" />
|
||||
<Setter Property="ItemVerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="HorizontalSpacingPanel8" TargetType="wpf:SpacingStackPanel">
|
||||
<Setter Property="Orientation" Value="Horizontal" />
|
||||
<Setter Property="HorizontalSpacing" Value="8" />
|
||||
<Setter Property="ItemVerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
|
||||
<Style x:Key="VerticalSpacingPanel" TargetType="wpf:SpacingStackPanel">
|
||||
<Setter Property="Orientation" Value="Vertical" />
|
||||
<Setter Property="VerticalSpacing" Value="2" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="VerticalSpacingPanel4" TargetType="wpf:SpacingStackPanel">
|
||||
<Setter Property="Orientation" Value="Vertical" />
|
||||
<Setter Property="VerticalSpacing" Value="4" />
|
||||
</Style>
|
||||
<Style x:Key="VerticalSpacingPanel8" TargetType="wpf:SpacingStackPanel">
|
||||
<Setter Property="Orientation" Value="Vertical" />
|
||||
<Setter Property="VerticalSpacing" Value="8" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="HorizontalIconText"
|
||||
BasedOn="{StaticResource BaseStyle}"
|
||||
TargetType="f:HorizontalIconText">
|
||||
<Setter Property="Spacing" Value="4" />
|
||||
<Setter Property="Focusable" Value="False" />
|
||||
<Setter Property="IconSize" Value="16" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="f:HorizontalIconText">
|
||||
<Border>
|
||||
<DockPanel>
|
||||
<TextBlock
|
||||
FontSize="{TemplateBinding IconSize}"
|
||||
Style="{StaticResource TextBlockIcon}"
|
||||
Text="{TemplateBinding Icon}"
|
||||
Visibility="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ShowIcon, Converter={StaticResource Boolean2VisibilityConverter}}" />
|
||||
<Border Width="{TemplateBinding Spacing}" Visibility="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=ShowSpace, Converter={StaticResource Boolean2VisibilityConverter}}" />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Text="{TemplateBinding Text}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style BasedOn="{StaticResource HorizontalIconText}" TargetType="f:HorizontalIconText" />
|
||||
|
||||
<Style x:Key="Dialog" TargetType="UserControl">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Focusable" Value="True" />
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
|
||||
<Setter Property="Background" Value="White" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="DialogButtonStyle"
|
||||
BasedOn="{StaticResource ButtonDefault}"
|
||||
TargetType="Button">
|
||||
<Setter Property="MinWidth" Value="70" />
|
||||
<Setter Property="Height" Value="26" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="DialogPrimaryButtonStyle"
|
||||
BasedOn="{StaticResource ButtonPrimary}"
|
||||
TargetType="Button">
|
||||
<Setter Property="MinWidth" Value="70" />
|
||||
<Setter Property="Height" Value="26" />
|
||||
</Style>
|
||||
|
||||
|
||||
|
||||
|
||||
<Style
|
||||
x:Key="ConfirmDialog"
|
||||
BasedOn="{StaticResource Dialog}"
|
||||
TargetType="UserControl">
|
||||
<Setter Property="Background" Value="White" />
|
||||
<Setter Property="Padding" Value="10" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="UserControl">
|
||||
<Border
|
||||
d:DataContext="{d:DesignInstance Type=f:ConfirmDialogViewModel}"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter />
|
||||
</Border>
|
||||
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Padding="5,8"
|
||||
BorderBrush="{StaticResource BorderBrush}"
|
||||
BorderThickness="0,1,0,0">
|
||||
<Grid>
|
||||
<Border Padding="2,0">
|
||||
<f:HorizontalIconText
|
||||
d:Text="错误信息"
|
||||
Foreground="{StaticResource DangerBrush}"
|
||||
Icon="{StaticResource txt-warning}"
|
||||
Text="{Binding ErrorMsg}"
|
||||
Visibility="{Binding ErrorMsg, Converter={StaticResource String2VisibilityConverter}}" />
|
||||
</Border>
|
||||
<wpf:SpacingStackPanel HorizontalAlignment="Right" Style="{StaticResource HorizontalSpacingPanel4}">
|
||||
<Button Command="{Binding ConfirmCommand}" Style="{StaticResource DialogPrimaryButtonStyle}">
|
||||
<TextBlock Text="确认" />
|
||||
</Button>
|
||||
<Button
|
||||
HorizontalAlignment="Right"
|
||||
Command="{Binding CancelCommand}"
|
||||
Style="{StaticResource DialogButtonStyle}">
|
||||
<TextBlock Text="取消" />
|
||||
</Button>
|
||||
</wpf:SpacingStackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
|
||||
|
||||
|
||||
<Style
|
||||
x:Key="VerticalAlignment"
|
||||
BasedOn="{StaticResource TitleContentBaseStyle}"
|
||||
|
||||
@@ -10,6 +10,8 @@ public interface IUserService
|
||||
Task<PageResult<UserInfo>> SearchAsync(UserSearchRequest request, CancellationToken cancellationToken = default);
|
||||
Task<UserInfo> CreateAsync(UserCreateRequest request, CancellationToken cancellationToken = default);
|
||||
Task<UserInfo> UpdateAsync(UserUpdateRequest request, CancellationToken cancellationToken = default);
|
||||
Task<UserInfo> UpdateBillingAsync(UserBillingUpdateRequest request, CancellationToken cancellationToken = default);
|
||||
Task ChangePasswordAsync(ChangePasswordRequest request, CancellationToken cancellationToken = default);
|
||||
Task DeleteAsync(int id, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -24,6 +26,12 @@ internal sealed class UserService(StoreApiClient client) : IUserService, ISingle
|
||||
public Task<UserInfo> UpdateAsync(UserUpdateRequest request, CancellationToken cancellationToken = default)
|
||||
=> client.Users.UpdateAsync(request, cancellationToken);
|
||||
|
||||
public Task<UserInfo> UpdateBillingAsync(UserBillingUpdateRequest request, CancellationToken cancellationToken = default)
|
||||
=> client.Users.UpdateBillingAsync(request, cancellationToken);
|
||||
|
||||
public Task ChangePasswordAsync(ChangePasswordRequest request, CancellationToken cancellationToken = default)
|
||||
=> client.Users.ChangePasswordAsync(request, cancellationToken);
|
||||
|
||||
public Task DeleteAsync(int id, CancellationToken cancellationToken = default)
|
||||
=> client.Users.DeleteAsync(id, cancellationToken);
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using Store.Management.Notifications;
|
||||
using Store.Management.Services;
|
||||
using Store.Management.ViewModels;
|
||||
using Store.Management.Views;
|
||||
using Store.Management.Views.Dialogs;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
namespace Store.Management;
|
||||
@@ -76,6 +77,7 @@ public partial class App : FrameworkApplication
|
||||
{
|
||||
|
||||
containerRegistry.RegisterDialogWindow<Framework.Controls.DialogWindow>();
|
||||
containerRegistry.RegisterDialog<UserBillingDialog, UserBillingDialogViewModel>();
|
||||
containerRegistry.RegisterDialog<UserEditorDialog, UserEditorDialogViewModel>();
|
||||
|
||||
containerRegistry.RegisterDialog<DataGridTestView, DataGridTestViewModel>();
|
||||
|
||||
@@ -18,8 +18,40 @@ public sealed class UserEditorDialogViewModel(IUserService userService) : Confir
|
||||
public const int CompanyLimit = 100;
|
||||
public const int NotesLimit = 255;
|
||||
|
||||
public const int AgentCodeLimit = 50;
|
||||
public sealed class PermissionOption(string name)
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public bool IsSelected { get; set; }
|
||||
}
|
||||
|
||||
public PermissionOption[] Permissions { get; } =
|
||||
[
|
||||
new("微信回采"), new("货源选择"), new("评分考核"), new("爆款数据"),
|
||||
new("在线寄件"), new("佣金上货"), new("充值用户"), new("商品采集"),
|
||||
new("裂变调试"), new("学员数据"), new("定向品牌"), new("活动库存")
|
||||
];
|
||||
|
||||
private UserInfo? user;
|
||||
private string passwordHash = "";
|
||||
private string? passwordHash;
|
||||
private string? confirmationHash;
|
||||
private string? oldPasswordHash;
|
||||
public bool ShowOldPassword => !IsCreate && passwordHash != null;
|
||||
public string? ParentUserName { get; set; }
|
||||
public bool IsCreatingSubUser => IsCreate && Group != UserGroup.MainAccount;
|
||||
public UserGroup? Group
|
||||
{
|
||||
get;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref field, value))
|
||||
{
|
||||
RaisePropertyChanged(nameof(IsCreatingSubUser));
|
||||
}
|
||||
}
|
||||
}
|
||||
public UserAdminLevel Level { get; set => SetProperty(ref field, value); }
|
||||
public string AgentCode { get; set => SetProperty(ref field, value); } = "";
|
||||
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();
|
||||
@@ -33,14 +65,25 @@ public sealed class UserEditorDialogViewModel(IUserService userService) : Confir
|
||||
Id = user.Id, NickName = user.NickName, Mobile = user.Mobile,
|
||||
CompanyName = user.CompanyName, Notes = user.Notes
|
||||
};
|
||||
Group = user == null ? UserGroup.MainAccount : (UserGroup)user.GroupId;
|
||||
Level = user?.SaLevel ?? UserAdminLevel.Normal;
|
||||
AgentCode = user?.OemId ?? "";
|
||||
var granted = (user?.Authority ?? "").Split(',', StringSplitOptions.TrimEntries);
|
||||
foreach (var option in Permissions)
|
||||
{
|
||||
option.IsSelected = granted.Contains(option.Name);
|
||||
}
|
||||
Title = IsCreate ? "创建用户" : "编辑用户";
|
||||
RaisePropertyChanged(nameof(IsCreate));
|
||||
}
|
||||
|
||||
public void UpdatePassword(string password)
|
||||
public void UpdatePasswords(string password, string confirmation, string oldPassword)
|
||||
{
|
||||
passwordHash = password.Length >= 6 && !string.IsNullOrWhiteSpace(password)
|
||||
passwordHash = password.Length == 0 ? null : password.Length >= 6 && !string.IsNullOrWhiteSpace(password)
|
||||
? LoginViewModel.HashPassword(password) : "";
|
||||
oldPasswordHash = oldPassword.Length == 0 ? null : LoginViewModel.HashPassword(oldPassword);
|
||||
RaisePropertyChanged(nameof(ShowOldPassword));
|
||||
confirmationHash = confirmation.Length == 0 ? null : LoginViewModel.HashPassword(confirmation);
|
||||
}
|
||||
|
||||
public override bool CanCloseDialog() => !IsLoading;
|
||||
@@ -48,19 +91,53 @@ public sealed class UserEditorDialogViewModel(IUserService userService) : Confir
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Draft.NickName) || string.IsNullOrWhiteSpace(Draft.Mobile))
|
||||
{
|
||||
ErrorMsg = "请输入昵称和手机号。";
|
||||
return;
|
||||
}
|
||||
if (Draft.Mobile.Trim().Length != 11 || !Draft.Mobile.Trim().All(char.IsAsciiDigit))
|
||||
{
|
||||
ErrorMsg = "手机号应为 11 位数字。";
|
||||
return;
|
||||
}
|
||||
if (passwordHash == "" || (IsCreate && passwordHash == null))
|
||||
{
|
||||
ErrorMsg = "请输入至少 6 位密码。";
|
||||
return;
|
||||
}
|
||||
if (passwordHash != confirmationHash)
|
||||
{
|
||||
ErrorMsg = "两次输入的密码不一致。";
|
||||
return;
|
||||
}
|
||||
if (Group == null || (user != null && Group != (UserGroup)user.GroupId &&
|
||||
(Group == UserGroup.MainAccount || user.GroupId == (int)UserGroup.MainAccount)))
|
||||
{
|
||||
ErrorMsg = "请选择有效分组,已有账户不能切换主号和子账号。";
|
||||
return;
|
||||
}
|
||||
Draft.GroupId = user?.GroupId == (int?)Group ? null : Group;
|
||||
Draft.SaLevel = Level == (user?.SaLevel ?? UserAdminLevel.Normal) ? null : Level;
|
||||
Draft.OemId = AgentCode.Trim() == (user?.OemId ?? "") ? null : AgentCode.Trim();
|
||||
var granted = (user?.Authority ?? "").Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToHashSet();
|
||||
bool changed = false;
|
||||
foreach (var option in Permissions)
|
||||
{
|
||||
changed |= option.IsSelected ? granted.Add(option.Name) : granted.Remove(option.Name);
|
||||
}
|
||||
Draft.Authority = changed ? string.Join(",", granted) : null;
|
||||
|
||||
if (Draft is UserCreateRequest create)
|
||||
{
|
||||
create.UserName = UserName.Trim();
|
||||
if (create.UserName.Length == 0 || passwordHash.Length == 0)
|
||||
{
|
||||
ErrorMsg = "请输入用户名和至少 6 位的初始密码。";
|
||||
return;
|
||||
}
|
||||
create.Password = passwordHash;
|
||||
create.ParentUserName = IsCreatingSubUser ? ParentUserName : null;
|
||||
create.UserName = Draft.Mobile.Trim();
|
||||
create.Password = passwordHash!;
|
||||
}
|
||||
|
||||
IsLoading = true;
|
||||
ErrorMsg = "";
|
||||
bool profileSaved = false;
|
||||
try
|
||||
{
|
||||
if (Draft is UserCreateRequest newUser)
|
||||
@@ -69,12 +146,17 @@ public sealed class UserEditorDialogViewModel(IUserService userService) : Confir
|
||||
}
|
||||
else
|
||||
{
|
||||
await userService.UpdateAsync((UserUpdateRequest)Draft);
|
||||
user = await userService.UpdateAsync((UserUpdateRequest)Draft);
|
||||
profileSaved = true;
|
||||
if (passwordHash != null)
|
||||
{
|
||||
await userService.ChangePasswordAsync(new ChangePasswordRequest(UserName, passwordHash, oldPasswordHash));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ApiException ex)
|
||||
{
|
||||
ErrorMsg = ex.Message;
|
||||
ErrorMsg = profileSaved ? $"资料已保存,密码修改失败:{ex.Message}" : ex.Message;
|
||||
return;
|
||||
}
|
||||
catch (ValidationException ex)
|
||||
@@ -91,7 +173,7 @@ public sealed class UserEditorDialogViewModel(IUserService userService) : Confir
|
||||
|
||||
public override void OnDialogClosed()
|
||||
{
|
||||
passwordHash = "";
|
||||
passwordHash = confirmationHash = oldPasswordHash = null;
|
||||
if (Draft is UserCreateRequest create)
|
||||
{
|
||||
create.Password = "";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Prism.Commands;
|
||||
using Prism.Mvvm;
|
||||
using Prism.Regions;
|
||||
using Prism.Services.Dialogs;
|
||||
using Store.Management.Api.Models;
|
||||
using Store.Management.Api.Transport;
|
||||
@@ -7,6 +8,7 @@ using Store.Management.Api.Users;
|
||||
using Store.Management.Framework.Controls;
|
||||
using Store.Management.Framework.Services;
|
||||
using Store.Management.Views;
|
||||
using Store.Management.Views.Dialogs;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls.Primitives;
|
||||
|
||||
@@ -14,122 +16,124 @@ namespace Store.Management.ViewModels;
|
||||
|
||||
public sealed class UserManagementPageViewModel(IUserService userService, IDialogService dialogs, IStatusBarService statusBar) : ViewModelBase
|
||||
{
|
||||
public const int KeywordLimit = 100;
|
||||
private string appliedKeyword = "";
|
||||
private int pageSize = 50;
|
||||
public const int KeywordLimit = 100;
|
||||
private string appliedKeyword = "";
|
||||
private UserGroup? appliedGroup;
|
||||
private int pageSize = 50;
|
||||
|
||||
public string Keyword { get; set; } = "";
|
||||
private bool _canInteract = true;
|
||||
public bool CanInteract { get => _canInteract; private set => SetProperty(ref _canInteract, value); }
|
||||
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 string Keyword { get; set; } = "";
|
||||
public UserGroup? SelectedGroup { get; set; }
|
||||
private bool _canInteract = true;
|
||||
public bool CanInteract { get => _canInteract; private set => SetProperty(ref _canInteract, value); }
|
||||
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), _ => CanInteract);
|
||||
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);
|
||||
|
||||
protected override void OnLoaded()
|
||||
public AsyncDelegateCommand SearchCommand => field ??= new(() => LoadAsync(Keyword.Trim(), SelectedGroup, 1, pageSize));
|
||||
public AsyncDelegateCommand<DataGridPageRequest> PageCommand => field ??= new(
|
||||
request => LoadAsync(appliedKeyword, appliedGroup, request.PageIndex, request.PageSize), _ => CanInteract);
|
||||
public AsyncDelegateCommand CreateCommand => field ??= new(() => OpenEditorAsync(null));
|
||||
public AsyncDelegateCommand EditCommand => field ??= new AsyncDelegateCommand(() => OpenEditorAsync(SelectedUser),
|
||||
() => SelectedUser != null).ObservesProperty(() => SelectedUser);
|
||||
public AsyncDelegateCommand BillingCommand => field ??= new AsyncDelegateCommand(async () =>
|
||||
{
|
||||
base.OnLoaded();
|
||||
ButtonResult result = ButtonResult.None;
|
||||
dialogs.ShowDialog(nameof(UserBillingDialog), SelectedUser!, response => result = response.Result);
|
||||
if (result == ButtonResult.OK)
|
||||
{
|
||||
await LoadAsync(appliedKeyword, appliedGroup, Page, pageSize);
|
||||
}
|
||||
}, () => SelectedUser != null).ObservesProperty(() => SelectedUser);
|
||||
public AsyncDelegateCommand DeleteCommand => field ??= new AsyncDelegateCommand(DeleteAsync,
|
||||
() => SelectedUser != null).ObservesProperty(() => SelectedUser);
|
||||
|
||||
protected override void OnLoadedOnce()
|
||||
{
|
||||
_ = LoadAsync(Keyword.Trim(), SelectedGroup, 1, pageSize);
|
||||
}
|
||||
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 (!CanInteract)
|
||||
{
|
||||
return;
|
||||
}
|
||||
CanInteract = false;
|
||||
try
|
||||
{
|
||||
statusBar.Report("正在查询用户…");
|
||||
UserSearchRequest request = new() { Keyword = keyword, Page = page, PageSize = size };
|
||||
PageResult<UserInfo> result = await userService.SearchAsync(request);
|
||||
// 删除末页最后一条记录后,按服务端总数回到有效页。
|
||||
int lastPage = (int)Math.Clamp((result.Total - 1) / size + 1, 1, page);
|
||||
if (page > lastPage)
|
||||
{
|
||||
request.Page = lastPage;
|
||||
result = await userService.SearchAsync(request);
|
||||
}
|
||||
appliedKeyword = keyword;
|
||||
pageSize = size;
|
||||
SelectedUser = null;
|
||||
Users = result.Items;
|
||||
Total = (int)Math.Min(result.Total, int.MaxValue);
|
||||
Page = request.Page;
|
||||
private async Task LoadAsync(string keyword, UserGroup? group, int page, int size)
|
||||
{
|
||||
if (!CanInteract)
|
||||
{
|
||||
return;
|
||||
}
|
||||
CanInteract = false;
|
||||
try
|
||||
{
|
||||
statusBar.Report("正在查询用户…");
|
||||
UserSearchRequest request = new() { Keyword = keyword, GroupId = (int?)group, Page = page, PageSize = size };
|
||||
PageResult<UserInfo> result = await userService.SearchAsync(request);
|
||||
// 删除末页最后一条记录后,按服务端总数回到有效页。
|
||||
int lastPage = (int)Math.Clamp((result.Total - 1) / size + 1, 1, page);
|
||||
if (page > lastPage)
|
||||
{
|
||||
request.Page = lastPage;
|
||||
result = await userService.SearchAsync(request);
|
||||
}
|
||||
appliedKeyword = keyword;
|
||||
appliedGroup = group;
|
||||
pageSize = size;
|
||||
SelectedUser = null;
|
||||
Users = result.Items;
|
||||
Total = (int)Math.Min(result.Total, int.MaxValue);
|
||||
Page = request.Page;
|
||||
|
||||
statusBar.Report($"用户查询完成,共 {Total} 条。", StatusLevel.Success,100);
|
||||
}
|
||||
catch (ApiException ex)
|
||||
{
|
||||
SelectedUser = null;
|
||||
Users = [];
|
||||
Total = 0;
|
||||
Page = 1;
|
||||
appliedKeyword = keyword;
|
||||
pageSize = size;
|
||||
statusBar.Report(ex.Message, StatusLevel.Error);
|
||||
}
|
||||
|
||||
finally
|
||||
{
|
||||
CanInteract = true;
|
||||
}
|
||||
}
|
||||
statusBar.Report($"用户查询完成,共 {Total} 条。", StatusLevel.Success, 100);
|
||||
}
|
||||
catch (ApiException ex)
|
||||
{
|
||||
SelectedUser = null;
|
||||
Users = [];
|
||||
Total = 0;
|
||||
Page = 1;
|
||||
appliedKeyword = keyword;
|
||||
appliedGroup = group;
|
||||
pageSize = size;
|
||||
statusBar.Report(ex.Message, StatusLevel.Error);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
CanInteract = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteAsync()
|
||||
{
|
||||
UserInfo user = SelectedUser!;
|
||||
if (MessageBox.Show($"确定删除用户“{user.UserName}”?", "删除用户", MessageBoxButton.OKCancel,
|
||||
MessageBoxImage.Warning, MessageBoxResult.Cancel) != MessageBoxResult.OK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
CanInteract = false;
|
||||
try
|
||||
{
|
||||
statusBar.Report("正在删除用户…");
|
||||
await userService.DeleteAsync(user.Id);
|
||||
}
|
||||
catch (ApiException ex)
|
||||
{
|
||||
statusBar.Report(ex.Message, StatusLevel.Error);
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
CanInteract = true;
|
||||
}
|
||||
await LoadAsync(appliedKeyword, Page, pageSize);
|
||||
}
|
||||
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, appliedGroup, 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;
|
||||
}
|
||||
CanInteract = false;
|
||||
try
|
||||
{
|
||||
statusBar.Report("正在删除用户…");
|
||||
await userService.DeleteAsync(user.Id);
|
||||
}
|
||||
catch (ApiException ex)
|
||||
{
|
||||
statusBar.Report(ex.Message, StatusLevel.Error);
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
CanInteract = true;
|
||||
}
|
||||
await LoadAsync(appliedKeyword, appliedGroup, Page, pageSize);
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,6 @@
|
||||
Text="当前页实时搜索" />
|
||||
<TextBox
|
||||
Width="220"
|
||||
AutomationProperties.Name="当前页实时搜索"
|
||||
MaxLength="{x:Static vm:DataGridTestViewModel.SearchTextLimit}"
|
||||
Text="{Binding LocalKeyword, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ToolTip="输入即过滤当前页;清空恢复本页数据,不改变分页总数" />
|
||||
@@ -93,7 +92,6 @@
|
||||
Text="后台搜索(模拟)" />
|
||||
<TextBox
|
||||
Width="220"
|
||||
AutomationProperties.Name="后台搜索关键字"
|
||||
MaxLength="{x:Static vm:DataGridTestViewModel.SearchTextLimit}"
|
||||
Text="{Binding BackendKeyword, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ToolTip="按名称、ID、分组、子账号、手机号、状态或备注搜索全部样例" />
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<UserControl
|
||||
x:Class="Store.Management.Views.Dialogs.UserBillingDialog"
|
||||
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:hc="https://handyorg.github.io/handycontrol"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:wpf="MAX.WPF"
|
||||
Width="460"
|
||||
Height="310"
|
||||
d:Background="White"
|
||||
Style="{StaticResource ConfirmDialog}"
|
||||
mc:Ignorable="d">
|
||||
<ItemsControl
|
||||
d:ItemsSource="{d:SampleData ItemCount=5}"
|
||||
IsEnabled="{Binding EnableConfirm, Mode=OneWay}"
|
||||
ItemsSource="{Binding Fields, Mode=OneWay}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<wpf:SpacingStackPanel Style="{StaticResource VerticalSpacingPanel4}" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<wpf:TitleContent
|
||||
Title="{Binding Label, Mode=OneWay}"
|
||||
Placement="Left"
|
||||
TitleVerticalAlignment="Center"
|
||||
TitleWidth="200">
|
||||
<hc:NumericUpDown
|
||||
DecimalPlaces="{Binding DecimalPlaces, Mode=OneWay}"
|
||||
Increment="{Binding Increment, Mode=OneWay}"
|
||||
Maximum="{Binding Maximum, Mode=OneWay}"
|
||||
Minimum="{Binding Minimum, Mode=OneWay}"
|
||||
TextBoxBase.TextChanged="OnAmountTextChanged"
|
||||
ValueChanged="OnAmountValueChanged"
|
||||
Value="{Binding Value, Mode=OneWay}" />
|
||||
</wpf:TitleContent>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,127 @@
|
||||
using Prism.Commands;
|
||||
using Prism.Services.Dialogs;
|
||||
using Store.Management.Api.Transport;
|
||||
using Store.Management.Api.Users;
|
||||
using Store.Management.Framework.Controls;
|
||||
using Store.Management.Framework.Services;
|
||||
using NumericUpDown = HandyControl.Controls.NumericUpDown;
|
||||
using System.Globalization;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Store.Management.Views.Dialogs;
|
||||
|
||||
public partial class UserBillingDialog : UserControl
|
||||
{
|
||||
public UserBillingDialog() => InitializeComponent();
|
||||
|
||||
private void OnAmountValueChanged(object sender, HandyControl.Data.FunctionEventArgs<double> e)
|
||||
{
|
||||
if (sender is NumericUpDown { IsKeyboardFocusWithin: true, DataContext: UserBillingDialogViewModel.BillingField field })
|
||||
{
|
||||
field.Value = (decimal)e.Info;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAmountTextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
// 显式输入与控件初始值相同的金额时,Value 不会触发变化。
|
||||
if (sender is NumericUpDown { IsKeyboardFocusWithin: true, DataContext: UserBillingDialogViewModel.BillingField field }
|
||||
&& e.OriginalSource is TextBox input
|
||||
&& decimal.TryParse(input.Text, NumberStyles.Number, CultureInfo.CurrentCulture, out decimal amount)
|
||||
&& amount == field.Value)
|
||||
{
|
||||
field.Value = amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class UserBillingDialogViewModel(IUserService userService) : ConfirmDialogViewModel<UserInfo>
|
||||
{
|
||||
public sealed class BillingField(string label, decimal? original, decimal minimum,
|
||||
Action<UserBillingUpdateRequest, decimal> apply, bool integer = false)
|
||||
{
|
||||
private readonly decimal? originalValue = original;
|
||||
public string Label { get; } = label;
|
||||
public decimal? EnteredValue { get; private set; } = original;
|
||||
public decimal Value
|
||||
{
|
||||
get => EnteredValue ?? minimum;
|
||||
set => EnteredValue = value;
|
||||
}
|
||||
public decimal Minimum => Math.Min(originalValue ?? minimum, minimum);
|
||||
public decimal Maximum => Math.Max(originalValue ?? minimum, integer ? int.MaxValue : 999999m);
|
||||
public int DecimalPlaces => integer ? 0 : 2;
|
||||
public decimal Increment => integer ? 1m : 0.01m;
|
||||
|
||||
internal bool ApplyChange(UserBillingUpdateRequest request)
|
||||
{
|
||||
if (EnteredValue is not decimal value || value == originalValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
decimal maximum = integer ? int.MaxValue : 999999m;
|
||||
if (value < minimum || value > maximum || decimal.Round(value, DecimalPlaces) != value)
|
||||
{
|
||||
throw new FormatException($"{Label}范围为 {minimum} 至 {maximum}" + (integer ? ",请输入整数。" : ",最多两位小数。"));
|
||||
}
|
||||
apply(request, value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public BillingField[] Fields { get; private set => SetProperty(ref field, value); } = [];
|
||||
|
||||
public override void OnDialogOpened(IDialogParameters parameters)
|
||||
{
|
||||
base.OnDialogOpened(parameters);
|
||||
Title = $"计费配置 · {Parameter.UserName}";
|
||||
Fields =
|
||||
[
|
||||
new("店铺每月续费积分(积分/月)", Parameter.ShopPointsPerMonth, 30m, (request, value) => request.ShopPointsPerMonth = (int)value, integer: true),
|
||||
new("中转仓快递费(元/单)", Parameter.PostagePerOrder, 2.20m, (request, value) => request.PostagePerOrder = value),
|
||||
new("中转仓退货快递费(元/单)", Parameter.RefundPostagePerOrder, 2.50m, (request, value) => request.RefundPostagePerOrder = value),
|
||||
new("礼物单附加费(元/单)", Parameter.GiftOemAmountPerOrder, 0m, (request, value) => request.GiftOemAmountPerOrder = value),
|
||||
new("物流同步费(元/单)", Parameter.SyncLogisticsPerOrder, 0m, (request, value) => request.SyncLogisticsPerOrder = value)
|
||||
];
|
||||
}
|
||||
|
||||
public override bool CanCloseDialog() => !IsLoading;
|
||||
public override ICommand ConfirmCommand => field ??= new AsyncDelegateCommand(SaveAsync);
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
var request = new UserBillingUpdateRequest { Id = Parameter.Id };
|
||||
try
|
||||
{
|
||||
bool changed = false;
|
||||
foreach (BillingField field in Fields)
|
||||
{
|
||||
changed |= field.ApplyChange(request);
|
||||
}
|
||||
if (!changed)
|
||||
{
|
||||
OnRequestClose(new DialogResult(ButtonResult.Cancel));
|
||||
return;
|
||||
}
|
||||
ErrorMsg = "";
|
||||
IsLoading = true;
|
||||
await userService.UpdateBillingAsync(request);
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
ErrorMsg = ex.Message;
|
||||
return;
|
||||
}
|
||||
catch (ApiException ex)
|
||||
{
|
||||
ErrorMsg = ex.Message;
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
OnRequestClose(new DialogResult(ButtonResult.OK));
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,6 @@
|
||||
TitleWidth="50">
|
||||
<TextBox
|
||||
VerticalContentAlignment="Center"
|
||||
AutomationProperties.Name="用户名"
|
||||
IsEnabled="{Binding CanEdit, Mode=OneWay}"
|
||||
MaxLength="{x:Static vm:LoginViewModel.InputMaxLength}"
|
||||
Text="{Binding UserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
@@ -72,7 +71,6 @@
|
||||
<PasswordBox
|
||||
x:Name="PasswordBox"
|
||||
VerticalContentAlignment="Center"
|
||||
AutomationProperties.Name="密码"
|
||||
IsEnabled="{Binding CanEdit, Mode=OneWay}"
|
||||
MaxLength="{x:Static vm:LoginViewModel.InputMaxLength}"
|
||||
PasswordChanged="OnPasswordChanged" />
|
||||
|
||||
@@ -76,7 +76,6 @@
|
||||
d:Visibility="Visible"
|
||||
hc:BorderElement.CornerRadius="2"
|
||||
hc:VisualElement.Text=""
|
||||
AutomationProperties.Name="任务进度"
|
||||
Foreground="{Binding Path=(TextElement.Foreground), RelativeSource={RelativeSource AncestorType=Grid}}"
|
||||
Value="{Binding Progress, Mode=OneWay, TargetNullValue=0}">
|
||||
<ProgressBar.Style>
|
||||
|
||||
@@ -1,53 +1,99 @@
|
||||
<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
|
||||
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:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:users="clr-namespace:Store.Management.Api.Users;assembly=Store.Management.Api"
|
||||
xmlns:vm="clr-namespace:Store.Management.ViewModels"
|
||||
xmlns:wpf="MAX.WPF"
|
||||
Width="500"
|
||||
Height="620"
|
||||
Style="{StaticResource ConfirmDialog}"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="ShowWhenTrue" />
|
||||
<Style BasedOn="{StaticResource {x:Type wpf:TitleContent}}" TargetType="wpf:TitleContent">
|
||||
<Setter Property="Placement" Value="Left" />
|
||||
<Setter Property="TitleWidth" Value="80" />
|
||||
<Setter Property="TitleVerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
<ScrollViewer IsEnabled="{Binding EnableConfirm, Mode=OneWay}" VerticalScrollBarVisibility="Auto">
|
||||
<wpf:SpacingStackPanel Orientation="Vertical" VerticalSpacing="8">
|
||||
<wpf:TitleContent Title="昵称">
|
||||
<TextBox MaxLength="{x:Static vm:UserEditorDialogViewModel.NickNameLimit}" Text="{Binding Draft.NickName, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</wpf:TitleContent>
|
||||
<wpf:TitleContent Title="手机号">
|
||||
<TextBox MaxLength="{x:Static vm:UserEditorDialogViewModel.MobileLimit}" Text="{Binding Draft.Mobile, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</wpf:TitleContent>
|
||||
<wpf:TitleContent Title="密码">
|
||||
<PasswordBox
|
||||
x:Name="NewPassword"
|
||||
MaxLength="{x:Static vm:UserEditorDialogViewModel.PasswordLimit}"
|
||||
PasswordChanged="OnPasswordChanged" />
|
||||
</wpf:TitleContent>
|
||||
<wpf:TitleContent Title="重复密码">
|
||||
<PasswordBox
|
||||
x:Name="Confirmation"
|
||||
MaxLength="{x:Static vm:UserEditorDialogViewModel.PasswordLimit}"
|
||||
PasswordChanged="OnPasswordChanged" />
|
||||
</wpf:TitleContent>
|
||||
<wpf:TitleContent Title="原密码" Visibility="{Binding ShowOldPassword, Mode=OneWay, Converter={StaticResource ShowWhenTrue}}">
|
||||
<PasswordBox
|
||||
x:Name="OldPassword"
|
||||
MaxLength="{x:Static vm:UserEditorDialogViewModel.PasswordLimit}"
|
||||
PasswordChanged="OnPasswordChanged"
|
||||
ToolTip="普通用户修改自己的密码时填写" />
|
||||
</wpf:TitleContent>
|
||||
<wpf:TitleContent Title="分组">
|
||||
<ComboBox
|
||||
d:ItemsSource="{d:SampleData ItemCount=2}"
|
||||
DisplayMemberPath="Display"
|
||||
ItemsSource="{wpf:EnumBindingMarkup EnumType={x:Type users:UserGroup},
|
||||
Converter={StaticResource EnumDisplayConverter}}"
|
||||
SelectedValue="{Binding Group, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
SelectedValuePath="Value" />
|
||||
</wpf:TitleContent>
|
||||
<wpf:TitleContent Title="父账号" Visibility="{Binding IsCreatingSubUser, Mode=OneWay, Converter={StaticResource ShowWhenTrue}}">
|
||||
<TextBox MaxLength="{x:Static vm:UserEditorDialogViewModel.UserNameLimit}" Text="{Binding ParentUserName, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</wpf:TitleContent>
|
||||
<wpf:TitleContent Title="公司名称">
|
||||
<TextBox MaxLength="{x:Static vm:UserEditorDialogViewModel.CompanyLimit}" Text="{Binding Draft.CompanyName, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</wpf:TitleContent>
|
||||
<wpf:TitleContent Title="代理编码">
|
||||
<TextBox MaxLength="{x:Static vm:UserEditorDialogViewModel.AgentCodeLimit}" Text="{Binding AgentCode, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</wpf:TitleContent>
|
||||
<wpf:TitleContent Title="超管等级">
|
||||
<ComboBox
|
||||
d:ItemsSource="{d:SampleData ItemCount=2}"
|
||||
DisplayMemberPath="Display"
|
||||
ItemsSource="{wpf:EnumBindingMarkup EnumType={x:Type users:UserAdminLevel},
|
||||
Converter={StaticResource EnumDisplayConverter}}"
|
||||
SelectedValue="{Binding Level, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
SelectedValuePath="Value" />
|
||||
</wpf:TitleContent>
|
||||
<wpf:TitleContent Title="权限">
|
||||
<ItemsControl ItemsSource="{Binding Permissions, Mode=OneWay}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Columns="2" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox
|
||||
Margin="0,2"
|
||||
Content="{Binding Name, Mode=OneWay}"
|
||||
IsChecked="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</wpf:TitleContent>
|
||||
<wpf:TitleContent Title="备注">
|
||||
<TextBox MaxLength="{x:Static vm:UserEditorDialogViewModel.NotesLimit}" Text="{Binding Draft.Notes, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</wpf:TitleContent>
|
||||
<TextBlock Text="新增时使用手机号登录。编辑时密码留空即保留原密码。" TextWrapping="Wrap" />
|
||||
</wpf:SpacingStackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@@ -10,6 +10,6 @@ public partial class UserEditorDialog : UserControl
|
||||
|
||||
private void OnPasswordChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
((UserEditorDialogViewModel)DataContext).UpdatePassword(((PasswordBox)sender).Password);
|
||||
((UserEditorDialogViewModel)DataContext).UpdatePasswords(NewPassword.Password, Confirmation.Password, OldPassword.Password);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,9 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:f="Store.Management.Framework"
|
||||
xmlns:hc="https://handyorg.github.io/handycontrol"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:users="clr-namespace:Store.Management.Api.Users;assembly=Store.Management.Api"
|
||||
xmlns:vm="clr-namespace:Store.Management.ViewModels"
|
||||
xmlns:wpf="MAX.WPF"
|
||||
Margin="5"
|
||||
@@ -13,9 +15,19 @@
|
||||
d:DesignHeight="760"
|
||||
d:DesignWidth="1280"
|
||||
Background="White"
|
||||
Loaded="OnLoaded"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<UserControl.Resources>
|
||||
<wpf:BindingProxy x:Key="PageViewModel" Data="{Binding Mode=OneWay}" />
|
||||
<ContextMenu x:Key="UserRowMenu" DataContext="{Binding Data, Source={StaticResource PageViewModel}, Mode=OneWay}">
|
||||
<MenuItem Command="{Binding EditCommand}" Header="编辑" />
|
||||
<MenuItem Command="{Binding BillingCommand}" Header="计费配置" />
|
||||
<MenuItem Command="{Binding DeleteCommand}" Header="删除" />
|
||||
<Separator />
|
||||
<MenuItem Command="{Binding CreateCommand}" Header="创建用户" />
|
||||
</ContextMenu>
|
||||
</UserControl.Resources>
|
||||
|
||||
<DockPanel IsEnabled="{Binding CanInteract}">
|
||||
|
||||
<!-- 查询及用户操作 -->
|
||||
@@ -30,14 +42,28 @@
|
||||
<wpf:TitleContent Title="关键词" Style="{StaticResource VerticalAlignment}">
|
||||
<TextBox
|
||||
Width="175"
|
||||
AutomationProperties.Name="用户查询关键词"
|
||||
hc:InfoElement.Placeholder="用户名,昵称,公司"
|
||||
MaxLength="{x:Static vm:UserManagementPageViewModel.KeywordLimit}"
|
||||
Style="{StaticResource TextBoxExtend}"
|
||||
Text="{Binding Keyword, UpdateSourceTrigger=PropertyChanged}">
|
||||
<TextBox.InputBindings>
|
||||
<KeyBinding Key="Enter" Command="{Binding SearchCommand}" />
|
||||
</TextBox.InputBindings>
|
||||
</TextBox>
|
||||
</wpf:TitleContent>
|
||||
<wpf:TitleContent Title="用户组" Style="{StaticResource VerticalAlignment}">
|
||||
<ComboBox
|
||||
x:Name="GroupFilter"
|
||||
Width="120"
|
||||
d:ItemsSource="{d:SampleData ItemCount=8}"
|
||||
DisplayMemberPath="Display"
|
||||
ItemsSource="{wpf:EnumBindingMarkup EnumType={x:Type users:UserGroup},
|
||||
AllowNull=True,
|
||||
NullDisplay=全部,
|
||||
Converter={StaticResource EnumDisplayConverter}}"
|
||||
SelectedValue="{Binding SelectedGroup, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
SelectedValuePath="Value" />
|
||||
</wpf:TitleContent>
|
||||
<Button
|
||||
Command="{Binding SearchCommand}"
|
||||
Content="查询"
|
||||
@@ -50,14 +76,18 @@
|
||||
|
||||
<!-- 用户列表与服务端分页 -->
|
||||
<f:DataGridEx
|
||||
d:ItemsSource="{d:SampleData ItemCount=8}"
|
||||
AutoGenerateColumns="False"
|
||||
BorderThickness="1"
|
||||
CanUserSortColumns="False"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
IsReadOnly="True"
|
||||
ItemsSource="{Binding Users, Mode=OneWay}"
|
||||
NoDataTips="没有符合条件的用户"
|
||||
PageChangedCommand="{Binding PageCommand}"
|
||||
PageSize="50"
|
||||
RowContextMenu="{StaticResource UserRowMenu}"
|
||||
SelectRowOnRightClick="True"
|
||||
SelectedItem="{Binding SelectedUser, UpdateSourceTrigger=PropertyChanged}"
|
||||
SelectionMode="Single"
|
||||
ShowCheckBox="False"
|
||||
@@ -68,22 +98,47 @@
|
||||
<f:DataGridEx.Columns>
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
MinWidth="140"
|
||||
Binding="{Binding UserName}"
|
||||
Header="用户名" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
MinWidth="120"
|
||||
Binding="{Binding NickName}"
|
||||
Header="昵称" />
|
||||
<DataGridTextColumn
|
||||
Width="125"
|
||||
MinWidth="125"
|
||||
Binding="{Binding Mobile}"
|
||||
Header="手机号" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
MinWidth="180"
|
||||
Binding="{Binding CompanyName}"
|
||||
Header="公司名称" />
|
||||
<DataGridTextColumn
|
||||
Width="90"
|
||||
MinWidth="90"
|
||||
Binding="{Binding Group, Mode=OneWay, Converter={StaticResource EnumDisplayConverter}}"
|
||||
Header="分组" />
|
||||
<DataGridTextColumn
|
||||
Width="110"
|
||||
MinWidth="110"
|
||||
Binding="{Binding OemId, Mode=OneWay}"
|
||||
Header="代理编码" />
|
||||
<DataGridTextColumn
|
||||
Width="100"
|
||||
MinWidth="100"
|
||||
Binding="{Binding SaLevel, Mode=OneWay, Converter={StaticResource EnumDisplayConverter}}"
|
||||
Header="超管等级" />
|
||||
<DataGridTextColumn
|
||||
Width="Auto"
|
||||
MinWidth="220"
|
||||
Binding="{Binding Authority, Mode=OneWay}"
|
||||
Header="权限" />
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
MinWidth="160"
|
||||
Binding="{Binding Notes}"
|
||||
Header="备注" />
|
||||
</f:DataGridEx.Columns>
|
||||
|
||||
@@ -8,9 +8,4 @@ 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();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user