新增物料类型处理逻辑,确保在保存和编辑称重记录时自动设置默认物料类型。更新前端表单以支持密炼物料的选择和显示,优化用户体验。添加分类字典和数据字典的事件广播功能,增强系统的实时数据同步能力。

This commit is contained in:
geht
2026-05-07 17:53:48 +08:00
parent ce9dc8efd8
commit f60a4fb203
55 changed files with 2968 additions and 375 deletions

View File

@@ -62,6 +62,28 @@ public class MixerMaterialListViewModel : BaseViewModel
public ObservableCollection<KeyValuePair<string, string>> MajorCategoryOptions { get; } = new();
public ObservableCollection<KeyValuePair<string, string>> MinorCategoryOptions { get; } = new();
public ObservableCollection<CategoryFilterNode> TreeNodes { get; } = new();
private CategoryFilterNode? _selectedTreeNode;
public CategoryFilterNode? SelectedTreeNode
{
get => _selectedTreeNode;
set => SetProperty(ref _selectedTreeNode, value);
}
private bool _isTreeAllExpanded = true;
public bool IsTreeAllExpanded
{
get => _isTreeAllExpanded;
set
{
if (SetProperty(ref _isTreeAllExpanded, value))
RaisePropertyChanged(nameof(ToggleExpandButtonText));
}
}
public string ToggleExpandButtonText => _isTreeAllExpanded ? "折叠全部" : "展开全部";
public DelegateCommand SearchCommand { get; }
public DelegateCommand ResetCommand { get; }
public DelegateCommand AddCommand { get; }
@@ -69,6 +91,7 @@ public class MixerMaterialListViewModel : BaseViewModel
public DelegateCommand<MesMixerMaterial> DeleteCommand { get; }
public DelegateCommand PrevPageCommand { get; }
public DelegateCommand NextPageCommand { get; }
public DelegateCommand ToggleExpandCommand { get; }
public MixerMaterialListViewModel(
IMixerMaterialService mixerMaterialService,
@@ -83,10 +106,12 @@ public class MixerMaterialListViewModel : BaseViewModel
FilterMaterialCode = null;
FilterMaterialName = null;
FilterErpCode = null;
FilterMajorCategoryId = null;
_filterMajorCategoryId = null;
RaisePropertyChanged(nameof(FilterMajorCategoryId));
FilterMinorCategoryId = null;
PageNo = 1;
SelectedTreeNode = null;
await LoadMinorCategoryOptionsAsync();
PageNo = 1;
await LoadAsync();
});
AddCommand = new DelegateCommand(async () => await ShowAddDialogAsync());
@@ -94,6 +119,7 @@ public class MixerMaterialListViewModel : BaseViewModel
DeleteCommand = new DelegateCommand<MesMixerMaterial>(async m => await DeleteAsync(m));
PrevPageCommand = new DelegateCommand(async () => { if (PageNo > 1) { PageNo--; await LoadAsync(); } });
NextPageCommand = new DelegateCommand(async () => { if ((long)PageNo * PageSize < Total) { PageNo++; await LoadAsync(); } });
ToggleExpandCommand = new DelegateCommand(ToggleExpand);
_materialChangedToken = _eventAggregator
.GetEvent<MixerMaterialChangedEvent>()
@@ -106,6 +132,7 @@ public class MixerMaterialListViewModel : BaseViewModel
{
try
{
await LoadCategoryTreeAsync();
await LoadMajorCategoryOptionsAsync();
await LoadMinorCategoryOptionsAsync();
await UIHelper.WaitForRenderAsync();
@@ -117,6 +144,70 @@ public class MixerMaterialListViewModel : BaseViewModel
}
}
private async Task LoadCategoryTreeAsync()
{
try
{
var tree = await _mixerMaterialService.GetMaterialCategoryTreeAsync();
TreeNodes.Clear();
var root = new CategoryFilterNode("", "全部", null, false, null, []) { IsExpanded = true };
foreach (var major in tree)
{
var majorNode = new CategoryFilterNode(major.Id, major.Name ?? major.Code ?? major.Id, major.Code, true, null, []);
foreach (var minor in major.Children)
{
majorNode.Children.Add(new CategoryFilterNode(minor.Id, minor.Name ?? minor.Code ?? minor.Id, minor.Code, false, major.Id, []));
}
root.Children.Add(majorNode);
}
TreeNodes.Add(root);
IsTreeAllExpanded = false;
}
catch (Exception ex)
{
Debug.WriteLine($"加载物料分类树失败: {ex.Message}");
}
}
private void ToggleExpand()
{
var expand = !_isTreeAllExpanded;
foreach (var root in TreeNodes)
{
root.IsExpanded = true; // 根节点始终展开
foreach (var major in root.Children)
major.IsExpanded = expand;
}
IsTreeAllExpanded = expand;
}
public async Task OnTreeNodeSelectedAsync(CategoryFilterNode? node)
{
SelectedTreeNode = node;
if (node == null || node.Id == "")
{
_filterMajorCategoryId = null;
RaisePropertyChanged(nameof(FilterMajorCategoryId));
FilterMinorCategoryId = null;
}
else if (node.IsMajor)
{
_filterMajorCategoryId = node.Id;
RaisePropertyChanged(nameof(FilterMajorCategoryId));
FilterMinorCategoryId = null;
}
else
{
_filterMajorCategoryId = node.ParentId;
RaisePropertyChanged(nameof(FilterMajorCategoryId));
FilterMinorCategoryId = node.Id;
}
await LoadMinorCategoryOptionsAsync();
PageNo = 1;
await LoadAsync();
}
private async Task LoadMajorCategoryOptionsAsync()
{
MajorCategoryOptions.Clear();
@@ -132,11 +223,11 @@ public class MixerMaterialListViewModel : BaseViewModel
{
MinorCategoryOptions.Clear();
MinorCategoryOptions.Add(new KeyValuePair<string, string>("全部", ""));
if (string.IsNullOrWhiteSpace(FilterMajorCategoryId))
if (string.IsNullOrWhiteSpace(_filterMajorCategoryId))
{
return;
}
var options = await _mixerMaterialService.GetMinorCategoryOptionsAsync(FilterMajorCategoryId!);
var options = await _mixerMaterialService.GetMinorCategoryOptionsAsync(_filterMajorCategoryId!);
foreach (var item in options)
{
MinorCategoryOptions.Add(item);
@@ -155,7 +246,7 @@ public class MixerMaterialListViewModel : BaseViewModel
{
IsLoading = true;
var result = await _mixerMaterialService.PageAsync(
PageNo, PageSize, FilterMaterialCode, FilterMaterialName, FilterErpCode, FilterMajorCategoryId, FilterMinorCategoryId);
PageNo, PageSize, FilterMaterialCode, FilterMaterialName, FilterErpCode, _filterMajorCategoryId, FilterMinorCategoryId);
Materials = new ObservableCollection<MesMixerMaterial>(result.Records);
Total = result.Total;
}
@@ -228,5 +319,40 @@ public class MixerMaterialListViewModel : BaseViewModel
_materialChangedToken = null;
}
}
}
public class CategoryFilterNode : System.ComponentModel.INotifyPropertyChanged
{
public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged;
public string Id { get; }
public string DisplayName { get; }
public string? Code { get; }
public bool IsMajor { get; }
public string? ParentId { get; }
public ObservableCollection<CategoryFilterNode> Children { get; }
private bool _isExpanded;
public bool IsExpanded
{
get => _isExpanded;
set
{
if (_isExpanded != value)
{
_isExpanded = value;
PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(nameof(IsExpanded)));
}
}
}
public CategoryFilterNode(string id, string? displayName, string? code, bool isMajor, string? parentId, List<CategoryFilterNode> children)
{
Id = id;
DisplayName = displayName ?? id;
Code = code;
IsMajor = isMajor;
ParentId = parentId;
Children = new ObservableCollection<CategoryFilterNode>(children);
}
}
}

