新增MES模块,包含供应商、客户、车辆和地磅数据记录管理功能,支持免密接口和数据同步。更新相关控制器、实体、服务和数据库配置,优化权限管理和数据字典支持,确保系统的灵活性和可扩展性。
This commit is contained in:
@@ -10,6 +10,7 @@ using YY.Admin.Core.Const;
|
||||
using YY.Admin.Core.EventBus;
|
||||
using YY.Admin.Core.Session;
|
||||
using YY.Admin.Services.Service.Auth;
|
||||
using YY.Admin.Services.Service.Config;
|
||||
using YY.Admin.Views;
|
||||
|
||||
namespace YY.Admin.ViewModels
|
||||
@@ -87,15 +88,58 @@ namespace YY.Admin.ViewModels
|
||||
}
|
||||
|
||||
#region 用户操作
|
||||
/// <summary>
|
||||
/// 从系统配置应用登录状态检查间隔(分钟)
|
||||
/// </summary>
|
||||
private static void ApplyTokenCheckIntervalFromConfig()
|
||||
{
|
||||
var minutes = 1;
|
||||
try
|
||||
{
|
||||
var cfg = Prism.Ioc.ContainerLocator.Current.Resolve<ISysConfigService>();
|
||||
var v = cfg.GetConfigValue<int>(ConfigConst.SysTokenCheckIntervalMinutes).GetAwaiter().GetResult();
|
||||
if (v >= 1 && v <= 120)
|
||||
minutes = v;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 使用默认 1 分钟
|
||||
}
|
||||
|
||||
if (_tokenCheckTimer != null)
|
||||
_tokenCheckTimer.Interval = TimeSpan.FromMinutes(minutes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否开启登录永不过期
|
||||
/// </summary>
|
||||
private static bool IsTokenNeverExpireEnabled()
|
||||
{
|
||||
try
|
||||
{
|
||||
var cfg = Prism.Ioc.ContainerLocator.Current.Resolve<ISysConfigService>();
|
||||
return cfg.GetConfigValue<bool>(ConfigConst.SysTokenNeverExpire).GetAwaiter().GetResult();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登录设置保存后刷新检查间隔(已启动定时器时立即生效)
|
||||
/// </summary>
|
||||
public static void RefreshTokenCheckIntervalFromConfig()
|
||||
{
|
||||
ApplyTokenCheckIntervalFromConfig();
|
||||
}
|
||||
|
||||
// 启动定时器的方法
|
||||
public static void StartTokenCheckTimer()
|
||||
{
|
||||
if (_tokenCheckTimer == null)
|
||||
{
|
||||
_tokenCheckTimer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromMinutes(1)
|
||||
};
|
||||
_tokenCheckTimer = new DispatcherTimer();
|
||||
_tokenCheckTimer.Tick += CheckTokenExpiration;
|
||||
|
||||
// 捕获全局用户输入事件
|
||||
@@ -107,6 +151,8 @@ namespace YY.Admin.ViewModels
|
||||
new KeyEventHandler(OnUserActivity));
|
||||
}
|
||||
|
||||
ApplyTokenCheckIntervalFromConfig();
|
||||
|
||||
if (!_tokenCheckTimer.IsEnabled)
|
||||
{
|
||||
_tokenCheckTimer.Start();
|
||||
@@ -124,6 +170,8 @@ namespace YY.Admin.ViewModels
|
||||
|
||||
private static void OnUserActivity(object sender, EventArgs e)
|
||||
{
|
||||
if (IsTokenNeverExpireEnabled())
|
||||
return;
|
||||
var authService = ContainerLocator.Current.Resolve<ISysAuthService>();
|
||||
authService.RefreshToken(UserContext?.Token?.AccessToken);
|
||||
}
|
||||
@@ -146,6 +194,9 @@ namespace YY.Admin.ViewModels
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsTokenNeverExpireEnabled())
|
||||
return;
|
||||
|
||||
var authService = ContainerLocator.Current.Resolve<ISysAuthService>();
|
||||
if (!authService.ValidateToken(UserContext.Token.AccessToken))
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Windows.Threading;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Util;
|
||||
using YY.Admin.Event;
|
||||
using Prism.Events;
|
||||
using YY.Admin.Module;
|
||||
using YY.Admin.Services;
|
||||
using YY.Admin.Services.Service.Menu;
|
||||
@@ -64,7 +65,40 @@ namespace YY.Admin.ViewModels.Control
|
||||
["/system/tenant"] = "TenantManagementView",
|
||||
["/system/tenant/index"] = "TenantManagementView",
|
||||
["/platform/tenant"] = "TenantManagementView",
|
||||
["sysTenant"] = "TenantManagementView"
|
||||
["sysTenant"] = "TenantManagementView",
|
||||
|
||||
// 已实现页面:菜单管理
|
||||
["MenuManagementView"] = "MenuManagementView",
|
||||
["/platform/menu"] = "MenuManagementView",
|
||||
["/system/menu/index"] = "MenuManagementView",
|
||||
["sysMenu"] = "MenuManagementView",
|
||||
|
||||
// 已实现页面:登录设置
|
||||
["LoginSettingsView"] = "LoginSettingsView",
|
||||
["/system/loginSettings"] = "LoginSettingsView",
|
||||
["/system/login-setting"] = "LoginSettingsView",
|
||||
["/system/login-settings"] = "LoginSettingsView",
|
||||
["loginSettings"] = "LoginSettingsView",
|
||||
["登录设置"] = "LoginSettingsView",
|
||||
["登陆设置"] = "LoginSettingsView",
|
||||
|
||||
// 已实现页面:车辆管理
|
||||
["VehicleListView"] = "VehicleListView",
|
||||
["/xslmes/mesXslVehicle"] = "VehicleListView",
|
||||
["/xslmes/vehicle"] = "VehicleListView",
|
||||
["mesXslVehicle"] = "VehicleListView",
|
||||
|
||||
// 已实现页面:客户管理
|
||||
["CustomerListView"] = "CustomerListView",
|
||||
["/xslmes/mesXslCustomer"] = "CustomerListView",
|
||||
["/xslmes/customer"] = "CustomerListView",
|
||||
["mesXslCustomer"] = "CustomerListView",
|
||||
|
||||
// 已实现页面:供应商管理
|
||||
["SupplierListView"] = "SupplierListView",
|
||||
["/xslmes/mesXslSupplier"] = "SupplierListView",
|
||||
["/xslmes/supplier"] = "SupplierListView",
|
||||
["mesXslSupplier"] = "SupplierListView"
|
||||
};
|
||||
|
||||
private MenuItem? _selectedMenuItem;
|
||||
@@ -83,6 +117,7 @@ namespace YY.Admin.ViewModels.Control
|
||||
|
||||
private SubscriptionToken? tabSelectedToken;
|
||||
private SubscriptionToken? tabClosedToken;
|
||||
private SubscriptionToken? _menuStructureToken;
|
||||
|
||||
public MenuTreeViewModel(
|
||||
ISysMenuService sysMenuService,
|
||||
@@ -99,10 +134,30 @@ namespace YY.Admin.ViewModels.Control
|
||||
// 订阅事件
|
||||
tabSelectedToken = _eventAggregator.GetEvent<TabSelectedEvent>().Subscribe(OnTabSelected);
|
||||
tabClosedToken = _eventAggregator.GetEvent<TabClosedEvent>().Subscribe(OnTabClosed);
|
||||
_menuStructureToken = _eventAggregator.GetEvent<MenuStructureChangedEvent>()
|
||||
.Subscribe(async _ => await ReloadMenusAsync(), ThreadOption.UIThread);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 菜单数据变更后刷新左侧树(不重复执行默认页签导航)
|
||||
/// </summary>
|
||||
private async Task ReloadMenusAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var menuTree = await _sysMenuService.GetLoginMenuTree();
|
||||
MenuItems.Clear();
|
||||
ConvertMenuTreeToViewModel(menuTree);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"菜单重新加载失败: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenOrActivateTab(MenuItem menuItem)
|
||||
{
|
||||
_logger.Debug($"菜单点击触发: Name={menuItem?.Name}, ViewName={menuItem?.ViewName}, ChildrenCount={menuItem?.Children?.Count ?? 0}");
|
||||
// 发布事件
|
||||
_eventAggregator.GetEvent<TabSourceSelectedEvent>().Publish(menuItem);
|
||||
}
|
||||
@@ -169,6 +224,7 @@ namespace YY.Admin.ViewModels.Control
|
||||
/// </summary>
|
||||
private void ConvertMenuTreeToViewModel(List<MenuOutput> menuTree)
|
||||
{
|
||||
MenuItems.Clear();
|
||||
// 过滤并排序菜单项:只包含目录和菜单类型,排除按钮类型,并按排序号排序
|
||||
var rootMenus = menuTree
|
||||
.Where(m => m.Type == MenuTypeEnum.Dir || m.Type == MenuTypeEnum.Menu)
|
||||
@@ -244,10 +300,14 @@ namespace YY.Admin.ViewModels.Control
|
||||
continue;
|
||||
|
||||
if (RouteToViewMap.TryGetValue(candidate.Trim(), out var viewName))
|
||||
{
|
||||
_logger.Debug($"菜单路由命中: Title={menu.Title}, Candidate={candidate}, View={viewName}");
|
||||
return viewName;
|
||||
}
|
||||
}
|
||||
|
||||
// 保留原始Path,若未注册将统一展示NotFoundView
|
||||
_logger.Warning($"菜单路由未命中映射: Title={menu.Title}, Path={menu.Path}, Component={menu.Component}, Name={menu.Name}");
|
||||
return menu.Path;
|
||||
}
|
||||
|
||||
@@ -336,6 +396,12 @@ namespace YY.Admin.ViewModels.Control
|
||||
.Unsubscribe(tabClosedToken);
|
||||
tabClosedToken = null;
|
||||
}
|
||||
|
||||
if (_menuStructureToken != null)
|
||||
{
|
||||
_eventAggregator.GetEvent<MenuStructureChangedEvent>().Unsubscribe(_menuStructureToken);
|
||||
_menuStructureToken = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
using HandyControl.Controls;
|
||||
using HandyControl.Tools.Extension;
|
||||
using System.Collections.ObjectModel;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Services.Service;
|
||||
|
||||
namespace YY.Admin.ViewModels.Customer;
|
||||
|
||||
public class CustomerEditDialogViewModel : BaseViewModel, IDialogResultable<bool>
|
||||
{
|
||||
private readonly ICustomerService _customerService;
|
||||
private readonly IJeecgDictSyncService _dictSyncService;
|
||||
|
||||
private MesXslCustomer? _customer;
|
||||
public MesXslCustomer? Customer
|
||||
{
|
||||
get => _customer;
|
||||
set => SetProperty(ref _customer, value);
|
||||
}
|
||||
|
||||
public bool IsAddMode => string.IsNullOrWhiteSpace(Customer?.Id) || (Customer?.Id?.StartsWith("local-") ?? false);
|
||||
public string DialogTitle => IsAddMode ? "新增客户" : "编辑客户";
|
||||
|
||||
public ObservableCollection<KeyValuePair<string, string>> StatusOptions { get; } = new();
|
||||
public ObservableCollection<KeyValuePair<string, string>> CustomerRegionOptions { get; } = new();
|
||||
|
||||
private bool _result;
|
||||
public bool Result { get => _result; set => SetProperty(ref _result, value); }
|
||||
public Action? CloseAction { get; set; }
|
||||
|
||||
public DelegateCommand SaveCommand { get; }
|
||||
public DelegateCommand CancelCommand { get; }
|
||||
|
||||
public CustomerEditDialogViewModel(
|
||||
ICustomerService customerService,
|
||||
IJeecgDictSyncService dictSyncService,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager) : base(container, regionManager)
|
||||
{
|
||||
_customerService = customerService;
|
||||
_dictSyncService = dictSyncService;
|
||||
SaveCommand = new DelegateCommand(async () => await SaveAsync());
|
||||
CancelCommand = new DelegateCommand(() => CloseAction?.Invoke());
|
||||
_ = LoadDictOptionsAsync();
|
||||
}
|
||||
|
||||
private async Task LoadDictOptionsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var statusOpts = await _dictSyncService.GetDictOptionsAsync("xslmes_customer_status");
|
||||
var regionOpts = await _dictSyncService.GetDictOptionsAsync("xslmes_customer_region");
|
||||
|
||||
StatusOptions.Clear();
|
||||
foreach (var item in statusOpts) StatusOptions.Add(item);
|
||||
CustomerRegionOptions.Clear();
|
||||
foreach (var item in regionOpts) CustomerRegionOptions.Add(item);
|
||||
|
||||
if (StatusOptions.Count == 0)
|
||||
{
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("启用", "0"));
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("停用", "1"));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
StatusOptions.Clear();
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("启用", "0"));
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("停用", "1"));
|
||||
CustomerRegionOptions.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void InitializeForAdd()
|
||||
{
|
||||
Customer = new MesXslCustomer { Status = "0" };
|
||||
RaisePropertyChanged(nameof(IsAddMode));
|
||||
RaisePropertyChanged(nameof(DialogTitle));
|
||||
}
|
||||
|
||||
public void InitializeForEdit(MesXslCustomer customer)
|
||||
{
|
||||
Customer = new MesXslCustomer
|
||||
{
|
||||
Id = customer.Id,
|
||||
CustomerCode = customer.CustomerCode,
|
||||
CustomerName = customer.CustomerName,
|
||||
CustomerShortName = customer.CustomerShortName,
|
||||
CustomerRegion = customer.CustomerRegion,
|
||||
ErpCode = customer.ErpCode,
|
||||
Status = customer.Status,
|
||||
IzEnable = customer.IzEnable,
|
||||
CustomerDesc = customer.CustomerDesc,
|
||||
TenantId = customer.TenantId,
|
||||
};
|
||||
RaisePropertyChanged(nameof(IsAddMode));
|
||||
RaisePropertyChanged(nameof(DialogTitle));
|
||||
}
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
if (Customer == null) return;
|
||||
if (string.IsNullOrWhiteSpace(Customer.CustomerCode))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("客户编码不能为空!");
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(Customer.CustomerName))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("客户名称不能为空!");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
bool ok;
|
||||
if (IsAddMode)
|
||||
{
|
||||
ok = await _customerService.AddAsync(Customer);
|
||||
if (ok) HandyControl.Controls.MessageBox.Success("新增客户成功!");
|
||||
else { HandyControl.Controls.MessageBox.Error("新增客户失败!"); return; }
|
||||
}
|
||||
else
|
||||
{
|
||||
ok = await _customerService.EditAsync(Customer);
|
||||
if (!ok) { HandyControl.Controls.MessageBox.Error("编辑客户失败!"); return; }
|
||||
}
|
||||
Result = ok;
|
||||
CloseAction?.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Error($"操作失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
using HandyControl.Controls;
|
||||
using HandyControl.Tools.Extension;
|
||||
using Prism.Events;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Events;
|
||||
using YY.Admin.Core.Helper;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Services.Service;
|
||||
using YY.Admin.Services.Service.Customer;
|
||||
using YY.Admin.Views.Customer;
|
||||
|
||||
namespace YY.Admin.ViewModels.Customer;
|
||||
|
||||
public class CustomerListViewModel : BaseViewModel
|
||||
{
|
||||
private readonly ICustomerService _customerService;
|
||||
private readonly IJeecgDictSyncService _dictSyncService;
|
||||
private readonly IDialogService _dialogService;
|
||||
private SubscriptionToken? _customerChangedToken;
|
||||
private SubscriptionToken? _syncConflictToken;
|
||||
|
||||
private ObservableCollection<MesXslCustomer> _customers = new();
|
||||
public ObservableCollection<MesXslCustomer> Customers
|
||||
{
|
||||
get => _customers;
|
||||
set => SetProperty(ref _customers, value);
|
||||
}
|
||||
|
||||
private long _total;
|
||||
public long Total { get => _total; set => SetProperty(ref _total, value); }
|
||||
|
||||
private int _pageNo = 1;
|
||||
public int PageNo { get => _pageNo; set => SetProperty(ref _pageNo, value); }
|
||||
|
||||
private int _pageSize = 20;
|
||||
public int PageSize { get => _pageSize; set => SetProperty(ref _pageSize, value); }
|
||||
|
||||
private string? _filterCustomerCode;
|
||||
public string? FilterCustomerCode { get => _filterCustomerCode; set => SetProperty(ref _filterCustomerCode, value); }
|
||||
|
||||
private string? _filterCustomerName;
|
||||
public string? FilterCustomerName { get => _filterCustomerName; set => SetProperty(ref _filterCustomerName, value); }
|
||||
|
||||
private string? _filterStatus;
|
||||
public string? FilterStatus { get => _filterStatus; set => SetProperty(ref _filterStatus, value); }
|
||||
|
||||
private string? _filterCustomerRegion;
|
||||
public string? FilterCustomerRegion { get => _filterCustomerRegion; set => SetProperty(ref _filterCustomerRegion, value); }
|
||||
|
||||
public ObservableCollection<KeyValuePair<string, string>> StatusOptions { get; } = new();
|
||||
public ObservableCollection<KeyValuePair<string, string>> CustomerRegionOptions { get; } = new();
|
||||
|
||||
public DelegateCommand SearchCommand { get; }
|
||||
public DelegateCommand ResetCommand { get; }
|
||||
public DelegateCommand AddCommand { get; }
|
||||
public DelegateCommand<MesXslCustomer> EditCommand { get; }
|
||||
public DelegateCommand<MesXslCustomer> DeleteCommand { get; }
|
||||
public DelegateCommand<MesXslCustomer> ToggleStatusCommand { get; }
|
||||
public DelegateCommand PrevPageCommand { get; }
|
||||
public DelegateCommand NextPageCommand { get; }
|
||||
|
||||
public CustomerListViewModel(
|
||||
ICustomerService customerService,
|
||||
IJeecgDictSyncService dictSyncService,
|
||||
IContainerExtension container,
|
||||
IDialogService dialogService,
|
||||
IRegionManager regionManager) : base(container, regionManager)
|
||||
{
|
||||
_customerService = customerService;
|
||||
_dictSyncService = dictSyncService;
|
||||
_dialogService = dialogService;
|
||||
|
||||
SearchCommand = new DelegateCommand(async () => { PageNo = 1; await LoadAsync(); });
|
||||
ResetCommand = new DelegateCommand(async () =>
|
||||
{
|
||||
FilterCustomerCode = null; FilterCustomerName = null;
|
||||
FilterStatus = null; FilterCustomerRegion = null;
|
||||
PageNo = 1; await LoadAsync();
|
||||
});
|
||||
AddCommand = new DelegateCommand(async () => await ShowAddDialogAsync());
|
||||
EditCommand = new DelegateCommand<MesXslCustomer>(async c => await ShowEditDialogAsync(c));
|
||||
DeleteCommand = new DelegateCommand<MesXslCustomer>(async c => await DeleteAsync(c));
|
||||
ToggleStatusCommand = new DelegateCommand<MesXslCustomer>(async c => await ToggleStatusAsync(c));
|
||||
PrevPageCommand = new DelegateCommand(async () => { if (PageNo > 1) { PageNo--; await LoadAsync(); } });
|
||||
NextPageCommand = new DelegateCommand(async () => { if ((long)PageNo * PageSize < Total) { PageNo++; await LoadAsync(); } });
|
||||
|
||||
_customerChangedToken = _eventAggregator.GetEvent<CustomerChangedEvent>()
|
||||
.Subscribe(async p => await OnCustomerChangedAsync(p), ThreadOption.UIThread);
|
||||
|
||||
_syncConflictToken = _eventAggregator.GetEvent<SyncConflictEvent>()
|
||||
.Subscribe(OnSyncConflict, ThreadOption.UIThread);
|
||||
|
||||
_ = InitializeAsync();
|
||||
}
|
||||
|
||||
private async Task OnCustomerChangedAsync(CustomerChangedPayload payload)
|
||||
{
|
||||
if (payload.Action == "edit" && !string.IsNullOrWhiteSpace(payload.CustomerId))
|
||||
{
|
||||
await RefreshSingleAsync(payload.CustomerId!);
|
||||
}
|
||||
else
|
||||
{
|
||||
await LoadAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshSingleAsync(string customerId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var updated = await _customerService.GetByIdAsync(customerId);
|
||||
if (updated == null) return;
|
||||
var idx = Customers.ToList().FindIndex(c => string.Equals(c.Id, customerId, StringComparison.OrdinalIgnoreCase));
|
||||
if (idx >= 0)
|
||||
Customers[idx] = updated;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"[客户] 单条刷新失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSyncConflict(SyncConflictPayload payload)
|
||||
{
|
||||
if (!string.Equals(payload.EntityName, "客户", StringComparison.OrdinalIgnoreCase)) return;
|
||||
|
||||
var parts = new List<string>();
|
||||
if (payload.PushedCount > 0)
|
||||
parts.Add($"已同步 {payload.PushedCount} 条本地改动到服务器");
|
||||
if (payload.NewRecordsPushed > 0)
|
||||
parts.Add($"已上传 {payload.NewRecordsPushed} 条本地新增记录");
|
||||
if (payload.ConflictCount > 0)
|
||||
parts.Add($"{payload.ConflictCount} 条记录与服务器版本冲突,已保留服务器版本");
|
||||
|
||||
if (parts.Count == 0) return;
|
||||
|
||||
var message = string.Join("\n", parts);
|
||||
if (payload.ConflictCount > 0)
|
||||
Growl.Warning(message);
|
||||
else
|
||||
Growl.Success(message);
|
||||
}
|
||||
|
||||
private async Task InitializeAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await LoadDictOptionsAsync();
|
||||
await UIHelper.WaitForRenderAsync();
|
||||
await LoadAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"客户列表初始化失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadDictOptionsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var statusOpts = await _dictSyncService.GetDictOptionsAsync("xslmes_customer_status", includeAll: true);
|
||||
var regionOpts = await _dictSyncService.GetDictOptionsAsync("xslmes_customer_region", includeAll: true);
|
||||
|
||||
StatusOptions.Clear();
|
||||
foreach (var item in statusOpts) StatusOptions.Add(item);
|
||||
CustomerRegionOptions.Clear();
|
||||
foreach (var item in regionOpts) CustomerRegionOptions.Add(item);
|
||||
|
||||
if (StatusOptions.Count == 0)
|
||||
{
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("全部", ""));
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("启用", "0"));
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("停用", "1"));
|
||||
}
|
||||
if (CustomerRegionOptions.Count == 0)
|
||||
CustomerRegionOptions.Add(new KeyValuePair<string, string>("全部", ""));
|
||||
}
|
||||
catch
|
||||
{
|
||||
StatusOptions.Clear();
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("全部", ""));
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("启用", "0"));
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("停用", "1"));
|
||||
CustomerRegionOptions.Clear();
|
||||
CustomerRegionOptions.Add(new KeyValuePair<string, string>("全部", ""));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task LoadAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
var result = await _customerService.PageAsync(PageNo, PageSize,
|
||||
FilterCustomerCode, FilterCustomerName, FilterStatus, FilterCustomerRegion);
|
||||
Customers = new ObservableCollection<MesXslCustomer>(result.Records);
|
||||
Total = result.Total;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"加载客户列表失败:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowAddDialogAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await HandyControl.Controls.Dialog.Show<CustomerEditDialogView>()
|
||||
.Initialize<CustomerEditDialogViewModel>(vm => vm.InitializeForAdd())
|
||||
.GetResultAsync<bool>();
|
||||
if (result) await LoadAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"打开新增对话框失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowEditDialogAsync(MesXslCustomer customer)
|
||||
{
|
||||
if (customer == null) return;
|
||||
try
|
||||
{
|
||||
var result = await HandyControl.Controls.Dialog.Show<CustomerEditDialogView>()
|
||||
.Initialize<CustomerEditDialogViewModel>(vm => vm.InitializeForEdit(customer))
|
||||
.GetResultAsync<bool>();
|
||||
if (result) await LoadAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"打开编辑对话框失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteAsync(MesXslCustomer customer)
|
||||
{
|
||||
if (customer?.Id == null) return;
|
||||
var confirm = System.Windows.MessageBox.Show(
|
||||
$"确定删除客户【{customer.CustomerName}】?此操作不可恢复!",
|
||||
"确认删除", MessageBoxButton.OKCancel, MessageBoxImage.Question);
|
||||
if (confirm != System.Windows.MessageBoxResult.OK) return;
|
||||
|
||||
var ok = await _customerService.DeleteAsync(customer.Id);
|
||||
if (ok) { Growl.Success("删除成功!"); await LoadAsync(); }
|
||||
else Growl.Error("删除失败!");
|
||||
}
|
||||
|
||||
private async Task ToggleStatusAsync(MesXslCustomer customer)
|
||||
{
|
||||
if (customer?.Id == null) return;
|
||||
var newStatus = customer.Status == "1" ? "0" : "1";
|
||||
var ok = await _customerService.UpdateStatusAsync(customer.Id, newStatus);
|
||||
if (ok)
|
||||
{
|
||||
// 客户列表离线场景下只改行对象字段,DataGrid 对计算列(StatusText)可能不会立即重绘。
|
||||
// 这里统一重载一次列表,确保状态、筛选结果和分页统计立即一致。
|
||||
await LoadAsync();
|
||||
}
|
||||
else Growl.Error("状态切换失败!");
|
||||
}
|
||||
|
||||
protected override void CleanUp()
|
||||
{
|
||||
base.CleanUp();
|
||||
if (_customerChangedToken != null)
|
||||
{
|
||||
_eventAggregator.GetEvent<CustomerChangedEvent>().Unsubscribe(_customerChangedToken);
|
||||
_customerChangedToken = null;
|
||||
}
|
||||
|
||||
if (_syncConflictToken != null)
|
||||
{
|
||||
_eventAggregator.GetEvent<SyncConflictEvent>().Unsubscribe(_syncConflictToken);
|
||||
_syncConflictToken = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,27 @@ namespace YY.Admin.ViewModels.Dialogs
|
||||
set => SetProperty(ref _errorMessage, value);
|
||||
}
|
||||
|
||||
private bool _disconnectConnection;
|
||||
/// <summary>
|
||||
/// 是否断开连接(true=断开,false=连接)
|
||||
/// </summary>
|
||||
public bool DisconnectConnection
|
||||
{
|
||||
get => _disconnectConnection;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _disconnectConnection, value))
|
||||
{
|
||||
RaisePropertyChanged(nameof(IsConnectionFieldsEnabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接参数是否可编辑(勾选断开连接后禁用)。
|
||||
/// </summary>
|
||||
public bool IsConnectionFieldsEnabled => !DisconnectConnection;
|
||||
|
||||
public DelegateCommand SaveCommand { get; }
|
||||
public DelegateCommand CancelCommand { get; }
|
||||
public DialogCloseListener RequestClose { get; private set; }
|
||||
@@ -68,6 +89,7 @@ namespace YY.Admin.ViewModels.Dialogs
|
||||
: settings.WebSocketUrl;
|
||||
_loadedWebSocketUrl = WebSocketUrl;
|
||||
BasePath = string.IsNullOrWhiteSpace(settings.BasePath) ? "/jeecg-boot" : settings.BasePath;
|
||||
DisconnectConnection = settings.DisconnectConnection;
|
||||
ErrorMessage = string.Empty;
|
||||
}
|
||||
|
||||
@@ -102,7 +124,8 @@ namespace YY.Admin.ViewModels.Dialogs
|
||||
BaseScheme = "http",
|
||||
BasePath = basePath,
|
||||
WebSocketUrl = finalWsUrl,
|
||||
WebSocketPath = DefaultWebSocketPath
|
||||
WebSocketPath = DefaultWebSocketPath,
|
||||
DisconnectConnection = DisconnectConnection
|
||||
});
|
||||
RequestClose.Invoke(new DialogResult(ButtonResult.OK));
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ using System.Windows.Threading;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Const;
|
||||
using YY.Admin.Core.Model;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Core.Session;
|
||||
using YY.Admin.Core.Util;
|
||||
using YY.Admin.Event;
|
||||
@@ -20,6 +21,7 @@ using YY.Admin.Services.Service.Auth;
|
||||
using YY.Admin.Services.Service.Jeecg;
|
||||
using YY.Admin.Services.Service.Menu;
|
||||
using YY.Admin.ViewModels.Control;
|
||||
using YY.Admin.Helper;
|
||||
|
||||
namespace YY.Admin.ViewModels
|
||||
{
|
||||
@@ -220,8 +222,9 @@ namespace YY.Admin.ViewModels
|
||||
_refreshTabToken = _eventAggregator.GetEvent<TabRefreshEvent>().Subscribe(OnRefreshTab);
|
||||
_loginOutToken = _eventAggregator.GetEvent<SysUserEvents.LoginOutEvent>().Subscribe(Destroy);
|
||||
|
||||
// Jeecg 用户增量同步:定时 + 可选 WebSocket(工控机断网续传)
|
||||
_jeecgUserSyncCoordinator.Start();
|
||||
// 首次按默认连接;后续按本地保存状态恢复
|
||||
var settings = ServerSettingsStore.Load();
|
||||
IsServerConnectionEnabled = !settings.DisconnectConnection;
|
||||
|
||||
// 主窗口底部连接状态圆点
|
||||
_ = StartBackendConnectivityLoopAsync(_backendConnectivityCts.Token);
|
||||
@@ -251,6 +254,30 @@ namespace YY.Admin.ViewModels
|
||||
|
||||
public Brush BackendConnectionStatusBrush => IsBackendConnected ? Brushes.LimeGreen : Brushes.Red;
|
||||
|
||||
private bool _isServerConnectionEnabled = true;
|
||||
/// <summary>
|
||||
/// 是否启用服务器连接(默认启用)
|
||||
/// </summary>
|
||||
public bool IsServerConnectionEnabled
|
||||
{
|
||||
get => _isServerConnectionEnabled;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _isServerConnectionEnabled, value))
|
||||
{
|
||||
if (!value)
|
||||
{
|
||||
IsBackendConnected = false;
|
||||
_jeecgUserSyncCoordinator.Stop();
|
||||
}
|
||||
else
|
||||
{
|
||||
_jeecgUserSyncCoordinator.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DelegateCommand LogoutCommand { get; }
|
||||
|
||||
private void InitNavItems()
|
||||
@@ -355,7 +382,8 @@ namespace YY.Admin.ViewModels
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error($"导航失败: {viewName}");
|
||||
var exMsg = result.Exception?.Message;
|
||||
_logger.Error($"导航失败: {viewName}, Region={regionName}, Exception={exMsg}");
|
||||
tcs.SetResult(false);
|
||||
}
|
||||
}, parameters);
|
||||
@@ -586,6 +614,26 @@ namespace YY.Admin.ViewModels
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
bool connected = false;
|
||||
if (!IsServerConnectionEnabled)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Application.Current.Dispatcher.InvokeAsync(() => { IsBackendConnected = false; });
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(ConnectivityCheckIntervalSeconds), cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
try
|
||||
{
|
||||
// 每轮都重新读取配置,保存服务器设置后可即时生效
|
||||
@@ -631,7 +679,24 @@ namespace YY.Admin.ViewModels
|
||||
|
||||
private void OpenServerSettings()
|
||||
{
|
||||
_dialogService.ShowDialog("ServerSettingsDialog", r => { });
|
||||
_dialogService.ShowDialog("ServerSettingsDialog", r =>
|
||||
{
|
||||
if (r.Result == ButtonResult.OK)
|
||||
{
|
||||
var settings = ServerSettingsStore.Load();
|
||||
IsServerConnectionEnabled = !settings.DisconnectConnection;
|
||||
if (settings.DisconnectConnection)
|
||||
{
|
||||
var signalService = _container.Resolve<ISignalRService>();
|
||||
_ = signalService.DisconnectAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
var signalService = _container.Resolve<ISignalRService>();
|
||||
_ = signalService.ConnectUnifiedDeviceChannelAsync(CancellationToken.None);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
using HandyControl.Controls;
|
||||
using HandyControl.Tools.Extension;
|
||||
using System.Collections.ObjectModel;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Services.Service;
|
||||
|
||||
namespace YY.Admin.ViewModels.Supplier;
|
||||
|
||||
public class SupplierEditDialogViewModel : BaseViewModel, HandyControl.Tools.Extension.IDialogResultable<bool>
|
||||
{
|
||||
private readonly ISupplierService _supplierService;
|
||||
private readonly IJeecgDictSyncService _dictSyncService;
|
||||
private bool _result;
|
||||
public bool Result { get => _result; set => SetProperty(ref _result, value); }
|
||||
public Action? CloseAction { get; set; }
|
||||
|
||||
private MesXslSupplier? _supplier;
|
||||
public MesXslSupplier? Supplier
|
||||
{
|
||||
get => _supplier;
|
||||
set => SetProperty(ref _supplier, value);
|
||||
}
|
||||
|
||||
public bool IsAddMode => string.IsNullOrWhiteSpace(Supplier?.Id) || (Supplier?.Id?.StartsWith("local-") ?? false);
|
||||
public string DialogTitle => IsAddMode ? "新增供应商" : "编辑供应商";
|
||||
public ObservableCollection<KeyValuePair<string, string>> StatusOptions { get; } = new();
|
||||
public DelegateCommand SaveCommand { get; }
|
||||
public DelegateCommand CancelCommand { get; }
|
||||
|
||||
public SupplierEditDialogViewModel(ISupplierService supplierService, IJeecgDictSyncService dictSyncService, IContainerExtension container, IRegionManager regionManager) : base(container, regionManager)
|
||||
{
|
||||
_supplierService = supplierService;
|
||||
_dictSyncService = dictSyncService;
|
||||
SaveCommand = new DelegateCommand(async () => await SaveAsync());
|
||||
CancelCommand = new DelegateCommand(() => CloseAction?.Invoke());
|
||||
_ = LoadDictOptionsAsync();
|
||||
}
|
||||
|
||||
private async Task LoadDictOptionsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var statusOpts = await _dictSyncService.GetDictOptionsAsync("xslmes_supplier_status");
|
||||
StatusOptions.Clear();
|
||||
foreach (var item in statusOpts) StatusOptions.Add(item);
|
||||
if (StatusOptions.Count == 0)
|
||||
{
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("启用", "0"));
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("停用", "1"));
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public void InitializeForAdd()
|
||||
{
|
||||
Supplier = new MesXslSupplier { Status = "0" };
|
||||
RaisePropertyChanged(nameof(IsAddMode));
|
||||
RaisePropertyChanged(nameof(DialogTitle));
|
||||
}
|
||||
|
||||
public void InitializeForEdit(MesXslSupplier supplier)
|
||||
{
|
||||
Supplier = new MesXslSupplier
|
||||
{
|
||||
Id = supplier.Id,
|
||||
SupplierCode = supplier.SupplierCode,
|
||||
SupplierName = supplier.SupplierName,
|
||||
SupplierShortName = supplier.SupplierShortName,
|
||||
ErpCode = supplier.ErpCode,
|
||||
Status = supplier.Status,
|
||||
Remark = supplier.Remark,
|
||||
TenantId = supplier.TenantId,
|
||||
};
|
||||
RaisePropertyChanged(nameof(IsAddMode));
|
||||
RaisePropertyChanged(nameof(DialogTitle));
|
||||
}
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
if (Supplier == null) return;
|
||||
if (string.IsNullOrWhiteSpace(Supplier.SupplierCode))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("供应商编码不能为空!");
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(Supplier.SupplierName))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("供应商名称不能为空!");
|
||||
return;
|
||||
}
|
||||
|
||||
bool ok = IsAddMode ? await _supplierService.AddAsync(Supplier) : await _supplierService.EditAsync(Supplier);
|
||||
if (ok)
|
||||
{
|
||||
if (IsAddMode)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Success("新增供应商成功!");
|
||||
}
|
||||
Result = true;
|
||||
CloseAction?.Invoke();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Error("保存供应商失败!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
using HandyControl.Controls;
|
||||
using HandyControl.Tools.Extension;
|
||||
using Prism.Events;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Events;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Services.Service;
|
||||
using YY.Admin.Views.Supplier;
|
||||
|
||||
namespace YY.Admin.ViewModels.Supplier;
|
||||
|
||||
public class SupplierListViewModel : BaseViewModel
|
||||
{
|
||||
private readonly ISupplierService _supplierService;
|
||||
private readonly IJeecgDictSyncService _dictSyncService;
|
||||
private SubscriptionToken? _supplierChangedToken;
|
||||
private SubscriptionToken? _syncConflictToken;
|
||||
|
||||
public ObservableCollection<MesXslSupplier> Suppliers { get; set; } = [];
|
||||
public ObservableCollection<KeyValuePair<string, string>> StatusOptions { get; } = [];
|
||||
public long Total { get; set; }
|
||||
public int PageNo { get; set; } = 1;
|
||||
public int PageSize { get; set; } = 20;
|
||||
public string? FilterSupplierCode { get; set; }
|
||||
public string? FilterSupplierName { get; set; }
|
||||
public string? FilterSupplierShortName { get; set; }
|
||||
public string? FilterErpCode { get; set; }
|
||||
public string? FilterStatus { get; set; }
|
||||
|
||||
public DelegateCommand SearchCommand { get; }
|
||||
public DelegateCommand ResetCommand { get; }
|
||||
public DelegateCommand AddCommand { get; }
|
||||
public DelegateCommand<MesXslSupplier> EditCommand { get; }
|
||||
public DelegateCommand<MesXslSupplier> DeleteCommand { get; }
|
||||
public DelegateCommand<MesXslSupplier> ToggleStatusCommand { get; }
|
||||
|
||||
public SupplierListViewModel(
|
||||
ISupplierService supplierService,
|
||||
IJeecgDictSyncService dictSyncService,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager)
|
||||
: base(container, regionManager)
|
||||
{
|
||||
_supplierService = supplierService;
|
||||
_dictSyncService = dictSyncService;
|
||||
SearchCommand = new DelegateCommand(async () => { PageNo = 1; await LoadAsync(); });
|
||||
ResetCommand = new DelegateCommand(async () =>
|
||||
{
|
||||
FilterSupplierCode = FilterSupplierName = FilterSupplierShortName = FilterErpCode = FilterStatus = null;
|
||||
PageNo = 1;
|
||||
await LoadAsync();
|
||||
});
|
||||
AddCommand = new DelegateCommand(async () => await ShowAddDialogAsync());
|
||||
EditCommand = new DelegateCommand<MesXslSupplier>(async s => await ShowEditDialogAsync(s));
|
||||
DeleteCommand = new DelegateCommand<MesXslSupplier>(async s => await DeleteAsync(s));
|
||||
ToggleStatusCommand = new DelegateCommand<MesXslSupplier>(async s => await ToggleStatusAsync(s));
|
||||
|
||||
_supplierChangedToken = _eventAggregator.GetEvent<SupplierChangedEvent>()
|
||||
.Subscribe(async p => await OnSupplierChangedAsync(p), ThreadOption.UIThread);
|
||||
_syncConflictToken = _eventAggregator.GetEvent<SyncConflictEvent>()
|
||||
.Subscribe(OnSyncConflict, ThreadOption.UIThread);
|
||||
|
||||
_ = InitializeAsync();
|
||||
}
|
||||
|
||||
private async Task InitializeAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var statusOpts = await _dictSyncService.GetDictOptionsAsync("xslmes_supplier_status", includeAll: true);
|
||||
StatusOptions.Clear();
|
||||
foreach (var item in statusOpts) StatusOptions.Add(item);
|
||||
if (StatusOptions.Count == 0)
|
||||
{
|
||||
StatusOptions.Add(new("全部", ""));
|
||||
StatusOptions.Add(new("启用", "0"));
|
||||
StatusOptions.Add(new("停用", "1"));
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
await LoadAsync();
|
||||
}
|
||||
|
||||
public async Task LoadAsync()
|
||||
{
|
||||
var result = await _supplierService.PageAsync(
|
||||
PageNo, PageSize,
|
||||
FilterSupplierCode, FilterSupplierName,
|
||||
FilterSupplierShortName, FilterErpCode, FilterStatus);
|
||||
Suppliers = new ObservableCollection<MesXslSupplier>(result.Records);
|
||||
Total = result.Total;
|
||||
RaisePropertyChanged(nameof(Suppliers));
|
||||
RaisePropertyChanged(nameof(Total));
|
||||
}
|
||||
|
||||
private async Task OnSupplierChangedAsync(SupplierChangedPayload payload)
|
||||
{
|
||||
if (payload.Action == "edit" && !string.IsNullOrWhiteSpace(payload.SupplierId))
|
||||
{
|
||||
await RefreshSingleAsync(payload.SupplierId!);
|
||||
}
|
||||
else
|
||||
{
|
||||
await LoadAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshSingleAsync(string supplierId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var updated = await _supplierService.GetByIdAsync(supplierId);
|
||||
if (updated == null) return;
|
||||
var idx = Suppliers.ToList().FindIndex(s => string.Equals(s.Id, supplierId, StringComparison.OrdinalIgnoreCase));
|
||||
if (idx >= 0)
|
||||
{
|
||||
Suppliers[idx] = updated;
|
||||
RaisePropertyChanged(nameof(Suppliers));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"[供应商] 单条刷新失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSyncConflict(SyncConflictPayload payload)
|
||||
{
|
||||
if (!string.Equals(payload.EntityName, "供应商", StringComparison.Ordinal)) return;
|
||||
|
||||
var parts = new List<string>();
|
||||
if (payload.PushedCount > 0)
|
||||
parts.Add($"已同步 {payload.PushedCount} 条本地改动到服务器");
|
||||
if (payload.NewRecordsPushed > 0)
|
||||
parts.Add($"已上传 {payload.NewRecordsPushed} 条本地新增记录");
|
||||
if (payload.ConflictCount > 0)
|
||||
parts.Add($"{payload.ConflictCount} 条记录与服务器版本冲突,已保留服务器版本");
|
||||
|
||||
if (parts.Count == 0) return;
|
||||
|
||||
var message = string.Join("\n", parts);
|
||||
if (payload.ConflictCount > 0)
|
||||
Growl.Warning(message);
|
||||
else
|
||||
Growl.Success(message);
|
||||
}
|
||||
|
||||
private async Task ShowAddDialogAsync()
|
||||
{
|
||||
var ok = await HandyControl.Controls.Dialog.Show<SupplierEditDialogView>()
|
||||
.Initialize<SupplierEditDialogViewModel>(vm => vm.InitializeForAdd())
|
||||
.GetResultAsync<bool>();
|
||||
if (ok) await LoadAsync();
|
||||
}
|
||||
|
||||
private async Task ShowEditDialogAsync(MesXslSupplier supplier)
|
||||
{
|
||||
if (supplier == null) return;
|
||||
var ok = await HandyControl.Controls.Dialog.Show<SupplierEditDialogView>()
|
||||
.Initialize<SupplierEditDialogViewModel>(vm => vm.InitializeForEdit(supplier))
|
||||
.GetResultAsync<bool>();
|
||||
if (ok) await LoadAsync();
|
||||
}
|
||||
|
||||
private async Task DeleteAsync(MesXslSupplier supplier)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(supplier?.Id)) return;
|
||||
if (System.Windows.MessageBox.Show(
|
||||
$"确定删除供应商【{supplier.SupplierName}】?",
|
||||
"确认删除",
|
||||
System.Windows.MessageBoxButton.OKCancel) != System.Windows.MessageBoxResult.OK) return;
|
||||
if (await _supplierService.DeleteAsync(supplier.Id)) await LoadAsync();
|
||||
}
|
||||
|
||||
private async Task ToggleStatusAsync(MesXslSupplier supplier)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(supplier?.Id)) return;
|
||||
var newStatus = supplier.Status == "1" ? "0" : "1";
|
||||
if (await _supplierService.UpdateStatusAsync(supplier.Id, newStatus))
|
||||
{
|
||||
supplier.Status = newStatus;
|
||||
RaisePropertyChanged(nameof(Suppliers));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
using HandyControl.Controls;
|
||||
using Prism.Commands;
|
||||
using Prism.Ioc;
|
||||
using YY.Admin.Core.Const;
|
||||
using YY.Admin.Services.Service.Config;
|
||||
|
||||
namespace YY.Admin.ViewModels.SysManage;
|
||||
|
||||
/// <summary>
|
||||
/// 登录与会话相关系统配置
|
||||
/// </summary>
|
||||
public class LoginSettingsViewModel : BaseViewModel
|
||||
{
|
||||
private readonly ISysConfigService _configService;
|
||||
|
||||
private int _tokenExpireMinutes = 30;
|
||||
public int TokenExpireMinutes
|
||||
{
|
||||
get => _tokenExpireMinutes;
|
||||
set => SetProperty(ref _tokenExpireMinutes, value);
|
||||
}
|
||||
|
||||
private int _refreshTokenExpireMinutes = 20160;
|
||||
public int RefreshTokenExpireMinutes
|
||||
{
|
||||
get => _refreshTokenExpireMinutes;
|
||||
set => SetProperty(ref _refreshTokenExpireMinutes, value);
|
||||
}
|
||||
|
||||
private int _idleExtendMinutes = 20;
|
||||
public int IdleExtendMinutes
|
||||
{
|
||||
get => _idleExtendMinutes;
|
||||
set => SetProperty(ref _idleExtendMinutes, value);
|
||||
}
|
||||
|
||||
private int _checkIntervalMinutes = 1;
|
||||
public int CheckIntervalMinutes
|
||||
{
|
||||
get => _checkIntervalMinutes;
|
||||
set => SetProperty(ref _checkIntervalMinutes, value);
|
||||
}
|
||||
|
||||
private bool _neverExpire;
|
||||
public bool NeverExpire
|
||||
{
|
||||
get => _neverExpire;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _neverExpire, value))
|
||||
RaisePropertyChanged(nameof(IsConfigEditable));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsConfigEditable => !NeverExpire;
|
||||
|
||||
public DelegateCommand LoadCommand { get; }
|
||||
public DelegateCommand SaveCommand { get; }
|
||||
|
||||
public LoginSettingsViewModel(
|
||||
ISysConfigService configService,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager) : base(container, regionManager)
|
||||
{
|
||||
_configService = configService;
|
||||
LoadCommand = new DelegateCommand(async () => await LoadAsync());
|
||||
SaveCommand = new DelegateCommand(async () => await SaveAsync());
|
||||
_ = LoadAsync();
|
||||
}
|
||||
|
||||
private async Task LoadAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
var t = await _configService.GetConfigValue<int>(ConfigConst.SysTokenExpire);
|
||||
TokenExpireMinutes = t > 0 ? t : 30;
|
||||
|
||||
var r = await _configService.GetConfigValue<int>(ConfigConst.SysRefreshTokenExpire);
|
||||
RefreshTokenExpireMinutes = r > 0 ? r : 20160;
|
||||
|
||||
var i = await _configService.GetConfigValue<int>(ConfigConst.SysTokenIdleExtendMinutes);
|
||||
IdleExtendMinutes = i > 0 ? i : 20;
|
||||
|
||||
var c = await _configService.GetConfigValue<int>(ConfigConst.SysTokenCheckIntervalMinutes);
|
||||
CheckIntervalMinutes = c > 0 ? c : 1;
|
||||
|
||||
NeverExpire = await _configService.GetConfigValue<bool>(ConfigConst.SysTokenNeverExpire);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"加载配置失败:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
if (!NeverExpire && (TokenExpireMinutes < 5 || TokenExpireMinutes > 43200))
|
||||
{
|
||||
Growl.Warning("Token 过期时间建议在 5~43200 分钟之间");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NeverExpire && (IdleExtendMinutes < 1 || IdleExtendMinutes > 1440))
|
||||
{
|
||||
Growl.Warning("会话续期阈值建议在 1~1440 分钟之间");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NeverExpire && IdleExtendMinutes > TokenExpireMinutes)
|
||||
{
|
||||
Growl.Warning("续期阈值不应大于 Token 过期时间");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NeverExpire && (CheckIntervalMinutes < 1 || CheckIntervalMinutes > 120))
|
||||
{
|
||||
Growl.Warning("登录状态检查间隔建议在 1~120 分钟之间");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NeverExpire && RefreshTokenExpireMinutes < TokenExpireMinutes * 2)
|
||||
{
|
||||
Growl.Warning("RefreshToken 过期时间建议不小于 Token 过期时间的 2 倍");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
var pairs = new[]
|
||||
{
|
||||
(ConfigConst.SysTokenExpire, TokenExpireMinutes.ToString()),
|
||||
(ConfigConst.SysRefreshTokenExpire, RefreshTokenExpireMinutes.ToString()),
|
||||
(ConfigConst.SysTokenIdleExtendMinutes, IdleExtendMinutes.ToString()),
|
||||
(ConfigConst.SysTokenCheckIntervalMinutes, CheckIntervalMinutes.ToString()),
|
||||
(ConfigConst.SysTokenNeverExpire, NeverExpire.ToString()),
|
||||
};
|
||||
|
||||
foreach (var (code, val) in pairs)
|
||||
{
|
||||
var (ok, msg) = await _configService.SetConfigValueAsync(code, val);
|
||||
if (!ok)
|
||||
{
|
||||
Growl.Warning($"{code}:{msg}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Growl.Success("登录相关配置已保存");
|
||||
BaseViewModel.RefreshTokenCheckIntervalFromConfig();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"保存失败:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using HandyControl.Controls;
|
||||
using Prism.Commands;
|
||||
using Prism.Mvvm;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Extension;
|
||||
using YY.Admin.Event;
|
||||
using YY.Admin.Services.Service.Menu;
|
||||
|
||||
namespace YY.Admin.ViewModels.SysManage;
|
||||
|
||||
/// <summary>
|
||||
/// 左侧列表一行(树形扁平展示)
|
||||
/// </summary>
|
||||
public sealed class MenuFlatRow
|
||||
{
|
||||
public SysMenu Menu { get; }
|
||||
public int Depth { get; }
|
||||
public string IndentTitle { get; }
|
||||
|
||||
public MenuFlatRow(SysMenu menu, int depth)
|
||||
{
|
||||
Menu = menu;
|
||||
Depth = depth;
|
||||
var pad = new string(' ', depth);
|
||||
var tag = menu.Type == MenuTypeEnum.Dir ? "[目录] " : menu.Type == MenuTypeEnum.Btn ? "[按钮] " : "[菜单] ";
|
||||
IndentTitle = pad + tag + menu.Title;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可编辑字段(与界面绑定)
|
||||
/// </summary>
|
||||
public class MenuEditorModel : BindableBase
|
||||
{
|
||||
private long _id;
|
||||
private long _pid;
|
||||
private MenuTypeEnum _type = MenuTypeEnum.Menu;
|
||||
private string? _name;
|
||||
private string? _path;
|
||||
private string? _component;
|
||||
private string? _redirect;
|
||||
private string? _permission;
|
||||
private string _title = string.Empty;
|
||||
private string? _icon;
|
||||
private bool _isIframe;
|
||||
private string? _outLink;
|
||||
private bool _isHide;
|
||||
private bool _isKeepAlive = true;
|
||||
private bool _isAffix;
|
||||
private int _orderNo = 100;
|
||||
private StatusEnum _status = StatusEnum.Enable;
|
||||
private string? _remark;
|
||||
|
||||
public long Id { get => _id; set => SetProperty(ref _id, value); }
|
||||
public long Pid { get => _pid; set => SetProperty(ref _pid, value); }
|
||||
public MenuTypeEnum Type { get => _type; set => SetProperty(ref _type, value); }
|
||||
public string? Name { get => _name; set => SetProperty(ref _name, value); }
|
||||
public string? Path { get => _path; set => SetProperty(ref _path, value); }
|
||||
public string? Component { get => _component; set => SetProperty(ref _component, value); }
|
||||
public string? Redirect { get => _redirect; set => SetProperty(ref _redirect, value); }
|
||||
public string? Permission { get => _permission; set => SetProperty(ref _permission, value); }
|
||||
public string Title { get => _title; set => SetProperty(ref _title, value); }
|
||||
public string? Icon { get => _icon; set => SetProperty(ref _icon, value); }
|
||||
public bool IsIframe { get => _isIframe; set => SetProperty(ref _isIframe, value); }
|
||||
public string? OutLink { get => _outLink; set => SetProperty(ref _outLink, value); }
|
||||
public bool IsHide { get => _isHide; set => SetProperty(ref _isHide, value); }
|
||||
public bool IsKeepAlive { get => _isKeepAlive; set => SetProperty(ref _isKeepAlive, value); }
|
||||
public bool IsAffix { get => _isAffix; set => SetProperty(ref _isAffix, value); }
|
||||
public int OrderNo { get => _orderNo; set => SetProperty(ref _orderNo, value); }
|
||||
public StatusEnum Status { get => _status; set => SetProperty(ref _status, value); }
|
||||
public string? Remark { get => _remark; set => SetProperty(ref _remark, value); }
|
||||
|
||||
public bool IsNew => Id == 0;
|
||||
|
||||
public void LoadFrom(SysMenu m)
|
||||
{
|
||||
Id = m.Id;
|
||||
Pid = m.Pid;
|
||||
Type = m.Type;
|
||||
Name = m.Name;
|
||||
Path = m.Path;
|
||||
Component = m.Component;
|
||||
Redirect = m.Redirect;
|
||||
Permission = m.Permission;
|
||||
Title = m.Title;
|
||||
Icon = m.Icon;
|
||||
IsIframe = m.IsIframe;
|
||||
OutLink = m.OutLink;
|
||||
IsHide = m.IsHide;
|
||||
IsKeepAlive = m.IsKeepAlive;
|
||||
IsAffix = m.IsAffix;
|
||||
OrderNo = m.OrderNo;
|
||||
Status = m.Status;
|
||||
Remark = m.Remark;
|
||||
}
|
||||
|
||||
public SysMenu ToSysMenu()
|
||||
{
|
||||
return new SysMenu
|
||||
{
|
||||
Id = Id,
|
||||
Pid = Pid,
|
||||
Type = Type,
|
||||
Name = Name,
|
||||
Path = Path,
|
||||
Component = Component,
|
||||
Redirect = Redirect,
|
||||
Permission = Permission,
|
||||
Title = Title.Trim(),
|
||||
Icon = Icon,
|
||||
IsIframe = IsIframe,
|
||||
OutLink = OutLink,
|
||||
IsHide = IsHide,
|
||||
IsKeepAlive = IsKeepAlive,
|
||||
IsAffix = IsAffix,
|
||||
OrderNo = OrderNo,
|
||||
Status = Status,
|
||||
Remark = Remark
|
||||
};
|
||||
}
|
||||
|
||||
public void ResetForNew(long pid, MenuTypeEnum type)
|
||||
{
|
||||
Id = 0;
|
||||
Pid = pid;
|
||||
Type = type;
|
||||
Name = null;
|
||||
Path = null;
|
||||
Component = null;
|
||||
Redirect = null;
|
||||
Permission = null;
|
||||
Title = type == MenuTypeEnum.Dir ? "新目录" : type == MenuTypeEnum.Menu ? "新菜单" : "新按钮";
|
||||
Icon = "";
|
||||
IsIframe = false;
|
||||
OutLink = null;
|
||||
IsHide = false;
|
||||
IsKeepAlive = true;
|
||||
IsAffix = false;
|
||||
OrderNo = 100;
|
||||
Status = StatusEnum.Enable;
|
||||
Remark = null;
|
||||
}
|
||||
}
|
||||
|
||||
public class MenuManagementViewModel : BaseViewModel
|
||||
{
|
||||
private readonly ISysMenuService _menuService;
|
||||
|
||||
public ObservableCollection<MenuFlatRow> FlatRows { get; } = new();
|
||||
public ObservableCollection<KeyValuePair<long, string>> ParentOptions { get; } = new();
|
||||
|
||||
private MenuFlatRow? _selectedRow;
|
||||
public MenuFlatRow? SelectedRow
|
||||
{
|
||||
get => _selectedRow;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedRow, value))
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
Editor = null;
|
||||
RaisePropertyChanged(nameof(Editor));
|
||||
RaisePropertyChanged(nameof(HasEditor));
|
||||
}
|
||||
else
|
||||
{
|
||||
Editor ??= new MenuEditorModel();
|
||||
Editor.LoadFrom(value.Menu);
|
||||
RaisePropertyChanged(nameof(Editor));
|
||||
RaisePropertyChanged(nameof(HasEditor));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MenuEditorModel? _editor;
|
||||
public MenuEditorModel? Editor
|
||||
{
|
||||
get => _editor;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _editor, value))
|
||||
{
|
||||
RaisePropertyChanged(nameof(HasEditor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasEditor => Editor != null;
|
||||
|
||||
public List<KeyValuePair<string, int>> StatusList =>
|
||||
Enum.GetValues(typeof(StatusEnum))
|
||||
.Cast<StatusEnum>()
|
||||
.Select(e => new KeyValuePair<string, int>(e.GetDescription(), (int)e))
|
||||
.ToList();
|
||||
|
||||
public List<KeyValuePair<string, int>> MenuTypeList =>
|
||||
Enum.GetValues(typeof(MenuTypeEnum))
|
||||
.Cast<MenuTypeEnum>()
|
||||
.Select(e => new KeyValuePair<string, int>(e.GetDescription(), (int)e))
|
||||
.ToList();
|
||||
|
||||
public DelegateCommand RefreshCommand { get; }
|
||||
public DelegateCommand AddRootCommand { get; }
|
||||
public DelegateCommand AddChildCommand { get; }
|
||||
public DelegateCommand SaveCommand { get; }
|
||||
public DelegateCommand DeleteCommand { get; }
|
||||
|
||||
public MenuManagementViewModel(
|
||||
ISysMenuService menuService,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager) : base(container, regionManager)
|
||||
{
|
||||
_menuService = menuService;
|
||||
|
||||
RefreshCommand = new DelegateCommand(async () => await RefreshAsync());
|
||||
AddRootCommand = new DelegateCommand(() => BeginNew(0, MenuTypeEnum.Dir));
|
||||
AddChildCommand = new DelegateCommand(BeginNewChild, () => SelectedRow != null)
|
||||
.ObservesProperty(() => SelectedRow);
|
||||
SaveCommand = new DelegateCommand(async () => await SaveAsync(), () => Editor != null)
|
||||
.ObservesProperty(() => Editor);
|
||||
DeleteCommand = new DelegateCommand(async () => await DeleteAsync(), () => SelectedRow != null && SelectedRow.Menu.Id != 0)
|
||||
.ObservesProperty(() => SelectedRow);
|
||||
|
||||
_ = RefreshAsync();
|
||||
}
|
||||
|
||||
private async Task RefreshAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
var all = await _menuService.GetAllMenusForManageAsync();
|
||||
RebuildFlat(all);
|
||||
RebuildParentOptions(all);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"加载菜单失败:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void RebuildFlat(List<SysMenu> all)
|
||||
{
|
||||
FlatRows.Clear();
|
||||
void Walk(long pid, int depth)
|
||||
{
|
||||
foreach (var m in all.Where(x => x.Pid == pid).OrderBy(x => x.OrderNo).ThenBy(x => x.Id))
|
||||
{
|
||||
FlatRows.Add(new MenuFlatRow(m, depth));
|
||||
Walk(m.Id, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
Walk(0, 0);
|
||||
}
|
||||
|
||||
private void RebuildParentOptions(List<SysMenu> all)
|
||||
{
|
||||
ParentOptions.Clear();
|
||||
ParentOptions.Add(new KeyValuePair<long, string>(0, "(根目录)"));
|
||||
void Walk(long pid, int depth)
|
||||
{
|
||||
foreach (var m in all.Where(x => x.Pid == pid).OrderBy(x => x.OrderNo).ThenBy(x => x.Id))
|
||||
{
|
||||
var indent = new string(' ', depth);
|
||||
ParentOptions.Add(new KeyValuePair<long, string>(m.Id, indent + m.Title));
|
||||
Walk(m.Id, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
Walk(0, 0);
|
||||
}
|
||||
|
||||
private void BeginNew(long pid, MenuTypeEnum type)
|
||||
{
|
||||
// 先取消列表选中,避免 setter 把新建的 Editor 清空
|
||||
_selectedRow = null;
|
||||
RaisePropertyChanged(nameof(SelectedRow));
|
||||
Editor = new MenuEditorModel();
|
||||
Editor.ResetForNew(pid, type);
|
||||
RaisePropertyChanged(nameof(Editor));
|
||||
RaisePropertyChanged(nameof(HasEditor));
|
||||
}
|
||||
|
||||
private void BeginNewChild()
|
||||
{
|
||||
if (SelectedRow == null)
|
||||
return;
|
||||
BeginNew(SelectedRow.Menu.Id, MenuTypeEnum.Menu);
|
||||
}
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
if (Editor == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
var entity = Editor.ToSysMenu();
|
||||
if (Editor.IsNew)
|
||||
{
|
||||
var (ok, msg, id) = await _menuService.CreateMenuAsync(entity);
|
||||
if (ok)
|
||||
{
|
||||
Growl.Success(msg);
|
||||
_eventAggregator.GetEvent<MenuStructureChangedEvent>().Publish(true);
|
||||
await RefreshAsync();
|
||||
SelectedRow = FlatRows.FirstOrDefault(r => r.Menu.Id == id);
|
||||
}
|
||||
else
|
||||
{
|
||||
Growl.Warning(msg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var (ok, msg) = await _menuService.UpdateMenuAsync(entity);
|
||||
if (ok)
|
||||
{
|
||||
Growl.Success(msg);
|
||||
_eventAggregator.GetEvent<MenuStructureChangedEvent>().Publish(true);
|
||||
var keepId = entity.Id;
|
||||
await RefreshAsync();
|
||||
SelectedRow = FlatRows.FirstOrDefault(r => r.Menu.Id == keepId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Growl.Warning(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"保存失败:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteAsync()
|
||||
{
|
||||
if (SelectedRow == null)
|
||||
return;
|
||||
|
||||
var r = System.Windows.MessageBox.Show(
|
||||
$"确定删除「{SelectedRow.Menu.Title}」?若存在子菜单将无法删除。",
|
||||
"确认删除",
|
||||
System.Windows.MessageBoxButton.OKCancel,
|
||||
System.Windows.MessageBoxImage.Question);
|
||||
if (r != System.Windows.MessageBoxResult.OK)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
var (ok, msg) = await _menuService.DeleteMenuAsync(SelectedRow.Menu.Id);
|
||||
if (ok)
|
||||
{
|
||||
Growl.Success(msg);
|
||||
_eventAggregator.GetEvent<MenuStructureChangedEvent>().Publish(true);
|
||||
Editor = null;
|
||||
SelectedRow = null;
|
||||
await RefreshAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
Growl.Warning(msg);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"删除失败:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,14 +135,12 @@ namespace YY.Admin.ViewModels.SysManage
|
||||
SysUser = new SysUser
|
||||
{
|
||||
Id = user.Id,
|
||||
//Account = user.Account,
|
||||
Account = user.Account,
|
||||
RealName = user.RealName,
|
||||
NickName = user.NickName,
|
||||
//Phone = user.Phone,
|
||||
Sex = user.Sex,
|
||||
Birthday = user.Birthday,
|
||||
Age = user.Age,
|
||||
//AccountType = user.AccountType,
|
||||
Status = user.Status
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Windows;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Extension;
|
||||
using YY.Admin.Core.Helper;
|
||||
using YY.Admin.Infrastructure.Sync;
|
||||
using YY.Admin.Services.Service;
|
||||
using YY.Admin.Services.Service.User;
|
||||
using YY.Admin.ViewModels.Control;
|
||||
@@ -20,9 +21,11 @@ namespace YY.Admin.ViewModels.SysManage
|
||||
private PageUserInput _userInput;
|
||||
|
||||
private readonly ISysUserService _sysUserService;
|
||||
private readonly OutboxProcessor _outboxProcessor;
|
||||
|
||||
private readonly IDialogService _dialogService;
|
||||
private SubscriptionToken? _jeecgSyncToken;
|
||||
private bool _isManualUploading;
|
||||
public PaginationDataGridViewModel<UserOutput> PaginationDataGridViewModel
|
||||
{
|
||||
get => _paginationDataGridViewModel;
|
||||
@@ -94,6 +97,7 @@ namespace YY.Admin.ViewModels.SysManage
|
||||
public DelegateCommand<UserOutput> EditCommand { get; private set; }
|
||||
|
||||
public DelegateCommand<UserOutput> StatusToggleCommand { get; private set; }
|
||||
public DelegateCommand ManualUploadCommand { get; private set; }
|
||||
|
||||
|
||||
// 行选择改变命令
|
||||
@@ -101,12 +105,14 @@ namespace YY.Admin.ViewModels.SysManage
|
||||
|
||||
public UserManagementViewModel(
|
||||
ISysUserService sysUserService,
|
||||
OutboxProcessor outboxProcessor,
|
||||
IContainerExtension container,
|
||||
IDialogService dialogService,
|
||||
IRegionManager regionManager
|
||||
) : base(container, regionManager)
|
||||
{
|
||||
_sysUserService= sysUserService;
|
||||
_outboxProcessor = outboxProcessor;
|
||||
_dialogService = dialogService;
|
||||
// 创建分页控件的 ViewModel,传递一个获取数据的委托
|
||||
_paginationDataGridViewModel = new PaginationDataGridViewModel<UserOutput>(FetchUsersAsync);
|
||||
@@ -132,6 +138,7 @@ namespace YY.Admin.ViewModels.SysManage
|
||||
EditCommand = new DelegateCommand<UserOutput>(async (user) => await ShowEditDialog(user));
|
||||
|
||||
StatusToggleCommand = new DelegateCommand<UserOutput>(async (user) => await ToggleStatus(user));
|
||||
ManualUploadCommand = new DelegateCommand(async () => await ManualUploadAsync(), () => !_isManualUploading);
|
||||
|
||||
//RowSelectionChangedCommand = new DelegateCommand<UserOutput>(OnRowSelectionChanged);
|
||||
|
||||
@@ -457,6 +464,31 @@ namespace YY.Admin.ViewModels.SysManage
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ManualUploadAsync()
|
||||
{
|
||||
if (_isManualUploading)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isManualUploading = true;
|
||||
ManualUploadCommand.RaiseCanExecuteChanged();
|
||||
try
|
||||
{
|
||||
await _outboxProcessor.FlushPendingAsync(CancellationToken.None);
|
||||
Growl.Success("已触发手动上传,请稍后查看后端数据。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Warning($"手动上传失败:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isManualUploading = false;
|
||||
ManualUploadCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
// 全选/取消全选方法
|
||||
//private void SelectAll(bool isSelected)
|
||||
//{
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
using HandyControl.Controls;
|
||||
using HandyControl.Tools.Extension;
|
||||
using System.Collections.ObjectModel;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Services.Service;
|
||||
|
||||
namespace YY.Admin.ViewModels.Vehicle;
|
||||
|
||||
public class VehicleEditDialogViewModel : BaseViewModel, IDialogResultable<bool>
|
||||
{
|
||||
private readonly IVehicleService _vehicleService;
|
||||
private readonly IJeecgDictSyncService _dictSyncService;
|
||||
|
||||
private MesXslVehicle? _vehicle;
|
||||
public MesXslVehicle? Vehicle
|
||||
{
|
||||
get => _vehicle;
|
||||
set => SetProperty(ref _vehicle, value);
|
||||
}
|
||||
|
||||
public bool IsAddMode => string.IsNullOrWhiteSpace(Vehicle?.Id);
|
||||
public string DialogTitle => IsAddMode ? "新增车辆" : "编辑车辆";
|
||||
|
||||
public ObservableCollection<KeyValuePair<string, string>> VehicleBelongOptions { get; } = new();
|
||||
|
||||
public ObservableCollection<KeyValuePair<string, string>> StatusOptions { get; } = new();
|
||||
|
||||
private bool _result;
|
||||
public bool Result { get => _result; set => SetProperty(ref _result, value); }
|
||||
public Action? CloseAction { get; set; }
|
||||
|
||||
public DelegateCommand SaveCommand { get; }
|
||||
public DelegateCommand CancelCommand { get; }
|
||||
|
||||
public VehicleEditDialogViewModel(
|
||||
IVehicleService vehicleService,
|
||||
IJeecgDictSyncService dictSyncService,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager) : base(container, regionManager)
|
||||
{
|
||||
_vehicleService = vehicleService;
|
||||
_dictSyncService = dictSyncService;
|
||||
SaveCommand = new DelegateCommand(async () => await SaveAsync());
|
||||
CancelCommand = new DelegateCommand(() => CloseAction?.Invoke());
|
||||
_ = LoadDictOptionsAsync();
|
||||
}
|
||||
|
||||
private async Task LoadDictOptionsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var belongOptions = await _dictSyncService.GetDictOptionsAsync("xslmes_vehicle_belong");
|
||||
var statusOptions = await _dictSyncService.GetDictOptionsAsync("xslmes_vehicle_status");
|
||||
|
||||
VehicleBelongOptions.Clear();
|
||||
foreach (var item in belongOptions)
|
||||
{
|
||||
VehicleBelongOptions.Add(item);
|
||||
}
|
||||
|
||||
StatusOptions.Clear();
|
||||
foreach (var item in statusOptions)
|
||||
{
|
||||
StatusOptions.Add(item);
|
||||
}
|
||||
|
||||
if (VehicleBelongOptions.Count == 0)
|
||||
{
|
||||
VehicleBelongOptions.Add(new KeyValuePair<string, string>("客户", "1"));
|
||||
VehicleBelongOptions.Add(new KeyValuePair<string, string>("供应商", "2"));
|
||||
VehicleBelongOptions.Add(new KeyValuePair<string, string>("本公司", "3"));
|
||||
}
|
||||
if (StatusOptions.Count == 0)
|
||||
{
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("启用", "0"));
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("停用", "1"));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
VehicleBelongOptions.Clear();
|
||||
VehicleBelongOptions.Add(new KeyValuePair<string, string>("客户", "1"));
|
||||
VehicleBelongOptions.Add(new KeyValuePair<string, string>("供应商", "2"));
|
||||
VehicleBelongOptions.Add(new KeyValuePair<string, string>("本公司", "3"));
|
||||
|
||||
StatusOptions.Clear();
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("启用", "0"));
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("停用", "1"));
|
||||
}
|
||||
}
|
||||
|
||||
public void InitializeForAdd()
|
||||
{
|
||||
Vehicle = new MesXslVehicle { Status = "0", VehicleBelong = "1" };
|
||||
RaisePropertyChanged(nameof(IsAddMode));
|
||||
RaisePropertyChanged(nameof(DialogTitle));
|
||||
}
|
||||
|
||||
public void InitializeForEdit(MesXslVehicle vehicle)
|
||||
{
|
||||
Vehicle = new MesXslVehicle
|
||||
{
|
||||
Id = vehicle.Id,
|
||||
PlateNumber = vehicle.PlateNumber,
|
||||
VehicleBelong = vehicle.VehicleBelong,
|
||||
TareWeightKg = vehicle.TareWeightKg,
|
||||
LoadCapacity = vehicle.LoadCapacity,
|
||||
UnitId = vehicle.UnitId,
|
||||
LoadUnit = vehicle.LoadUnit,
|
||||
CustomerIds = vehicle.CustomerIds,
|
||||
CustomerShortName = vehicle.CustomerShortName,
|
||||
SupplierId = vehicle.SupplierId,
|
||||
SupplierName = vehicle.SupplierName,
|
||||
SupplierShortName = vehicle.SupplierShortName,
|
||||
VehicleLength = vehicle.VehicleLength,
|
||||
VehicleWidth = vehicle.VehicleWidth,
|
||||
VehicleHeight = vehicle.VehicleHeight,
|
||||
DriverName = vehicle.DriverName,
|
||||
DriverPhone = vehicle.DriverPhone,
|
||||
Status = vehicle.Status,
|
||||
TenantId = vehicle.TenantId,
|
||||
};
|
||||
RaisePropertyChanged(nameof(IsAddMode));
|
||||
RaisePropertyChanged(nameof(DialogTitle));
|
||||
}
|
||||
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
if (Vehicle == null) return;
|
||||
if (string.IsNullOrWhiteSpace(Vehicle.PlateNumber))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("车牌号不能为空!");
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(Vehicle.VehicleBelong))
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Warning("车辆归属不能为空!");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
bool ok;
|
||||
if (IsAddMode)
|
||||
{
|
||||
ok = await _vehicleService.AddAsync(Vehicle);
|
||||
if (ok)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Success("新增车辆成功!");
|
||||
}
|
||||
else
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Error("新增车辆失败!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ok = await _vehicleService.EditAsync(Vehicle);
|
||||
if (!ok)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Error("编辑车辆失败!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
Result = ok;
|
||||
CloseAction?.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandyControl.Controls.MessageBox.Error($"操作失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
using HandyControl.Controls;
|
||||
using HandyControl.Tools.Extension;
|
||||
using Prism.Events;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Events;
|
||||
using YY.Admin.Core.Helper;
|
||||
using YY.Admin.Core.Entity;
|
||||
using YY.Admin.Core.Services;
|
||||
using YY.Admin.Services.Service;
|
||||
using YY.Admin.Views.Vehicle;
|
||||
|
||||
namespace YY.Admin.ViewModels.Vehicle;
|
||||
|
||||
public class VehicleListViewModel : BaseViewModel
|
||||
{
|
||||
private readonly IVehicleService _vehicleService;
|
||||
private readonly IJeecgDictSyncService _dictSyncService;
|
||||
private readonly IDialogService _dialogService;
|
||||
private SubscriptionToken? _vehicleChangedToken;
|
||||
private SubscriptionToken? _syncConflictToken;
|
||||
|
||||
private ObservableCollection<MesXslVehicle> _vehicles = new();
|
||||
public ObservableCollection<MesXslVehicle> Vehicles
|
||||
{
|
||||
get => _vehicles;
|
||||
set => SetProperty(ref _vehicles, value);
|
||||
}
|
||||
|
||||
private long _total;
|
||||
public long Total { get => _total; set => SetProperty(ref _total, value); }
|
||||
|
||||
private int _pageNo = 1;
|
||||
public int PageNo { get => _pageNo; set => SetProperty(ref _pageNo, value); }
|
||||
|
||||
private int _pageSize = 20;
|
||||
public int PageSize { get => _pageSize; set => SetProperty(ref _pageSize, value); }
|
||||
|
||||
private string? _filterPlateNumber;
|
||||
public string? FilterPlateNumber { get => _filterPlateNumber; set => SetProperty(ref _filterPlateNumber, value); }
|
||||
|
||||
private string? _filterVehicleBelong;
|
||||
public string? FilterVehicleBelong { get => _filterVehicleBelong; set => SetProperty(ref _filterVehicleBelong, value); }
|
||||
|
||||
private string? _filterStatus;
|
||||
public string? FilterStatus { get => _filterStatus; set => SetProperty(ref _filterStatus, value); }
|
||||
|
||||
public ObservableCollection<KeyValuePair<string, string>> VehicleBelongOptions { get; } = new();
|
||||
|
||||
public ObservableCollection<KeyValuePair<string, string>> StatusOptions { get; } = new();
|
||||
|
||||
public DelegateCommand SearchCommand { get; }
|
||||
public DelegateCommand ResetCommand { get; }
|
||||
public DelegateCommand AddCommand { get; }
|
||||
public DelegateCommand<MesXslVehicle> EditCommand { get; }
|
||||
public DelegateCommand<MesXslVehicle> DeleteCommand { get; }
|
||||
public DelegateCommand<MesXslVehicle> ToggleStatusCommand { get; }
|
||||
public DelegateCommand PrevPageCommand { get; }
|
||||
public DelegateCommand NextPageCommand { get; }
|
||||
|
||||
public VehicleListViewModel(
|
||||
IVehicleService vehicleService,
|
||||
IJeecgDictSyncService dictSyncService,
|
||||
IContainerExtension container,
|
||||
IDialogService dialogService,
|
||||
IRegionManager regionManager) : base(container, regionManager)
|
||||
{
|
||||
_vehicleService = vehicleService;
|
||||
_dictSyncService = dictSyncService;
|
||||
_dialogService = dialogService;
|
||||
|
||||
SearchCommand = new DelegateCommand(async () => { PageNo = 1; await LoadAsync(); });
|
||||
ResetCommand = new DelegateCommand(async () =>
|
||||
{
|
||||
FilterPlateNumber = null;
|
||||
FilterVehicleBelong = null;
|
||||
FilterStatus = null;
|
||||
PageNo = 1;
|
||||
await LoadAsync();
|
||||
});
|
||||
AddCommand = new DelegateCommand(async () => await ShowAddDialogAsync());
|
||||
EditCommand = new DelegateCommand<MesXslVehicle>(async v => await ShowEditDialogAsync(v));
|
||||
DeleteCommand = new DelegateCommand<MesXslVehicle>(async v => await DeleteAsync(v));
|
||||
ToggleStatusCommand = new DelegateCommand<MesXslVehicle>(async v => await ToggleStatusAsync(v));
|
||||
PrevPageCommand = new DelegateCommand(async () => { if (PageNo > 1) { PageNo--; await LoadAsync(); } });
|
||||
NextPageCommand = new DelegateCommand(async () => { if ((long)PageNo * PageSize < Total) { PageNo++; await LoadAsync(); } });
|
||||
|
||||
_vehicleChangedToken = _eventAggregator
|
||||
.GetEvent<VehicleChangedEvent>()
|
||||
.Subscribe(async p => await OnVehicleChangedAsync(p), ThreadOption.UIThread);
|
||||
|
||||
_syncConflictToken = _eventAggregator.GetEvent<SyncConflictEvent>()
|
||||
.Subscribe(OnSyncConflict, ThreadOption.UIThread);
|
||||
|
||||
_ = InitializeAsync();
|
||||
}
|
||||
|
||||
private async Task OnVehicleChangedAsync(VehicleChangedPayload payload)
|
||||
{
|
||||
if (payload.Action == "edit" && !string.IsNullOrWhiteSpace(payload.VehicleId))
|
||||
{
|
||||
await RefreshSingleAsync(payload.VehicleId!);
|
||||
}
|
||||
else
|
||||
{
|
||||
await LoadAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshSingleAsync(string vehicleId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var updated = await _vehicleService.GetByIdAsync(vehicleId);
|
||||
if (updated == null) return;
|
||||
var idx = Vehicles.ToList().FindIndex(v => string.Equals(v.Id, vehicleId, StringComparison.OrdinalIgnoreCase));
|
||||
if (idx >= 0)
|
||||
Vehicles[idx] = updated;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"[车辆] 单条刷新失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSyncConflict(SyncConflictPayload payload)
|
||||
{
|
||||
if (!string.Equals(payload.EntityName, "车辆", StringComparison.OrdinalIgnoreCase)) return;
|
||||
|
||||
var parts = new List<string>();
|
||||
if (payload.PushedCount > 0)
|
||||
parts.Add($"已同步 {payload.PushedCount} 条本地改动到服务器");
|
||||
if (payload.NewRecordsPushed > 0)
|
||||
parts.Add($"已上传 {payload.NewRecordsPushed} 条本地新增记录");
|
||||
if (payload.ConflictCount > 0)
|
||||
parts.Add($"{payload.ConflictCount} 条记录与服务器版本冲突,已保留服务器版本");
|
||||
|
||||
if (parts.Count == 0) return;
|
||||
|
||||
var message = string.Join("\n", parts);
|
||||
if (payload.ConflictCount > 0)
|
||||
Growl.Warning(message);
|
||||
else
|
||||
Growl.Success(message);
|
||||
}
|
||||
|
||||
private async Task InitializeAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await LoadDictOptionsAsync();
|
||||
await UIHelper.WaitForRenderAsync();
|
||||
await LoadAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"车辆列表初始化失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadDictOptionsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var belongOptions = await _dictSyncService.GetDictOptionsAsync("xslmes_vehicle_belong", includeAll: true);
|
||||
var statusOptions = await _dictSyncService.GetDictOptionsAsync("xslmes_vehicle_status", includeAll: true);
|
||||
|
||||
VehicleBelongOptions.Clear();
|
||||
foreach (var item in belongOptions)
|
||||
{
|
||||
VehicleBelongOptions.Add(item);
|
||||
}
|
||||
|
||||
StatusOptions.Clear();
|
||||
foreach (var item in statusOptions)
|
||||
{
|
||||
StatusOptions.Add(item);
|
||||
}
|
||||
|
||||
if (VehicleBelongOptions.Count == 0)
|
||||
{
|
||||
VehicleBelongOptions.Add(new KeyValuePair<string, string>("全部", ""));
|
||||
}
|
||||
if (StatusOptions.Count == 0)
|
||||
{
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("全部", ""));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
VehicleBelongOptions.Clear();
|
||||
VehicleBelongOptions.Add(new KeyValuePair<string, string>("全部", ""));
|
||||
VehicleBelongOptions.Add(new KeyValuePair<string, string>("客户", "1"));
|
||||
VehicleBelongOptions.Add(new KeyValuePair<string, string>("供应商", "2"));
|
||||
VehicleBelongOptions.Add(new KeyValuePair<string, string>("本公司", "3"));
|
||||
|
||||
StatusOptions.Clear();
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("全部", ""));
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("启用", "0"));
|
||||
StatusOptions.Add(new KeyValuePair<string, string>("停用", "1"));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task LoadAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
var result = await _vehicleService.PageAsync(PageNo, PageSize, FilterPlateNumber, FilterVehicleBelong, FilterStatus);
|
||||
Vehicles = new ObservableCollection<MesXslVehicle>(result.Records);
|
||||
Total = result.Total;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"加载车辆列表失败:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowAddDialogAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await HandyControl.Controls.Dialog.Show<VehicleEditDialogView>()
|
||||
.Initialize<VehicleEditDialogViewModel>(vm => vm.InitializeForAdd())
|
||||
.GetResultAsync<bool>();
|
||||
if (result) await LoadAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"打开新增对话框失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowEditDialogAsync(MesXslVehicle vehicle)
|
||||
{
|
||||
if (vehicle == null) return;
|
||||
try
|
||||
{
|
||||
var result = await HandyControl.Controls.Dialog.Show<VehicleEditDialogView>()
|
||||
.Initialize<VehicleEditDialogViewModel>(vm => vm.InitializeForEdit(vehicle))
|
||||
.GetResultAsync<bool>();
|
||||
if (result) await LoadAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"打开编辑对话框失败:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteAsync(MesXslVehicle vehicle)
|
||||
{
|
||||
if (vehicle?.Id == null) return;
|
||||
var confirm = System.Windows.MessageBox.Show($"确定删除车辆 {vehicle.PlateNumber}?此操作不可恢复!", "确认删除",
|
||||
MessageBoxButton.OKCancel, MessageBoxImage.Question);
|
||||
if (confirm != System.Windows.MessageBoxResult.OK) return;
|
||||
|
||||
var ok = await _vehicleService.DeleteAsync(vehicle.Id);
|
||||
if (ok)
|
||||
{
|
||||
Growl.Success("删除成功!");
|
||||
await LoadAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
Growl.Error("删除失败!");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ToggleStatusAsync(MesXslVehicle vehicle)
|
||||
{
|
||||
if (vehicle?.Id == null) return;
|
||||
var newStatus = vehicle.Status == "1" ? "0" : "1";
|
||||
var ok = await _vehicleService.UpdateStatusAsync(vehicle.Id, newStatus);
|
||||
if (ok)
|
||||
{
|
||||
vehicle.Status = newStatus;
|
||||
RaisePropertyChanged(nameof(Vehicles));
|
||||
}
|
||||
else
|
||||
{
|
||||
Growl.Error("状态切换失败!");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void CleanUp()
|
||||
{
|
||||
base.CleanUp();
|
||||
if (_vehicleChangedToken != null)
|
||||
{
|
||||
_eventAggregator.GetEvent<VehicleChangedEvent>().Unsubscribe(_vehicleChangedToken);
|
||||
_vehicleChangedToken = null;
|
||||
}
|
||||
|
||||
if (_syncConflictToken != null)
|
||||
{
|
||||
_eventAggregator.GetEvent<SyncConflictEvent>().Unsubscribe(_syncConflictToken);
|
||||
_syncConflictToken = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user