View File

@@ -1,6 +1,8 @@
using HandyControl.Controls;
using Prism.Events;
using System.Collections.ObjectModel;
using YY.Admin.Core;
using YY.Admin.Core.Events;
using YY.Admin.Core.Helper;
using YY.Admin.Services.Service.Category;
using YY.Admin.Services.Service.Category.Dto;
@@ -11,6 +13,7 @@ namespace YY.Admin.ViewModels.SysManage;
public class CategoryDictionaryManagementViewModel : BaseViewModel
{
private readonly IJeecgCategorySyncService _categorySyncService;
private SubscriptionToken? _categoryChangedToken;
private PaginationDataGridViewModel<JeecgCategoryItemOutput> _paginationDataGridViewModel;
public PaginationDataGridViewModel<JeecgCategoryItemOutput> PaginationDataGridViewModel
@@ -28,7 +31,6 @@ public class CategoryDictionaryManagementViewModel : BaseViewModel
public DelegateCommand SearchCommand { get; }
public DelegateCommand ResetCommand { get; }
public DelegateCommand SyncCommand { get; }
public ObservableCollection<CategoryTreeNode> TreeNodes { get; } = [];
private CategoryTreeNode? _selectedTreeNode;
@@ -49,7 +51,10 @@ public class CategoryDictionaryManagementViewModel : BaseViewModel
SearchCommand = new DelegateCommand(async () => await SearchAsync());
ResetCommand = new DelegateCommand(async () => await ResetAsync());
SyncCommand = new DelegateCommand(async () => await SyncAsync());
_categoryChangedToken = _eventAggregator
.GetEvent<CategoryChangedEvent>()
.Subscribe(async _ => await OnCategoryChangedAsync(), ThreadOption.UIThread);
_ = InitializeAsync();
}
@@ -84,21 +89,6 @@ public class CategoryDictionaryManagementViewModel : BaseViewModel
await SearchAsync();
}
private async Task SyncAsync()
{
var count = await _categorySyncService.SyncFromJeecgAsync();
if (count > 0)
{
Growl.Success($"同步完成,共处理 {count} 条分类字典数据");
}
else
{
Growl.Warning("未同步到分类字典,请确认后端可访问");
}
await LoadTreeAsync();
await SearchAsync();
}
public async Task OnTreeSelectedAsync(CategoryTreeNode? node)
{
SelectedTreeNode = node;
@@ -131,6 +121,22 @@ public class CategoryDictionaryManagementViewModel : BaseViewModel
return node;
}
private async Task OnCategoryChangedAsync()
{
await LoadTreeAsync();
await PaginationDataGridViewModel.LoadDataAsync();
}
protected override void CleanUp()
{
base.CleanUp();
if (_categoryChangedToken != null)
{
_eventAggregator.GetEvent<CategoryChangedEvent>().Unsubscribe(_categoryChangedToken);
_categoryChangedToken = null;
}
}
public class CategoryTreeNode
{
public string Id { get; set; } = string.Empty;

View File

@@ -1,5 +1,7 @@
using HandyControl.Controls;
using Prism.Events;
using System.Collections.ObjectModel;
using YY.Admin.Core;
using YY.Admin.Core.Events;
using YY.Admin.Core.Extension;
using YY.Admin.Core.Helper;
using YY.Admin.Services.Service;
@@ -10,6 +12,16 @@ namespace YY.Admin.ViewModels.SysManage;
public class DataDictionaryManagementViewModel : BaseViewModel
{
private readonly IJeecgDictSyncService _dictSyncService;
private SubscriptionToken? _dictChangedToken;
public ObservableCollection<DictTreeNode> TreeNodes { get; } = [];
private DictTreeNode? _selectedTreeNode;
public DictTreeNode? SelectedTreeNode
{
get => _selectedTreeNode;
set => SetProperty(ref _selectedTreeNode, value);
}
private PaginationDataGridViewModel<JeecgDictItemOutput> _paginationDataGridViewModel;
public PaginationDataGridViewModel<JeecgDictItemOutput> PaginationDataGridViewModel
@@ -33,7 +45,6 @@ public class DataDictionaryManagementViewModel : BaseViewModel
public DelegateCommand SearchCommand { get; }
public DelegateCommand ResetCommand { get; }
public DelegateCommand SyncCommand { get; }
public DataDictionaryManagementViewModel(
IJeecgDictSyncService dictSyncService,
@@ -46,13 +57,17 @@ public class DataDictionaryManagementViewModel : BaseViewModel
SearchCommand = new DelegateCommand(async () => await SearchAsync());
ResetCommand = new DelegateCommand(async () => await ResetAsync());
SyncCommand = new DelegateCommand(async () => await SyncAsync());
_dictChangedToken = _eventAggregator
.GetEvent<DictChangedEvent>()
.Subscribe(async _ => await OnDictChangedAsync(), ThreadOption.UIThread);
_ = InitializeAsync();
}
private async Task InitializeAsync()
{
await LoadDictTreeAsync();
await UIHelper.WaitForRenderAsync();
await PaginationDataGridViewModel.LoadDataAsync();
}
@@ -74,21 +89,54 @@ public class DataDictionaryManagementViewModel : BaseViewModel
private async Task ResetAsync()
{
Input = new PageJeecgDictItemInput();
SelectedTreeNode = null;
await UIHelper.WaitForRenderAsync();
await SearchAsync();
}
private async Task SyncAsync()
public async Task OnDictTreeSelectedAsync(DictTreeNode? node)
{
var count = await _dictSyncService.SyncFromJeecgAsync();
if (count > 0)
{
Growl.Success($"同步完成,共处理 {count} 条数据字典项");
}
else
{
Growl.Warning("未同步到数据字典,请确认后端可访问");
}
SelectedTreeNode = node;
Input.DictCode = node?.DictCode;
await SearchAsync();
}
private async Task LoadDictTreeAsync()
{
var groups = await _dictSyncService.GetDictGroupsAsync();
TreeNodes.Clear();
foreach (var group in groups)
{
TreeNodes.Add(new DictTreeNode(group.Key, group.Value));
}
}
private async Task OnDictChangedAsync()
{
await LoadDictTreeAsync();
await PaginationDataGridViewModel.LoadDataAsync();
}
protected override void CleanUp()
{
base.CleanUp();
if (_dictChangedToken != null)
{
_eventAggregator.GetEvent<DictChangedEvent>().Unsubscribe(_dictChangedToken);
_dictChangedToken = null;
}
}
public class DictTreeNode
{
public string DictCode { get; }
public string DictName { get; }
public string DisplayName => $"[{DictCode}] {DictName}";
public DictTreeNode(string dictCode, string dictName)
{
DictCode = dictCode;
DictName = dictName;
}
}
}

View File

@@ -0,0 +1,129 @@
using HandyControl.Tools.Extension;
using System.Collections.ObjectModel;
using YY.Admin.Core;
using YY.Admin.Core.Entity;
using YY.Admin.Core.Services;
namespace YY.Admin.ViewModels.WeightRecord;
public class MixerMaterialPickerDialogViewModel : BaseViewModel, IDialogResultable<bool>
{
private readonly IMixerMaterialService _mixerMaterialService;
private string? _searchCode;
public string? SearchCode { get => _searchCode; set => SetProperty(ref _searchCode, value); }
private string? _searchName;
public string? SearchName { get => _searchName; set => SetProperty(ref _searchName, value); }
public ObservableCollection<SelectableMixerMaterial> Materials { get; } = new();
public IReadOnlyList<SelectableMixerMaterial> SelectedMaterials =>
Materials.Where(m => m.IsSelected).ToList();
public int SelectedCount => Materials.Count(m => m.IsSelected);
public string SelectedDisplayText
{
get
{
var names = Materials.Where(m => m.IsSelected).Select(m => m.Source.MaterialName).ToList();
return names.Count == 0 ? "请在上方列表中勾选物料,可多选" : string.Join("", names);
}
}
public bool HasSelectedMaterials => Materials.Any(m => m.IsSelected);
private bool _result;
public bool Result { get => _result; set => SetProperty(ref _result, value); }
public Action? CloseAction { get; set; }
public DelegateCommand SearchCommand { get; }
public DelegateCommand ConfirmCommand { get; }
public DelegateCommand CancelCommand { get; }
public DelegateCommand ClearSelectionCommand { get; }
public MixerMaterialPickerDialogViewModel(
IMixerMaterialService mixerMaterialService,
IContainerExtension container,
IRegionManager regionManager) : base(container, regionManager)
{
_mixerMaterialService = mixerMaterialService;
SearchCommand = new DelegateCommand(async () => await LoadAsync());
ConfirmCommand = new DelegateCommand(Confirm, () => HasSelectedMaterials);
CancelCommand = new DelegateCommand(() => CloseAction?.Invoke());
ClearSelectionCommand = new DelegateCommand(ClearSelection);
_ = LoadAsync();
}
private async Task LoadAsync()
{
try
{
IsLoading = true;
var selectedIds = Materials.Where(m => m.IsSelected).Select(m => m.Source.Id).ToHashSet();
var result = await _mixerMaterialService.PageAsync(1, 500,
materialCode: SearchCode?.Trim(),
materialName: SearchName?.Trim());
Materials.Clear();
foreach (var mat in result.Records)
{
var item = new SelectableMixerMaterial(mat);
if (selectedIds.Contains(mat.Id)) item.IsSelected = true;
item.SelectionChanged += NotifySelectionChanged;
Materials.Add(item);
}
NotifySelectionChanged();
}
catch
{
Materials.Clear();
}
finally
{
IsLoading = false;
}
}
private void NotifySelectionChanged()
{
RaisePropertyChanged(nameof(SelectedCount));
RaisePropertyChanged(nameof(SelectedDisplayText));
RaisePropertyChanged(nameof(HasSelectedMaterials));
ConfirmCommand.RaiseCanExecuteChanged();
}
private void ClearSelection()
{
foreach (var m in Materials) m.IsSelected = false;
}
private void Confirm()
{
if (!HasSelectedMaterials) return;
Result = true;
CloseAction?.Invoke();
}
public class SelectableMixerMaterial : System.ComponentModel.INotifyPropertyChanged
{
public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged;
public event Action? SelectionChanged;
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set
{
if (_isSelected == value) return;
_isSelected = value;
PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(nameof(IsSelected)));
SelectionChanged?.Invoke();
}
}
public MesMixerMaterial Source { get; }
public SelectableMixerMaterial(MesMixerMaterial source) => Source = source;
}
}

View File

@@ -5,6 +5,7 @@ using YY.Admin.Core;
using YY.Admin.Core.Entity;
using YY.Admin.Core.Services;
using YY.Admin.Services.Service;
using YY.Admin.Views.WeightRecord;
namespace YY.Admin.ViewModels.WeightRecord;
@@ -12,6 +13,16 @@ public class WeightRecordEditDialogViewModel : BaseViewModel, IDialogResultable<
{
private readonly IWeightRecordService _weightRecordService;
private readonly IJeecgDictSyncService _dictSyncService;
private readonly IMixerMaterialService _mixerMaterialService;
private string? _mixerMaterialIds;
private string? _mixerMaterialNames;
public string MixerMaterialDisplay => !string.IsNullOrWhiteSpace(_mixerMaterialNames)
? _mixerMaterialNames
: "点击右侧「选择」选取密炼物料(可多选)";
public bool HasSelectedMixerMaterials => !string.IsNullOrWhiteSpace(_mixerMaterialNames);
private MesXslWeightRecord? _record;
public MesXslWeightRecord? Record
@@ -31,20 +42,56 @@ public class WeightRecordEditDialogViewModel : BaseViewModel, IDialogResultable<
public DelegateCommand SaveCommand { get; }
public DelegateCommand CancelCommand { get; }
public DelegateCommand OpenMixerMaterialPickerCommand { get; }
public DelegateCommand ClearMixerMaterialCommand { get; }
public WeightRecordEditDialogViewModel(
IWeightRecordService weightRecordService,
IJeecgDictSyncService dictSyncService,
IMixerMaterialService mixerMaterialService,
IContainerExtension container,
IRegionManager regionManager) : base(container, regionManager)
{
_weightRecordService = weightRecordService;
_dictSyncService = dictSyncService;
_mixerMaterialService = mixerMaterialService;
SaveCommand = new DelegateCommand(async () => await SaveAsync());
CancelCommand = new DelegateCommand(() => CloseAction?.Invoke());
OpenMixerMaterialPickerCommand = new DelegateCommand(async () => await OpenMixerMaterialPickerAsync());
ClearMixerMaterialCommand = new DelegateCommand(ClearMixerMaterialSelection);
_ = LoadDictOptionsAsync();
}
private async Task OpenMixerMaterialPickerAsync()
{
MixerMaterialPickerDialogViewModel? pickerVm = null;
bool confirmed;
try
{
confirmed = await HandyControl.Controls.Dialog.Show<MixerMaterialPickerDialogView>()
.Initialize<MixerMaterialPickerDialogViewModel>(vm => { pickerVm = vm; })
.GetResultAsync<bool>();
}
catch { return; }
if (!confirmed || pickerVm == null) return;
var selected = pickerVm.SelectedMaterials;
if (selected.Count == 0) return;
_mixerMaterialIds = string.Join("", selected.Select(m => m.Source.Id ?? ""));
_mixerMaterialNames = string.Join("", selected.Select(m => m.Source.MaterialName ?? ""));
RaisePropertyChanged(nameof(MixerMaterialDisplay));
RaisePropertyChanged(nameof(HasSelectedMixerMaterials));
}
private void ClearMixerMaterialSelection()
{
_mixerMaterialIds = null;
_mixerMaterialNames = null;
RaisePropertyChanged(nameof(MixerMaterialDisplay));
RaisePropertyChanged(nameof(HasSelectedMixerMaterials));
}
private async Task LoadDictOptionsAsync()
{
try
@@ -69,7 +116,8 @@ public class WeightRecordEditDialogViewModel : BaseViewModel, IDialogResultable<
Record = new MesXslWeightRecord
{
WeighDate = DateTime.Today,
InoutDirection = "1"
InoutDirection = "1",
MaterialType = "1"
};
RaisePropertyChanged(nameof(IsAddMode));
RaisePropertyChanged(nameof(DialogTitle));
@@ -87,16 +135,20 @@ public class WeightRecordEditDialogViewModel : BaseViewModel, IDialogResultable<
PlateNumber = record.PlateNumber,
SenderUnit = record.SenderUnit,
ReceiverUnit = record.ReceiverUnit,
GoodsName = record.GoodsName,
GrossWeight = record.GrossWeight,
TareWeight = record.TareWeight,
NetWeight = record.NetWeight,
DriverName = record.DriverName,
DriverPhone = record.DriverPhone,
BillType = record.BillType,
MaterialType = record.MaterialType,
TenantId = record.TenantId,
UpdateTime = record.UpdateTime
};
_mixerMaterialIds = record.MixerMaterialIds;
_mixerMaterialNames = record.MixerMaterialNames;
RaisePropertyChanged(nameof(MixerMaterialDisplay));
RaisePropertyChanged(nameof(HasSelectedMixerMaterials));
RaisePropertyChanged(nameof(IsAddMode));
RaisePropertyChanged(nameof(DialogTitle));
}
@@ -120,6 +172,12 @@ public class WeightRecordEditDialogViewModel : BaseViewModel, IDialogResultable<
if (Record.GrossWeight.HasValue && Record.TareWeight.HasValue)
Record.NetWeight = Math.Round(Record.GrossWeight.Value - Record.TareWeight.Value, 2);
// 写入密炼物料
Record.MixerMaterialIds = _mixerMaterialIds;
Record.MixerMaterialNames = _mixerMaterialNames;
if (string.IsNullOrWhiteSpace(Record.MaterialType))
Record.MaterialType = "1";
try
{
bool ok;

View File

@@ -47,14 +47,15 @@ public class WeightRecordListViewModel : BaseViewModel
private string? _filterInoutDirection;
public string? FilterInoutDirection { get => _filterInoutDirection; set => SetProperty(ref _filterInoutDirection, value); }
private string? _filterGoodsName;
public string? FilterGoodsName { get => _filterGoodsName; set => SetProperty(ref _filterGoodsName, value); }
private string? _filterDriverName;
public string? FilterDriverName { get => _filterDriverName; set => SetProperty(ref _filterDriverName, value); }
private string? _filterMixerMaterialName;
public string? FilterMixerMaterialName { get => _filterMixerMaterialName; set => SetProperty(ref _filterMixerMaterialName, value); }
public ObservableCollection<KeyValuePair<string, string>> InoutDirectionOptions { get; } = new();
public ObservableCollection<KeyValuePair<string, string>> BillTypeOptions { get; } = new();
public ObservableCollection<KeyValuePair<string, string>> MaterialTypeOptions { get; } = new();
public DelegateCommand SearchCommand { get; }
public DelegateCommand ResetCommand { get; }
@@ -79,7 +80,7 @@ public class WeightRecordListViewModel : BaseViewModel
ResetCommand = new DelegateCommand(async () =>
{
FilterBillNo = null; FilterPlateNumber = null; FilterInoutDirection = null;
FilterGoodsName = null; FilterDriverName = null;
FilterDriverName = null; FilterMixerMaterialName = null;
PageNo = 1;
await LoadAsync();
});
@@ -133,6 +134,11 @@ public class WeightRecordListViewModel : BaseViewModel
BillTypeOptions.Clear();
foreach (var item in billTypeOptions) BillTypeOptions.Add(item);
if (BillTypeOptions.Count == 0) AddDefaultBillTypeOptions();
var materialTypeOptions = await _dictSyncService.GetDictOptionsAsync("xslmes_weight_material_type", includeAll: true);
MaterialTypeOptions.Clear();
foreach (var item in materialTypeOptions) MaterialTypeOptions.Add(item);
if (MaterialTypeOptions.Count == 0) AddDefaultMaterialTypeOptions();
}
catch
{
@@ -141,6 +147,7 @@ public class WeightRecordListViewModel : BaseViewModel
InoutDirectionOptions.Add(new KeyValuePair<string, string>("进厂", "1"));
InoutDirectionOptions.Add(new KeyValuePair<string, string>("出厂", "2"));
AddDefaultBillTypeOptions();
AddDefaultMaterialTypeOptions();
}
}
@@ -153,21 +160,31 @@ public class WeightRecordListViewModel : BaseViewModel
BillTypeOptions.Add(new KeyValuePair<string, string>("已称皮重", "3"));
}
private void AddDefaultMaterialTypeOptions()
{
MaterialTypeOptions.Clear();
MaterialTypeOptions.Add(new KeyValuePair<string, string>("全部", ""));
MaterialTypeOptions.Add(new KeyValuePair<string, string>("自动", "1"));
MaterialTypeOptions.Add(new KeyValuePair<string, string>("手动", "2"));
}
public async Task LoadAsync()
{
try
{
IsLoading = true;
var result = await _weightRecordService.PageAsync(
PageNo, PageSize, FilterBillNo, FilterPlateNumber, FilterInoutDirection, FilterGoodsName, FilterDriverName);
PageNo, PageSize, FilterBillNo, FilterPlateNumber, FilterInoutDirection, filterDriverName: FilterDriverName, filterMixerMaterialName: FilterMixerMaterialName);
// 填充字典显示文本
var dictMap = InoutDirectionOptions.ToDictionary(x => x.Value, x => x.Key);
var billTypeMap = BillTypeOptions.ToDictionary(x => x.Value, x => x.Key);
var materialTypeMap = MaterialTypeOptions.ToDictionary(x => x.Value, x => x.Key);
foreach (var r in result.Records)
{
r.InoutDirectionText = dictMap.TryGetValue(r.InoutDirection ?? "", out var txt) ? txt : r.InoutDirection ?? "";
r.BillTypeText = billTypeMap.TryGetValue(r.BillType ?? "", out var billTypeTxt) ? billTypeTxt : r.BillType ?? "";
r.MaterialTypeText = materialTypeMap.TryGetValue(r.MaterialType ?? "", out var materialTypeTxt) ? materialTypeTxt : r.MaterialType ?? "";
}
Records = new ObservableCollection<MesXslWeightRecord>(result.Records);

View File

@@ -1,6 +1,8 @@
using HandyControl.Controls;
using HandyControl.Tools.Extension;
using System.Collections.ObjectModel;
using System.IO;
using System.Text.Json;
using System.Windows.Threading;
using YY.Admin.Core;
using YY.Admin.Core.Entity;
@@ -19,6 +21,8 @@ public class WeightRecordOperationViewModel : BaseViewModel
private readonly IWeightRecordService _weightRecordService;
private readonly IJeecgDictSyncService _dictSyncService;
private readonly IVehicleService _vehicleService;
private readonly IMixerMaterialService _mixerMaterialService;
private readonly string _unitCacheFilePath;
// ─── 车辆档案匹配 ───
private MesXslVehicle? _matchedVehicle;
@@ -115,6 +119,7 @@ public class WeightRecordOperationViewModel : BaseViewModel
{
ClearFormByDirectionSwitch();
}
TryApplyCachedUnitByDirection(value);
_ = LoadRecentWeightRecordsAsync(PlateNumber);
}
}
@@ -142,6 +147,8 @@ public class WeightRecordOperationViewModel : BaseViewModel
private MesXslSupplier? _selectedSupplier;
private MesXslCustomer? _selectedCustomer;
private string? _cachedInboundReceiverUnit;
private string? _cachedOutboundSenderUnit;
public bool HasSelectedSupplier => _selectedSupplier != null;
public bool HasSelectedCustomer => _selectedCustomer != null;
@@ -176,8 +183,52 @@ public class WeightRecordOperationViewModel : BaseViewModel
}
}
private string? _goodsName;
public string? GoodsName { get => _goodsName; set => SetProperty(ref _goodsName, value); }
// ─── 密炼物料选择 ───
private string? _mixerMaterialIds;
private string? _mixerMaterialNames;
private bool _isMixerMaterialManual;
public bool IsMixerMaterialManual
{
get => _isMixerMaterialManual;
set
{
if (!SetProperty(ref _isMixerMaterialManual, value)) return;
// 切换为手动填写时,清空 ID避免保存时出现“名称手填 + ID遗留”不一致
if (value)
{
_mixerMaterialIds = null;
RaisePropertyChanged(nameof(HasSelectedMixerMaterials));
}
RaisePropertyChanged(nameof(IsMixerMaterialReadOnly));
OpenMixerMaterialPickerCommand.RaiseCanExecuteChanged();
}
}
public bool IsMixerMaterialReadOnly => !IsMixerMaterialManual;
public string? MixerMaterialNames
{
get => _mixerMaterialNames;
set
{
if (!SetProperty(ref _mixerMaterialNames, value)) return;
// 手动填写时名称由人工输入ID 不再有效
if (IsMixerMaterialManual)
{
_mixerMaterialIds = null;
}
RaisePropertyChanged(nameof(MixerMaterialDisplay));
RaisePropertyChanged(nameof(HasSelectedMixerMaterials));
}
}
public string MixerMaterialDisplay => !string.IsNullOrWhiteSpace(_mixerMaterialNames)
? _mixerMaterialNames
: "点击右侧「选择」选取密炼物料(可多选)";
public bool HasSelectedMixerMaterials => !string.IsNullOrWhiteSpace(_mixerMaterialNames);
private double? _grossWeight;
public double? GrossWeight
@@ -234,19 +285,30 @@ public class WeightRecordOperationViewModel : BaseViewModel
public DelegateCommand UseDetectedPlateCommand { get; }
public DelegateCommand OpenSupplierPickerCommand { get; }
public DelegateCommand OpenCustomerPickerCommand { get; }
public DelegateCommand OpenMixerMaterialPickerCommand { get; }
public DelegateCommand ClearSupplierCommand { get; }
public DelegateCommand ClearCustomerCommand { get; }
public DelegateCommand ClearMixerMaterialCommand { get; }
public WeightRecordOperationViewModel(
IWeightRecordService weightRecordService,
IJeecgDictSyncService dictSyncService,
IVehicleService vehicleService,
IMixerMaterialService mixerMaterialService,
IContainerExtension container,
IRegionManager regionManager) : base(container, regionManager)
{
_weightRecordService = weightRecordService;
_dictSyncService = dictSyncService;
_vehicleService = vehicleService;
_mixerMaterialService = mixerMaterialService;
var cacheDir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"YY.Admin",
"cache");
Directory.CreateDirectory(cacheDir);
_unitCacheFilePath = Path.Combine(cacheDir, "weight-record-unit-cache.json");
LoadUnitCache();
CaptureGrossWeightCommand = new DelegateCommand(CaptureGrossWeight, CanCaptureGrossWeight)
.ObservesProperty(() => IsWeightStable)
@@ -262,11 +324,15 @@ public class WeightRecordOperationViewModel : BaseViewModel
.ObservesProperty(() => HasPlate);
OpenSupplierPickerCommand = new DelegateCommand(async () => await OpenSupplierPickerAsync());
OpenCustomerPickerCommand = new DelegateCommand(async () => await OpenCustomerPickerAsync());
OpenMixerMaterialPickerCommand = new DelegateCommand(async () => await OpenMixerMaterialPickerAsync(), () => !IsMixerMaterialManual)
.ObservesProperty(() => IsMixerMaterialManual);
ClearSupplierCommand = new DelegateCommand(ClearSupplierSelection);
ClearCustomerCommand = new DelegateCommand(ClearCustomerSelection);
ClearMixerMaterialCommand = new DelegateCommand(ClearMixerMaterialSelection);
_ = LoadDictOptionsAsync();
_ = LoadRecentWeightRecordsAsync(PlateNumber);
TryApplyCachedUnitByDirection(InoutDirection);
// 启动串口模拟定时器200ms 刷新一次,约 5Hz
_serialTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(200) };
@@ -447,12 +513,15 @@ public class WeightRecordOperationViewModel : BaseViewModel
PlateNumber = PlateNumber,
SenderUnit = SenderUnit,
ReceiverUnit = ReceiverUnit,
GoodsName = GoodsName,
GrossWeight = GrossWeight,
TareWeight = TareWeight,
NetWeight = NetWeight,
DriverName = DriverName,
DriverPhone = DriverPhone
DriverPhone = DriverPhone,
MixerMaterialIds = _mixerMaterialIds,
MixerMaterialNames = _mixerMaterialNames,
// 勾选“手动”=2未勾选=1自动
MaterialType = IsMixerMaterialManual ? "2" : "1"
};
try
@@ -462,6 +531,7 @@ public class WeightRecordOperationViewModel : BaseViewModel
: await _weightRecordService.AddAsync(entity);
if (ok)
{
CacheUnitByDirection();
if (isCompleteSelectedRecord)
{
Growl.Success("皮重回填成功,单据已更新为称重完成!");
@@ -482,11 +552,14 @@ public class WeightRecordOperationViewModel : BaseViewModel
SelectedRecentWeightRecord = null;
IsPlateNumberLocked = false;
PlateNumber = null; DetectedPlate = string.Empty; HasPlate = false;
SenderUnit = null; ReceiverUnit = null; GoodsName = null;
SenderUnit = null; ReceiverUnit = null;
DriverName = null; DriverPhone = null;
ClearSupplierSelection();
ClearCustomerSelection();
ClearMixerMaterialSelection();
ClearVehicleMatch();
// 保存后立即按当前方向回填缓存,避免必须重进页面才显示
TryApplyCachedUnitByDirection(InoutDirection);
RaisePropertyChanged(nameof(NetWeightDisplay));
}
else
@@ -511,7 +584,7 @@ public class WeightRecordOperationViewModel : BaseViewModel
WeighDate = DateTime.Today;
InoutDirection = "1";
PlateNumber = null; SenderUnit = null; ReceiverUnit = null;
GoodsName = null; GrossWeight = null; TareWeight = null; NetWeight = null;
GrossWeight = null; TareWeight = null; NetWeight = null;
DriverName = null; DriverPhone = null;
GrossWeightCaptured = false; TareWeightCaptured = false;
DetectedPlate = string.Empty; HasPlate = false;
@@ -519,11 +592,123 @@ public class WeightRecordOperationViewModel : BaseViewModel
IsPlateNumberLocked = false;
ClearSupplierSelection();
ClearCustomerSelection();
ClearMixerMaterialSelection();
ClearVehicleMatch();
RaisePropertyChanged(nameof(NetWeightDisplay));
AddLog("表单已清空");
}
private void CacheUnitByDirection()
{
var changed = false;
// 按需求:进厂缓存收货单位;出厂缓存发货单位
if (string.Equals(InoutDirection, "1", StringComparison.Ordinal))
{
if (!string.IsNullOrWhiteSpace(ReceiverUnit) && !string.Equals(_cachedInboundReceiverUnit, ReceiverUnit, StringComparison.Ordinal))
{
_cachedInboundReceiverUnit = ReceiverUnit;
changed = true;
}
}
else if (string.Equals(InoutDirection, "2", StringComparison.Ordinal)
&& !string.IsNullOrWhiteSpace(SenderUnit)
&& !string.Equals(_cachedOutboundSenderUnit, SenderUnit, StringComparison.Ordinal))
{
_cachedOutboundSenderUnit = SenderUnit;
changed = true;
}
if (changed)
{
SaveUnitCache();
}
}
private void TryApplyCachedUnitByDirection(string? inoutDirection)
{
// 切换方向时动态切换缓存展示:
// 进厂只展示“收货单位缓存”,出厂只展示“发货单位缓存”
if (string.Equals(inoutDirection, "1", StringComparison.Ordinal))
{
// 进厂:清空发货单位展示
_selectedSupplier = null;
SenderUnit = null;
RaisePropertyChanged(nameof(SenderUnitDisplay));
RaisePropertyChanged(nameof(HasSelectedSupplier));
// 进厂:带出收货单位缓存(无缓存则清空)
if (!string.IsNullOrWhiteSpace(_cachedInboundReceiverUnit))
{
ReceiverUnit = _cachedInboundReceiverUnit;
AddLog($"已带出进厂收货单位缓存:{_cachedInboundReceiverUnit}");
}
else
{
_selectedCustomer = null;
ReceiverUnit = null;
RaisePropertyChanged(nameof(ReceiverUnitDisplay));
RaisePropertyChanged(nameof(HasSelectedCustomer));
}
return;
}
if (string.Equals(inoutDirection, "2", StringComparison.Ordinal))
{
// 出厂:清空收货单位展示
_selectedCustomer = null;
ReceiverUnit = null;
RaisePropertyChanged(nameof(ReceiverUnitDisplay));
RaisePropertyChanged(nameof(HasSelectedCustomer));
// 出厂:带出发货单位缓存(无缓存则清空)
if (!string.IsNullOrWhiteSpace(_cachedOutboundSenderUnit))
{
SenderUnit = _cachedOutboundSenderUnit;
AddLog($"已带出出厂发货单位缓存:{_cachedOutboundSenderUnit}");
}
else
{
_selectedSupplier = null;
SenderUnit = null;
RaisePropertyChanged(nameof(SenderUnitDisplay));
RaisePropertyChanged(nameof(HasSelectedSupplier));
}
}
}
private void LoadUnitCache()
{
try
{
if (!File.Exists(_unitCacheFilePath)) return;
var json = File.ReadAllText(_unitCacheFilePath);
var cache = JsonSerializer.Deserialize<UnitDirectionCache>(json);
if (cache == null) return;
_cachedInboundReceiverUnit = string.IsNullOrWhiteSpace(cache.InboundReceiverUnit) ? null : cache.InboundReceiverUnit;
_cachedOutboundSenderUnit = string.IsNullOrWhiteSpace(cache.OutboundSenderUnit) ? null : cache.OutboundSenderUnit;
}
catch
{
// 缓存读取失败不影响主流程
}
}
private void SaveUnitCache()
{
try
{
var cache = new UnitDirectionCache
{
InboundReceiverUnit = _cachedInboundReceiverUnit,
OutboundSenderUnit = _cachedOutboundSenderUnit,
};
var json = JsonSerializer.Serialize(cache);
File.WriteAllText(_unitCacheFilePath, json);
}
catch
{
// 缓存写入失败不影响主流程
}
}
private async Task OpenSupplierPickerAsync()
{
SupplierPickerDialogViewModel? pickerVm = null;
@@ -582,6 +767,42 @@ public class WeightRecordOperationViewModel : BaseViewModel
RaisePropertyChanged(nameof(HasSelectedCustomer));
}
private async Task OpenMixerMaterialPickerAsync()
{
MixerMaterialPickerDialogViewModel? pickerVm = null;
bool confirmed;
try
{
confirmed = await HandyControl.Controls.Dialog.Show<MixerMaterialPickerDialogView>()
.Initialize<MixerMaterialPickerDialogViewModel>(vm => { pickerVm = vm; })
.GetResultAsync<bool>();
}
catch { return; }
if (!confirmed || pickerVm == null) return;
var selected = pickerVm.SelectedMaterials;
if (selected.Count == 0) return;
_mixerMaterialIds = string.Join("", selected.Select(m => m.Source.Id ?? ""));
MixerMaterialNames = string.Join("", selected.Select(m => m.Source.MaterialName ?? ""));
if (IsMixerMaterialManual)
{
IsMixerMaterialManual = false;
}
RaisePropertyChanged(nameof(MixerMaterialDisplay));
RaisePropertyChanged(nameof(HasSelectedMixerMaterials));
AddLog($"已选密炼物料:{_mixerMaterialNames}");
}
private void ClearMixerMaterialSelection()
{
_mixerMaterialIds = null;
MixerMaterialNames = null;
IsMixerMaterialManual = false;
RaisePropertyChanged(nameof(MixerMaterialDisplay));
RaisePropertyChanged(nameof(HasSelectedMixerMaterials));
}
private void AddLog(string message)
{
var entry = $"{DateTime.Now:HH:mm:ss} {message}";
@@ -681,9 +902,13 @@ public class WeightRecordOperationViewModel : BaseViewModel
RaisePropertyChanged(nameof(ReceiverUnitDisplay));
RaisePropertyChanged(nameof(HasSelectedSupplier));
RaisePropertyChanged(nameof(HasSelectedCustomer));
GoodsName = selected.Source.GoodsName;
DriverName = selected.Source.DriverName;
DriverPhone = selected.Source.DriverPhone;
_mixerMaterialIds = selected.Source.MixerMaterialIds;
MixerMaterialNames = selected.Source.MixerMaterialNames;
IsMixerMaterialManual = string.Equals(selected.Source.MaterialType, "2", StringComparison.Ordinal);
RaisePropertyChanged(nameof(MixerMaterialDisplay));
RaisePropertyChanged(nameof(HasSelectedMixerMaterials));
var isOutbound = string.Equals(InoutDirection, "2", StringComparison.Ordinal);
if (isOutbound)
@@ -715,7 +940,6 @@ public class WeightRecordOperationViewModel : BaseViewModel
PlateNumber = null;
SenderUnit = null;
ReceiverUnit = null;
GoodsName = null;
DriverName = null;
DriverPhone = null;
GrossWeight = null;
@@ -727,6 +951,7 @@ public class WeightRecordOperationViewModel : BaseViewModel
IsPlateNumberLocked = false;
ClearSupplierSelection();
ClearCustomerSelection();
ClearMixerMaterialSelection();
ClearVehicleMatch();
RaisePropertyChanged(nameof(NetWeightDisplay));
AddLog("已切换进出方向,当前表单数据已清空");
@@ -889,6 +1114,12 @@ public class WeightRecordOperationViewModel : BaseViewModel
}
}
public class UnitDirectionCache
{
public string? InboundReceiverUnit { get; set; }
public string? OutboundSenderUnit { get; set; }
}
public class WeightRecordSimpleItem
{
public MesXslWeightRecord? Source { get; set; }