新增MES库区管理功能,包含免密接口、数据处理逻辑及相关控制器、服务和实体的实现。支持库区的增删改查操作,优化用户体验并增强系统的实时数据同步能力。

This commit is contained in:
geht
2026-05-12 14:06:07 +08:00
parent cffe32d896
commit b737dddb2a
74 changed files with 4937 additions and 174 deletions

View File

@@ -158,6 +158,10 @@ public class StompWebSocketService : ISignalRService
await SendFrameAsync(
BuildSubscribeFrame("sub-mes-raw-material-cards", "/topic/sync/mes-raw-material-cards"),
cancellationToken).ConfigureAwait(false);
// 库区数据变更:订阅 /topic/sync/mes-warehouse-areas
await SendFrameAsync(
BuildSubscribeFrame("sub-mes-warehouse-areas", "/topic/sync/mes-warehouse-areas"),
cancellationToken).ConfigureAwait(false);
// 订阅服务端 PONG 回复(应用层假在线检测)
await SendFrameAsync(

View File

@@ -14,6 +14,7 @@ using YY.Admin.ViewModels.Vehicle;
using YY.Admin.Views.Vehicle;
using YY.Admin.Views.WeightRecord;
using YY.Admin.Views.RawMaterialCard;
using YY.Admin.Views.WarehouseArea;
using YY.Admin.Views.RawMaterialEntry;
namespace YY.Admin
@@ -78,6 +79,8 @@ namespace YY.Admin
containerRegistry.RegisterForNavigation<RawMaterialEntryOperationView>();
// 原材料卡片
containerRegistry.RegisterForNavigation<RawMaterialCardListView>();
// 库区管理
containerRegistry.RegisterForNavigation<WarehouseAreaListView>();
}
}
public class DialogWindow : Window, IDialogWindow

View File

@@ -22,6 +22,8 @@ using YY.Admin.Services.Service.Supplier;
using YY.Admin.Services.Service.RawMaterialCard;
using YY.Admin.Services.Service.RawMaterialEntry;
using YY.Admin.Services.Service.Vehicle;
using YY.Admin.Services.Service.Warehouse;
using YY.Admin.Services.Service.WarehouseArea;
using YY.Admin.Services.Service.WeightRecord;
namespace YY.Admin.Module;
@@ -61,6 +63,11 @@ public class SyncModule : IModule
// 原材料卡片:免密 API 直连 + STOMP 实时通知
containerRegistry.RegisterSingleton<IRawMaterialCardService, RawMaterialCardService>();
containerRegistry.RegisterSingleton<RawMaterialCardSyncCoordinator>();
// 仓库数据只读缓存(供库区等模块筛选使用,不含 CRUD 页面)
containerRegistry.RegisterSingleton<IWarehouseService, WarehouseService>();
// 库区管理:免密 API 直连 + STOMP 实时通知
containerRegistry.RegisterSingleton<IWarehouseAreaService, WarehouseAreaService>();
containerRegistry.RegisterSingleton<WarehouseAreaSyncCoordinator>();
// 分类字典:启动同步 + 断线重连补刷
containerRegistry.RegisterSingleton<CategorySyncCoordinator>();
// 数据字典:启动同步 + 断线重连补刷
@@ -127,6 +134,8 @@ public class SyncModule : IModule
_ = containerProvider.Resolve<RawMaterialEntrySyncCoordinator>();
// 强制实例化原材料卡片同步协调器
_ = containerProvider.Resolve<RawMaterialCardSyncCoordinator>();
// 强制实例化库区同步协调器
_ = containerProvider.Resolve<WarehouseAreaSyncCoordinator>();
// 强制实例化分类字典同步协调器
_ = containerProvider.Resolve<CategorySyncCoordinator>();
// 强制实例化数据字典同步协调器

View File

@@ -135,7 +135,12 @@ namespace YY.Admin.ViewModels.Control
// 已实现页面:原材料卡片
["RawMaterialCardListView"] = "RawMaterialCardListView",
["/xslmes/mesXslRawMaterialCard"] = "RawMaterialCardListView",
["mesXslRawMaterialCard"] = "RawMaterialCardListView"
["mesXslRawMaterialCard"] = "RawMaterialCardListView",
// 已实现页面:库区管理
["WarehouseAreaListView"] = "WarehouseAreaListView",
["/xslmes/mesXslWarehouseArea"] = "WarehouseAreaListView",
["mesXslWarehouseArea"] = "WarehouseAreaListView"
};
private MenuItem? _selectedMenuItem;

View File

@@ -63,6 +63,9 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
public bool IsAddMode => string.IsNullOrWhiteSpace(Entry?.Id);
public string DialogTitle => IsAddMode ? "新增原料入场记录" : "编辑原料入场记录";
/// <summary>已打印PrintFlag=="1"):总重只读、不可新增明细。</summary>
public bool IsPrinted => string.Equals(Entry?.PrintFlag, "1", StringComparison.Ordinal);
public bool IsTotalWeightEditable => !IsPrinted;
public string SelectedMaterialDisplay => _selectedMaterial == null
? "请选择密炼物料"
: $"[{_selectedMaterial.MaterialCode}] {_selectedMaterial.MaterialName}";
@@ -130,6 +133,10 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
public DelegateCommand ClearWeightRecordCommand { get; }
public DelegateCommand OpenSupplierPickerCommand { get; }
public DelegateCommand ClearSupplierCommand { get; }
/// <summary>
/// 拆码明细 - 库位选择命令弹出「库区选择」弹窗单选。CommandParameter 为当前行 RawMaterialSplitDetailItem。
/// </summary>
public DelegateCommand<RawMaterialSplitDetailItem> OpenWarehouseAreaPickerCommand { get; }
public RawMaterialEntryEditDialogViewModel(
IRawMaterialEntryService entryService,
@@ -144,7 +151,7 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
SaveCommand = new DelegateCommand(async () => await SaveAsync());
CancelCommand = new DelegateCommand(() => CloseAction?.Invoke());
ResetCommand = new DelegateCommand(InitializeForAdd);
AddSplitDetailCommand = new DelegateCommand(AddSplitDetailRow);
AddSplitDetailCommand = new DelegateCommand(AddSplitDetailRow, () => !IsPrinted);
RemoveSplitDetailCommand = new DelegateCommand<RawMaterialSplitDetailItem>(RemoveSplitDetailRow);
OpenMaterialPickerCommand = new DelegateCommand(async () => await OpenMaterialPickerAsync());
ClearMaterialCommand = new DelegateCommand(ClearMaterialSelection);
@@ -152,6 +159,7 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
ClearWeightRecordCommand = new DelegateCommand(ClearWeightRecordSelection);
OpenSupplierPickerCommand = new DelegateCommand(async () => await OpenSupplierPickerAsync());
ClearSupplierCommand = new DelegateCommand(ClearSupplierSelection);
OpenWarehouseAreaPickerCommand = new DelegateCommand<RawMaterialSplitDetailItem>(async row => await OpenWarehouseAreaPickerAsync(row));
SplitCodeDetails.CollectionChanged += OnSplitCodeDetailsCollectionChanged;
_ = LoadAllAsync();
}
@@ -304,6 +312,9 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
RaisePropertyChanged(nameof(SplitTotalPortionsDisplay));
RaisePropertyChanged(nameof(SplitPortionWeightDisplay));
RaisePropertyChanged(nameof(SplitPortionPackagesDisplay));
RaisePropertyChanged(nameof(IsPrinted));
RaisePropertyChanged(nameof(IsTotalWeightEditable));
AddSplitDetailCommand.RaiseCanExecuteChanged();
}
public void InitializeForEdit(MesXslRawMaterialEntry entry)
@@ -317,6 +328,9 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
ManufacturerMaterialName = entry.ManufacturerMaterialName,
ShelfLife = entry.ShelfLife, TotalWeight = entry.TotalWeight, TotalPortions = entry.TotalPortions,
PortionWeight = entry.PortionWeight, PortionPackages = entry.PortionPackages,
PortionWarehouseLocations = entry.PortionWarehouseLocations,
PortionDetailIds = entry.PortionDetailIds,
PortionCardFlags = entry.PortionCardFlags,
TestResult = entry.TestResult, TestStatus = entry.TestStatus, PrintFlag = entry.PrintFlag,
StockBalance = entry.StockBalance, WarehouseLocation = entry.WarehouseLocation,
UnloadOperator = entry.UnloadOperator, IsSpecialAdoption = entry.IsSpecialAdoption,
@@ -347,6 +361,9 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
RaisePropertyChanged(nameof(SplitTotalPortionsDisplay));
RaisePropertyChanged(nameof(SplitPortionWeightDisplay));
RaisePropertyChanged(nameof(SplitPortionPackagesDisplay));
RaisePropertyChanged(nameof(IsPrinted));
RaisePropertyChanged(nameof(IsTotalWeightEditable));
AddSplitDetailCommand.RaiseCanExecuteChanged();
}
protected virtual async Task SaveAsync()
@@ -357,6 +374,18 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
ApplySplitDetailsToEntry();
ApplyDefaultEntryStatusForAdd();
ApplyHiddenFieldDefaultsForAdd();
var missing = new List<string>();
if (string.IsNullOrWhiteSpace(Entry.MaterialId)) missing.Add("密炼物料");
if (string.IsNullOrWhiteSpace(Entry.BillNo)) missing.Add("榜单号");
if (string.IsNullOrWhiteSpace(Entry.UnloadOperator)) missing.Add("卸货人");
if (string.IsNullOrWhiteSpace(Entry.SupplierName)) missing.Add("供应商名称");
if (missing.Count > 0)
{
HandyControl.Controls.MessageBox.Warning($"以下必填项不能为空:{string.Join("", missing)}");
return;
}
bool ok;
if (IsAddMode)
{
@@ -414,7 +443,57 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
Entry.BillNo = selected.BillNo;
Entry.SupplierName = selected.SenderUnit;
Entry.SupplierId = null;
// 选择榜单后,自动把「剩余可入场量 = 净重 - 已入场重量」带入「总重」,
// 用户仍可手动编辑;若净重为空则保留原值,避免误清空。
if (selected.NetWeight.HasValue)
{
var entered = selected.EnteredWeight ?? 0d;
var remaining = selected.NetWeight.Value - entered;
// 已入场重量可能因数据异常超过净重UI 不展示负数,强制夹到 0。
Entry.TotalWeight = Math.Round(Math.Max(0d, remaining), 2);
}
RaisePropertyChanged(nameof(Entry));
RaisePropertyChanged(nameof(TotalWeightInput));
}
/// <summary>
/// 弹出「库区选择」弹窗,把选中的 AreaName 写回当前明细行的 WarehouseLocation。
/// 入参 row被点击的拆码明细行为 null 直接 return防御性
/// </summary>
private async Task OpenWarehouseAreaPickerAsync(RawMaterialSplitDetailItem? row)
{
if (row == null)
{
return;
}
WarehouseAreaPickerDialogViewModel? pickerVm = null;
bool confirmed;
try
{
confirmed = await HandyControl.Controls.Dialog.Show<WarehouseAreaPickerDialogView>()
.Initialize<WarehouseAreaPickerDialogViewModel>(vm =>
{
pickerVm = vm;
vm.Initialize(row.WarehouseLocation);
})
.GetResultAsync<bool>();
}
catch (Exception ex)
{
// 保留 Debug 输出以便排查,但不阻断流程
System.Diagnostics.Debug.WriteLine($"[库位选择] 弹窗异常: {ex}");
return;
}
if (!confirmed || pickerVm?.SelectedRecord == null)
{
return;
}
// 仅回填库区名称(与后端 warehouse_location 字段保持单字符串语义)。
// 如未来需要严格关联库区 ID可在 RawMaterialSplitDetailItem 新增 WarehouseAreaId 一并保存。
row.WarehouseLocation = pickerVm.SelectedRecord.AreaName;
}
private async Task OpenMaterialPickerAsync()
@@ -537,7 +616,7 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
/// <summary>
/// 新增模式下为已在前端隐藏的字段补充默认值,确保保存到后端时不为空:
/// 检测结果=未检、检测状态=送样、打印标记=未打印、入库结存=否。
/// 检测结果=未检、打印标记=未打印、入库结存=否。
/// 字典就绪时取字典 code未就绪时回退到约定的常用 code。
/// </summary>
protected void ApplyHiddenFieldDefaultsForAdd()
@@ -551,10 +630,7 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
{
Entry.TestResult = ResolveDefaultOptionValue(TestResultOptions, "未检", "0");
}
if (string.IsNullOrWhiteSpace(Entry.TestStatus))
{
Entry.TestStatus = ResolveDefaultOptionValue(TestStatusOptions, "送样", "0");
}
if (string.IsNullOrWhiteSpace(Entry.PrintFlag))
{
Entry.PrintFlag = ResolveDefaultOptionValue(PrintFlagOptions, "未打印", "0");
@@ -582,22 +658,58 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
{
SplitCodeDetails.Clear();
// 个字段是按拆码明细多行拼接的字符串(末尾带 /),解析回明细列表
// 个字段是按拆码明细多行拼接的字符串(末尾带 /),解析回明细列表
var portionsArr = SplitJoinedValues(Entry?.TotalPortions);
var weightArr = SplitJoinedValues(Entry?.PortionWeight);
var packagesArr = SplitJoinedValues(Entry?.PortionPackages);
var rowCount = Math.Max(1, Math.Max(portionsArr.Length, Math.Max(weightArr.Length, packagesArr.Length)));
var locationsArr = SplitJoinedValues(Entry?.PortionWarehouseLocations);
var idsArr = SplitJoinedValues(Entry?.PortionDetailIds);
var cardFlagsArr = SplitJoinedValues(Entry?.PortionCardFlags);
// 历史记录可能没存 PortionCardFlags用 print_flag 推断(与原行为兼容)
var fallbackToPrintFlag = cardFlagsArr.Length == 0
&& string.Equals(Entry?.PrintFlag, "1", StringComparison.Ordinal);
var rowCount = Math.Max(1, Math.Max(Math.Max(portionsArr.Length, weightArr.Length),
Math.Max(Math.Max(packagesArr.Length, locationsArr.Length), idsArr.Length)));
for (var i = 0; i < rowCount; i++)
{
SplitCodeDetails.Add(new RawMaterialSplitDetailItem
var locationFromArr = GetAt(locationsArr, i);
// 兼容历史数据:明细库位拼接字段为空时,首行回退到 Entry.WarehouseLocation
// (早期版本里整票级库位曾被反写过,避免老记录打开后明细全空)
var locationFallback = (i == 0 && string.IsNullOrWhiteSpace(locationFromArr))
? Entry?.WarehouseLocation
: locationFromArr;
var item = new RawMaterialSplitDetailItem
{
Portions = TryParseInt(GetAt(portionsArr, i)),
PortionWeight = TryParseDouble(GetAt(weightArr, i)),
PortionPackages = TryParseInt(GetAt(packagesArr, i)),
// 库位是单值字段,仅赋给首行
WarehouseLocation = i == 0 ? Entry?.WarehouseLocation : null,
});
WarehouseLocation = locationFallback,
};
// 仅当后端已持久化了该行 ID 时才覆盖默认值,确保跨次保持稳定;
// 历史记录无 ID 字段时使用构造期生成的新 GUID之后保存会回填
var existingId = GetAt(idsArr, i);
if (!string.IsNullOrWhiteSpace(existingId))
{
item.Id = existingId;
}
// HasCard 行级解析(优先级 1从 PortionCardFlags 按位读取,"1"=已生成。
// 这是「保存后新增未生成行不被误判为已打印」的关键 — 新增行保存时 HasCard=false 持久化为 "0"
// 重新加载时回填 false「生成原材料卡片」就能正确把它列入待生成清单。
var flagAt = GetAt(cardFlagsArr, i);
if (!string.IsNullOrWhiteSpace(flagAt))
{
item.HasCard = string.Equals(flagAt, "1", StringComparison.Ordinal);
}
else if (fallbackToPrintFlag && !string.IsNullOrWhiteSpace(existingId))
{
// 优先级 2兼容历史记录未存 PortionCardFlags 时,沿用旧逻辑——
// 持久化 ID 存在 + 整票已打印 ⇒ 视为已生成卡片。
item.HasCard = true;
}
// 否则保持构造默认值 false新增态、未打印整票等场景
SplitCodeDetails.Add(item);
}
RaisePropertyChanged(nameof(SplitCodeTableHeight));
@@ -666,6 +778,14 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
private void OnSplitDetailItemPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
// 用户填写或修改某行的「份数」时,按公式自动算出该行「每份重量」:
// 每份重量 = (总重 - Σ其他行的份数×每份重量) / 当前行份数
if (e.PropertyName == nameof(RawMaterialSplitDetailItem.Portions)
&& sender is RawMaterialSplitDetailItem row)
{
RecalculatePortionWeightForRow(row);
}
if (e.PropertyName is nameof(RawMaterialSplitDetailItem.Portions)
or nameof(RawMaterialSplitDetailItem.PortionWeight)
or nameof(RawMaterialSplitDetailItem.PortionPackages))
@@ -674,6 +794,42 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
}
}
/// <summary>
/// 拆码明细某行的「份数」变化时,按公式重算该行「每份重量」:
/// 公式:每份重量 = (总重 - 其他行 Σ份数×每份重量) / 当前行份数
/// — 用户单独手改「每份重量」不触发;
/// — 总重为空 / ≤0、当前份数 ≤0、剩余总重 ≤0 时跳过;
/// — 结果四舍五入到两位小数;
/// — 写入 row.PortionWeight 会触发 PropertyChanged(PortionWeight)不会再次进入本方法PropertyName 已不是 Portions不会形成循环。
/// </summary>
private void RecalculatePortionWeightForRow(RawMaterialSplitDetailItem row)
{
if (Entry?.TotalWeight is not { } total || total <= 0d)
{
return;
}
if (row.Portions is not { } portions || portions <= 0)
{
return;
}
var otherSum = 0d;
foreach (var other in SplitCodeDetails)
{
if (ReferenceEquals(other, row)) continue;
if (other.Portions is not { } op || other.PortionWeight is not { } ow) continue;
otherSum += op * ow;
}
var remaining = total - otherSum;
if (remaining <= 0d)
{
return;
}
row.PortionWeight = Math.Round(remaining / portions, 2);
}
private void RaiseSplitDisplayPropertyChanged()
{
RaisePropertyChanged(nameof(SplitTotalPortionsDisplay));
@@ -708,21 +864,26 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
}
/// <summary>
/// 把 SplitCodeDetails 全部明细行的「份数 / 每份重量 / 每份包数」按 "x/y/z/" 拼接后持久化到 Entry
/// 库位仍取首行(业务上库位是单值)。与 SplitTotalPortionsDisplay 等只读展示属性同规则。
/// 把 SplitCodeDetails 全部明细行的「份数 / 每份重量 / 每份包数 / 库位 / 行 ID」按 "x/y/z/" 拼接后持久化到 Entry
/// 明细行的「库位」属于行级数据(用于后续生成原材料卡片时区分库位),与 Entry.WarehouseLocation
/// (基础资料整票级单值)独立保存,通过 portion_warehouse_locations 字段持久化。
/// 子类(如 OperationViewModel 的「重新拆码」)需要直接调用,因此声明为 protected。
/// </summary>
private void ApplySplitDetailsToEntry()
protected void ApplySplitDetailsToEntry()
{
if (Entry == null) return;
Entry.TotalPortions = JoinSplitValue(it => it.Portions?.ToString(CultureInfo.InvariantCulture), true);
Entry.PortionWeight = JoinSplitValue(it => FormatNullableDecimal(it.PortionWeight), true);
Entry.PortionPackages = JoinSplitValue(it => it.PortionPackages?.ToString(CultureInfo.InvariantCulture), true);
if (SplitCodeDetails.Count > 0)
{
Entry.WarehouseLocation = SplitCodeDetails[0].WarehouseLocation;
}
Entry.PortionWarehouseLocations = JoinSplitValue(it => string.IsNullOrWhiteSpace(it.WarehouseLocation) ? null : it.WarehouseLocation.Trim(), true);
// 持久化每行 GUID行序与上方四个字段对齐JoinSplitValue 会过滤空,但 Id 在构造时即生成不会为空。
Entry.PortionDetailIds = JoinSplitValue(it => it.Id, true);
// 持久化每行的「已生成卡片」标志1=已生成 0=未生成),
// 用于解决「保存后新增行被误判为已打印」的问题:
// 「生成原材料卡片」只对 HasCard=false 的行加卡 → 必须把真实的 HasCard 持久化,
// 否则重新加载后无法区分「已生成」与「保存后新增的未生成行」。
Entry.PortionCardFlags = JoinSplitValue(it => it.HasCard ? "1" : "0", true);
}
private double CalculateSplitCodeTableHeight()
@@ -740,6 +901,14 @@ public class RawMaterialEntryEditDialogViewModel : BaseViewModel, IDialogResulta
public class RawMaterialSplitDetailItem : BindableBase
{
/// <summary>
/// 拆码明细行 IDGUID构造时生成。前端隐藏
/// 「生成原材料卡片」时由桌面端写入对应 MesXslRawMaterialCard.SplitDetailId
/// 「重新拆码」时按入场记录的 PortionDetailIds 反查批删关联卡片。
/// 编辑回填时若 portion_detail_ids 已有该位置的值,会覆盖默认值,确保跨次保持稳定。
/// </summary>
public string Id { get; set; } = Guid.NewGuid().ToString("N");
private int? _portions;
public int? Portions
{
@@ -767,4 +936,18 @@ public class RawMaterialSplitDetailItem : BindableBase
get => _warehouseLocation;
set => SetProperty(ref _warehouseLocation, value);
}
/// <summary>
/// 该行是否已生成原材料卡片(运行时状态,不直接持久化)。
/// 加载时根据 Entry.PrintFlag + Id 是否来自持久化的 PortionDetailIds 推断;
/// 「生成原材料卡片」成功后由 VM 置为 true「重新拆码」清空集合时随之失效。
/// true → 行内输入项 / 删除 全部锁定,避免与已生成卡片数据脱节;
/// false → 该行参与下次「生成原材料卡片」(继续拆码 + 续号生成)。
/// </summary>
private bool _hasCard;
public bool HasCard
{
get => _hasCard;
set => SetProperty(ref _hasCard, value);
}
}

View File

@@ -31,6 +31,20 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
public ObservableCollection<MesXslRawMaterialEntry> TodayEntries { get; } = new();
public IReadOnlyList<string> DateRangeOptions { get; } =
["今日", "过去24小时", "过去48小时", "过去72小时"];
private string _selectedDateRange = "今日";
public string SelectedDateRange
{
get => _selectedDateRange;
set
{
if (SetProperty(ref _selectedDateRange, value))
_ = LoadTodayEntriesAsync();
}
}
private MesXslRawMaterialEntry? _selectedTodayEntry;
public MesXslRawMaterialEntry? SelectedTodayEntry
{
@@ -67,12 +81,20 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
}
}
/// <summary>仅当入场记录已保存(有 Id且存在拆码明细时允许生成原材料卡片。</summary>
public bool CanGenerateCards => !string.IsNullOrWhiteSpace(Entry?.Id) && SplitCodeDetails.Count > 0;
/// <summary>
/// 「生成原材料卡片」按钮可用条件:
/// 入场记录已保存(有 Id且至少存在一行「未生成卡片 + 份数>0」的明细。
/// 已打印态下用户「继续拆码」新增了行,按钮自动重新可用;全部行都已生成卡片时不可用。
/// </summary>
public bool CanGenerateCards =>
!string.IsNullOrWhiteSpace(Entry?.Id)
&& SplitCodeDetails.Any(d => !d.HasCard && (d.Portions ?? 0) > 0);
public DelegateCommand ToggleRightPanelCommand { get; }
public DelegateCommand RefreshTodayEntriesCommand { get; }
public DelegateCommand GenerateRawMaterialCardsCommand { get; }
/// <summary>「重新拆码」:清除已生成的卡片 + 清空明细,仅在编辑态可用。</summary>
public DelegateCommand ResplitCommand { get; }
public RawMaterialEntryOperationViewModel(
IRawMaterialEntryService entryService,
@@ -88,7 +110,52 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
ToggleRightPanelCommand = new DelegateCommand(() => IsRightPanelExpanded = !IsRightPanelExpanded);
RefreshTodayEntriesCommand = new DelegateCommand(async () => await LoadTodayEntriesAsync());
GenerateRawMaterialCardsCommand = new DelegateCommand(async () => await GenerateRawMaterialCardsAsync());
SplitCodeDetails.CollectionChanged += (_, _) => RaisePropertyChanged(nameof(CanGenerateCards));
ResplitCommand = new DelegateCommand(async () => await ResplitAsync(), () => CanResplit)
.ObservesProperty(() => Entry);
// 集合变化:批量重订阅 item.PropertyChanged 监听 HasCard/Portions并同步刷新两个 Can*。
SplitCodeDetails.CollectionChanged += OnSplitCodeDetailsCollectionChangedForCanFlags;
}
/// <summary>
/// 「重新拆码」按钮可用条件:入场记录已保存 + 至少有一行已生成卡片。
/// 全是新增未生成的行时,用户直接点「删除」即可,无需走「清空卡片」流程。
/// </summary>
public bool CanResplit =>
!string.IsNullOrWhiteSpace(Entry?.Id)
&& SplitCodeDetails.Any(d => d.HasCard);
/// <summary>
/// 集合变化时:对新增/移除的 item 维护 PropertyChanged 订阅,并刷新两个 Can* 属性。
/// HasCard / Portions 变化会让「生成原材料卡片」和「重新拆码」按钮可用性同步刷新。
/// </summary>
private void OnSplitCodeDetailsCollectionChangedForCanFlags(
object? sender,
System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
{
foreach (RawMaterialSplitDetailItem o in e.OldItems)
o.PropertyChanged -= OnSplitDetailHasCardChanged;
}
if (e.NewItems != null)
{
foreach (RawMaterialSplitDetailItem n in e.NewItems)
n.PropertyChanged += OnSplitDetailHasCardChanged;
}
RaisePropertyChanged(nameof(CanGenerateCards));
RaisePropertyChanged(nameof(CanResplit));
ResplitCommand.RaiseCanExecuteChanged();
}
private void OnSplitDetailHasCardChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName is nameof(RawMaterialSplitDetailItem.HasCard)
or nameof(RawMaterialSplitDetailItem.Portions))
{
RaisePropertyChanged(nameof(CanGenerateCards));
RaisePropertyChanged(nameof(CanResplit));
ResplitCommand.RaiseCanExecuteChanged();
}
}
public override void InitializeForAdd()
@@ -117,9 +184,15 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
{
IsLoading = true;
var result = await EntryService.PageAsync(1, TodayListFetchSize);
var today = DateTime.Today;
DateTime? threshold = _selectedDateRange switch
{
"过去24小时" => DateTime.Now.AddHours(-24),
"过去48小时" => DateTime.Now.AddHours(-48),
"过去72小时" => DateTime.Now.AddHours(-72),
_ => null // "今日":按自然日过滤
};
var rows = result.Records
.Where(e => IsTodayEntry(e, today))
.Where(e => IsInRange(e, threshold))
.OrderByDescending(e => e.EntryTime ?? e.CreateTime ?? DateTime.MinValue)
.ToList();
TodayEntries.Clear();
@@ -136,11 +209,14 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
}
}
private static bool IsTodayEntry(MesXslRawMaterialEntry e, DateTime today)
private static bool IsInRange(MesXslRawMaterialEntry e, DateTime? threshold)
{
var byEntry = e.EntryTime?.Date == today;
var byCreate = e.CreateTime?.Date == today;
return byEntry || byCreate;
if (threshold == null)
{
var today = DateTime.Today;
return (e.EntryTime?.Date == today) || (e.CreateTime?.Date == today);
}
return (e.EntryTime >= threshold) || (e.CreateTime >= threshold);
}
/// <summary>
@@ -158,20 +234,118 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
await LoadMaterialOptionsAsync();
}
/// <summary>编辑保存成功后:刷新右侧今日列表并切回新增态,避免连续误改同一条。</summary>
/// <summary>
/// 保存成功后:无论新增还是编辑,都立即刷新右侧「今日入场」列表。
/// 编辑成功额外把表单切回「新增态」,避免用户连续误改同一条;新增成功后由基类负责清空表单。
/// </summary>
protected override async Task SaveAsync()
{
var wasEdit = !IsAddMode;
await base.SaveAsync();
RaisePropertyChanged(nameof(CanGenerateCards));
if (wasEdit && Result && CloseAction == null)
if (!Result || CloseAction != null)
{
return;
}
await LoadTodayEntriesAsync();
if (wasEdit)
{
HandyControl.Controls.MessageBox.Success("编辑成功!");
await LoadTodayEntriesAsync();
InitializeForAdd();
}
}
/// <summary>
/// 「重新拆码」:弹窗预提示「将清除 N 张原材料卡片」→ 确认 → 后端按 splitDetailId IN 批删
/// → 清空 SplitCodeDetails → Entry.PrintFlag=0 → EditAsync(Entry) 持久化新状态 → 刷新今日列表。
/// 流程对应「清空卡片重新生成」的业务诉求;用户可立即重新维护明细并再次「生成原材料卡片」。
/// </summary>
private async Task ResplitAsync()
{
if (Entry == null || string.IsNullOrWhiteSpace(Entry.Id))
{
HandyControl.Controls.MessageBox.Warning("请先选中一条已保存的入场记录后再「重新拆码」。");
return;
}
var detailIds = SplitCodeDetails
.Select(d => d.Id)
.Where(id => !string.IsNullOrWhiteSpace(id))
.Distinct()
.ToList();
try
{
IsLoading = true;
// dryRun 预查:统计当前明细行已生成的卡片数量
var cardCount = detailIds.Count > 0
? await _rawMaterialCardService.DeleteBySplitDetailIdsAsync(detailIds, dryRun: true)
: 0;
if (cardCount < 0)
{
HandyControl.Controls.MessageBox.Error("无法连接服务器,重新拆码已取消。");
return;
}
var confirm = HandyControl.Controls.MessageBox.Show(
$"当前入场记录已生成 {cardCount} 条原材料卡片,是否清空卡片重新生成?",
"重新拆码",
System.Windows.MessageBoxButton.OKCancel,
System.Windows.MessageBoxImage.Question);
if (confirm != System.Windows.MessageBoxResult.OK)
{
return;
}
// 真正执行批删(即使 cardCount=0 也调用一次,结果幂等)
if (cardCount > 0)
{
var deleted = await _rawMaterialCardService.DeleteBySplitDetailIdsAsync(detailIds, dryRun: false);
if (deleted < 0)
{
HandyControl.Controls.MessageBox.Error("清除已生成的原材料卡片失败,重新拆码已中止。");
return;
}
}
// 清空明细 + 重置打印标记,并把变更持久化到后端入场记录
SplitCodeDetails.Clear();
Entry.PrintFlag = "0";
ApplySplitDetailsToEntry(); // 同步把 PortionDetailIds 等清空到 Entry 上
var ok = await EntryService.EditAsync(Entry);
if (!ok)
{
HandyControl.Controls.MessageBox.Error("保存重新拆码后的入场记录失败,请刷新后重试。");
return;
}
RaisePropertyChanged(nameof(Entry));
RaisePropertyChanged(nameof(IsPrinted));
RaisePropertyChanged(nameof(IsTotalWeightEditable));
AddSplitDetailCommand.RaiseCanExecuteChanged();
RaisePropertyChanged(nameof(CanGenerateCards));
RaisePropertyChanged(nameof(CanResplit));
HandyControl.Controls.MessageBox.Success($"已清除 {cardCount} 条原材料卡片,请重新维护拆码明细。");
await LoadTodayEntriesAsync();
}
catch (Exception ex)
{
HandyControl.Controls.MessageBox.Error($"重新拆码失败:{ex.Message}");
}
finally
{
IsLoading = false;
}
}
/// <summary>
/// 「生成原材料卡片」:仅处理 HasCard==false 的明细行(「继续拆码」流程的核心)。
/// 条码续号 = 已有卡片总数(Σ HasCard==true 行的 Portions+ 1避免与已生成卡片的条码冲突。
/// 成功生成的行置 HasCard=trueEntry.PrintFlag=1整票级标记
/// </summary>
private async Task GenerateRawMaterialCardsAsync()
{
if (Entry == null || string.IsNullOrWhiteSpace(Entry.Id))
@@ -179,26 +353,73 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
HandyControl.Controls.MessageBox.Warning("请先保存入场记录再生成原材料卡片!");
return;
}
if (SplitCodeDetails.Count == 0)
// 只处理未生成卡片的行 + 份数>0已生成行直接跳过避免重复加卡和条码冲突
var pendingRows = SplitCodeDetails
.Where(d => !d.HasCard && (d.Portions ?? 0) > 0)
.ToList();
if (pendingRows.Count == 0)
{
HandyControl.Controls.MessageBox.Warning("当前入场记录无拆分明细,无法生成原材料卡片!");
HandyControl.Controls.MessageBox.Warning("当前没有待生成卡片的拆码明细(已生成的行不会重复处理)。请先「新增明细」再生成。");
return;
}
// 库位必填仅校验本批待生成行;行号取在原集合的真实索引,避免编号偏移误导
foreach (var row in pendingRows)
{
if (string.IsNullOrWhiteSpace(row.WarehouseLocation))
{
var realIndex = SplitCodeDetails.IndexOf(row);
HandyControl.Controls.MessageBox.Warning($"拆码明细第 {realIndex + 1} 行的「库位」未填写,请点击该行库位选择库区后再生成原材料卡片。");
return;
}
}
// 总重核对:拆码明细合计 vs 基础资料总重
{
var splitTotal = SplitCodeDetails.Sum(d => (d.PortionWeight ?? 0d) * (d.Portions ?? 0));
var basicTotal = Entry.TotalWeight ?? 0d;
if (Math.Abs(splitTotal - basicTotal) > 0.01d)
{
string hint;
if (splitTotal > basicTotal)
hint = $"本次打印拆码总重 {splitTotal:0.##},超出基础资料总重 {basicTotal:0.##},超出部分是否需要核对?";
else
hint = $"本次打印拆码总重 {splitTotal:0.##},少于基础资料总重 {basicTotal:0.##},剩余部分将无法继续拆码,是否继续?";
var confirm = System.Windows.MessageBox.Show(
hint,
"总重核对",
System.Windows.MessageBoxButton.OKCancel,
System.Windows.MessageBoxImage.Warning);
if (confirm != System.Windows.MessageBoxResult.OK) return;
}
}
try
{
IsLoading = true;
var globalIndex = 1;
// 续号起点:已有卡片数 = Σ HasCard==true 行的 Portions新增卡片从其后续编
var alreadyGenerated = SplitCodeDetails
.Where(d => d.HasCard)
.Sum(d => d.Portions ?? 0);
var globalIndex = alreadyGenerated + 1;
var baseBarcode = Entry.Barcode ?? "";
var failCount = 0;
var newCardCount = 0;
// 按行收集成功标记:只要该行所有份都加卡成功,行 HasCard=true
// 中途任一份失败则保留 HasCard=false下次再点「生成」时会重试该行
var rowSuccessMap = new Dictionary<RawMaterialSplitDetailItem, bool>();
foreach (var detail in SplitCodeDetails)
foreach (var detail in pendingRows)
{
var portions = detail.Portions ?? 0;
var rowAllOk = true;
for (var i = 0; i < portions; i++)
{
var card = new MesXslRawMaterialCard
{
// 关联到当前拆码明细行 — 便于「重新拆码」按此 ID 批删
SplitDetailId = detail.Id,
Barcode = baseBarcode + globalIndex.ToString("D3"),
BatchNo = Entry.BatchNo,
EntryDate = Entry.EntryTime?.Date ?? DateTime.Today,
@@ -219,21 +440,39 @@ public class RawMaterialEntryOperationViewModel : RawMaterialEntryEditDialogView
TenantId = Entry.TenantId
};
var ok = await _rawMaterialCardService.AddAsync(card);
if (!ok) failCount++;
if (ok) newCardCount++;
else { failCount++; rowAllOk = false; }
globalIndex++;
}
rowSuccessMap[detail] = rowAllOk;
}
// 更新打印状态为「已打印」
Entry.PrintFlag = "1";
await EntryService.EditAsync(Entry);
RaisePropertyChanged(nameof(Entry));
// 把整行加卡都成功的标记 HasCard=true触发该行 UI 自动锁定 + 删除按钮隐藏 + 打印列显示「已打印」
foreach (var kv in rowSuccessMap)
{
if (kv.Value) kv.Key.HasCard = true;
}
// 整票级 PrintFlag只要本批至少有一张卡片成功即置 1与原行为兼容
// 关键:必须先 ApplySplitDetailsToEntry(),把刚被设为 true 的 HasCard 序列化到 PortionCardFlags
// 否则下次重新打开时该行会回退为 falsePortionCardFlags 旧值未更新),从而被「继续拆码」流程误纳入待生成。
if (newCardCount > 0)
{
Entry.PrintFlag = "1";
ApplySplitDetailsToEntry();
await EntryService.EditAsync(Entry);
RaisePropertyChanged(nameof(Entry));
RaisePropertyChanged(nameof(IsPrinted));
RaisePropertyChanged(nameof(IsTotalWeightEditable));
AddSplitDetailCommand.RaiseCanExecuteChanged();
}
RaisePropertyChanged(nameof(CanGenerateCards));
RaisePropertyChanged(nameof(CanResplit));
var total = globalIndex - 1;
if (failCount == 0)
HandyControl.Controls.MessageBox.Success($"已生成 {total} 张原材料卡片,打印状态已更新为「已打印」!");
HandyControl.Controls.MessageBox.Success($"已生成 {newCardCount} 张原材料卡片(累计 {alreadyGenerated + newCardCount} 张),打印状态已更新为「已打印」!");
else
HandyControl.Controls.MessageBox.Warning($"共生成 {total} 张,其中 {failCount} 张失败,请检查网络后重试");
HandyControl.Controls.MessageBox.Warning($"本次共尝试生成 {newCardCount + failCount} 张,成功 {newCardCount} 张,失败 {failCount} 张失败的行未标记为「已打印」,可检查网络后再次点击「生成原材料卡片」重试");
}
catch (Exception ex)
{

View File

@@ -0,0 +1,133 @@
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.RawMaterialEntry;
/// <summary>
/// 库区选择弹窗 ViewModel。
/// 用于「拆码明细 → 库位」字段:点击库位输入框时弹出本对话框,支持按名称/编码搜索,单选确认。
/// </summary>
public class WarehouseAreaPickerDialogViewModel : BaseViewModel, IDialogResultable<bool>
{
private readonly IWarehouseAreaService _warehouseAreaService;
private string? _searchAreaName;
public string? SearchAreaName
{
get => _searchAreaName;
set => SetProperty(ref _searchAreaName, value);
}
private string? _searchAreaCode;
public string? SearchAreaCode
{
get => _searchAreaCode;
set => SetProperty(ref _searchAreaCode, value);
}
public ObservableCollection<MesXslWarehouseArea> Records { get; } = new();
private MesXslWarehouseArea? _selectedRecord;
public MesXslWarehouseArea? SelectedRecord
{
get => _selectedRecord;
set
{
SetProperty(ref _selectedRecord, value);
ConfirmCommand.RaiseCanExecuteChanged();
RaisePropertyChanged(nameof(SelectedRecordDisplay));
RaisePropertyChanged(nameof(HasSelectedRecord));
}
}
public string SelectedRecordDisplay => _selectedRecord != null
? $"[{_selectedRecord.AreaCode}] {_selectedRecord.AreaName} · 仓库:{_selectedRecord.WarehouseName ?? "-"}"
: "选中库区后点击「确认选择」";
public bool HasSelectedRecord => _selectedRecord != null;
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 WarehouseAreaPickerDialogViewModel(
IWarehouseAreaService warehouseAreaService,
IContainerExtension container,
IRegionManager regionManager) : base(container, regionManager)
{
_warehouseAreaService = warehouseAreaService;
SearchCommand = new DelegateCommand(async () => await LoadAsync());
ConfirmCommand = new DelegateCommand(Confirm, () => SelectedRecord != null);
CancelCommand = new DelegateCommand(() => CloseAction?.Invoke());
_ = LoadAsync();
}
/// <summary>
/// 可由外部传入「当前已选库区名称」用于打开后高亮预选。
/// </summary>
public void Initialize(string? currentAreaName)
{
if (!string.IsNullOrWhiteSpace(currentAreaName))
{
SearchAreaName = currentAreaName.Trim();
_ = LoadAsync();
}
}
private async Task LoadAsync()
{
try
{
IsLoading = true;
var result = await _warehouseAreaService.PageAsync(
1,
500,
areaCode: SearchAreaCode?.Trim(),
areaName: SearchAreaName?.Trim(),
warehouseId: null,
status: "0");
Records.Clear();
foreach (var record in result.Records)
{
Records.Add(record);
}
if (!string.IsNullOrWhiteSpace(SearchAreaName))
{
SelectedRecord = Records.FirstOrDefault(x =>
string.Equals(x.AreaName, SearchAreaName, StringComparison.OrdinalIgnoreCase));
}
}
catch
{
Records.Clear();
}
finally
{
IsLoading = false;
}
}
private void Confirm()
{
if (SelectedRecord == null)
{
return;
}
Result = true;
CloseAction?.Invoke();
}
}

View File

@@ -0,0 +1,113 @@
using HandyControl.Controls;
using HandyControl.Tools.Extension;
using YY.Admin.Core;
using YY.Admin.Core.Entity;
using YY.Admin.Core.Services;
namespace YY.Admin.ViewModels.WarehouseArea;
public class WarehouseAreaEditDialogViewModel : BaseViewModel, IDialogResultable<bool>
{
private readonly IWarehouseAreaService _warehouseAreaService;
private MesXslWarehouseArea? _area;
public MesXslWarehouseArea? Area
{
get => _area;
set => SetProperty(ref _area, value);
}
public bool IsAddMode => string.IsNullOrWhiteSpace(Area?.Id);
public string DialogTitle => IsAddMode ? "新增库区" : "编辑库区";
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 WarehouseAreaEditDialogViewModel(
IWarehouseAreaService warehouseAreaService,
IContainerExtension container,
IRegionManager regionManager) : base(container, regionManager)
{
_warehouseAreaService = warehouseAreaService;
SaveCommand = new DelegateCommand(async () => await SaveAsync());
CancelCommand = new DelegateCommand(() => CloseAction?.Invoke());
}
public void InitializeForAdd()
{
Area = new MesXslWarehouseArea { Status = "0" };
RaisePropertyChanged(nameof(IsAddMode));
RaisePropertyChanged(nameof(DialogTitle));
}
public void InitializeForEdit(MesXslWarehouseArea area)
{
Area = new MesXslWarehouseArea
{
Id = area.Id,
AreaCode = area.AreaCode,
AreaName = area.AreaName,
WarehouseId = area.WarehouseId,
WarehouseName = area.WarehouseName,
WarehouseCategory = area.WarehouseCategory,
WarehouseCategoryName = area.WarehouseCategoryName,
MaxCapacity = area.MaxCapacity,
ActualCapacity = area.ActualCapacity,
Remark = area.Remark,
Status = area.Status,
TenantId = area.TenantId,
};
RaisePropertyChanged(nameof(IsAddMode));
RaisePropertyChanged(nameof(DialogTitle));
}
private async Task SaveAsync()
{
if (Area == null) return;
if (string.IsNullOrWhiteSpace(Area.AreaCode))
{
MessageBox.Warning("库区编码不能为空!");
return;
}
var codeAvailable = await _warehouseAreaService.CheckAreaCodeAsync(Area.AreaCode.Trim(), Area.Id);
if (!codeAvailable)
{
MessageBox.Warning("该库区编码已存在,请更换!");
return;
}
Area.AreaCode = Area.AreaCode.Trim();
if (string.IsNullOrWhiteSpace(Area.AreaName))
Area.AreaName = Area.AreaCode;
try
{
bool ok;
if (IsAddMode)
{
ok = await _warehouseAreaService.AddAsync(Area);
if (ok) Growl.Success("新增库区成功!");
else { Growl.Error("新增失败,请重试!"); return; }
}
else
{
ok = await _warehouseAreaService.EditAsync(Area);
if (ok) Growl.Success("编辑库区成功!");
else { Growl.Error("编辑失败,请重试!"); return; }
}
Result = true;
CloseAction?.Invoke();
}
catch (Exception ex)
{
Growl.Error($"保存失败:{ex.Message}");
}
}
}

View File

@@ -0,0 +1,262 @@
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.Views.WarehouseArea;
namespace YY.Admin.ViewModels.WarehouseArea;
public class WarehouseAreaListViewModel : BaseViewModel
{
private readonly IWarehouseAreaService _warehouseAreaService;
private readonly IWarehouseService _warehouseService;
private readonly IDialogService _dialogService;
private SubscriptionToken? _changedToken;
private SubscriptionToken? _syncConflictToken;
private ObservableCollection<MesXslWarehouseArea> _areas = new();
public ObservableCollection<MesXslWarehouseArea> Areas
{
get => _areas;
set => SetProperty(ref _areas, 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? _filterAreaCode;
public string? FilterAreaCode { get => _filterAreaCode; set => SetProperty(ref _filterAreaCode, value); }
private string? _filterAreaName;
public string? FilterAreaName { get => _filterAreaName; set => SetProperty(ref _filterAreaName, value); }
private string? _filterWarehouseId;
public string? FilterWarehouseId { get => _filterWarehouseId; set => SetProperty(ref _filterWarehouseId, value); }
private string? _filterStatus;
public string? FilterStatus { get => _filterStatus; set => SetProperty(ref _filterStatus, value); }
public ObservableCollection<KeyValuePair<string, string>> WarehouseOptions { get; } = new();
public ObservableCollection<KeyValuePair<string, string>> StatusOptions { get; } = new();
public DelegateCommand SearchCommand { get; }
public DelegateCommand ResetCommand { get; }
public DelegateCommand AddCommand { get; }
public DelegateCommand<MesXslWarehouseArea> EditCommand { get; }
public DelegateCommand<MesXslWarehouseArea> DeleteCommand { get; }
public DelegateCommand<MesXslWarehouseArea> ToggleStatusCommand { get; }
public DelegateCommand PrevPageCommand { get; }
public DelegateCommand NextPageCommand { get; }
public WarehouseAreaListViewModel(
IWarehouseAreaService warehouseAreaService,
IWarehouseService warehouseService,
IContainerExtension container,
IDialogService dialogService,
IRegionManager regionManager) : base(container, regionManager)
{
_warehouseAreaService = warehouseAreaService;
_warehouseService = warehouseService;
_dialogService = dialogService;
SearchCommand = new DelegateCommand(async () => { PageNo = 1; await LoadAsync(); });
ResetCommand = new DelegateCommand(async () =>
{
FilterAreaCode = null;
FilterAreaName = null;
FilterWarehouseId = null;
FilterStatus = null;
PageNo = 1;
await LoadAsync();
});
AddCommand = new DelegateCommand(async () => await ShowAddDialogAsync());
EditCommand = new DelegateCommand<MesXslWarehouseArea>(async a => await ShowEditDialogAsync(a));
DeleteCommand = new DelegateCommand<MesXslWarehouseArea>(async a => await DeleteAsync(a));
ToggleStatusCommand = new DelegateCommand<MesXslWarehouseArea>(async a => await ToggleStatusAsync(a));
PrevPageCommand = new DelegateCommand(async () => { if (PageNo > 1) { PageNo--; await LoadAsync(); } });
NextPageCommand = new DelegateCommand(async () => { if ((long)PageNo * PageSize < Total) { PageNo++; await LoadAsync(); } });
_changedToken = _eventAggregator
.GetEvent<WarehouseAreaChangedEvent>()
.Subscribe(async p => await OnChangedAsync(p), ThreadOption.UIThread);
_syncConflictToken = _eventAggregator.GetEvent<SyncConflictEvent>()
.Subscribe(OnSyncConflict, ThreadOption.UIThread);
_ = InitializeAsync();
}
private async Task OnChangedAsync(WarehouseAreaChangedPayload payload)
{
if (payload.Action == "edit" && !string.IsNullOrWhiteSpace(payload.WarehouseAreaId))
await RefreshSingleAsync(payload.WarehouseAreaId!);
else
await LoadAsync();
}
private async Task RefreshSingleAsync(string id)
{
try
{
var updated = await _warehouseAreaService.GetByIdAsync(id);
if (updated == null) return;
var idx = Areas.ToList().FindIndex(a => string.Equals(a.Id, id, StringComparison.OrdinalIgnoreCase));
if (idx >= 0) Areas[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
{
StatusOptions.Clear();
StatusOptions.Add(new KeyValuePair<string, string>("全部", ""));
StatusOptions.Add(new KeyValuePair<string, string>("启用", "0"));
StatusOptions.Add(new KeyValuePair<string, string>("停用", "1"));
await LoadWarehouseOptionsAsync();
await UIHelper.WaitForRenderAsync();
await LoadAsync();
}
catch (Exception ex)
{
Debug.WriteLine($"库区列表初始化失败: {ex.Message}");
}
}
private async Task LoadWarehouseOptionsAsync()
{
try
{
var warehouses = await _warehouseService.GetAllAsync();
WarehouseOptions.Clear();
WarehouseOptions.Add(new KeyValuePair<string, string>("全部", ""));
foreach (var w in warehouses.Where(w => !string.IsNullOrWhiteSpace(w.Id)))
WarehouseOptions.Add(new KeyValuePair<string, string>(w.WarehouseName ?? w.Id!, w.Id!));
}
catch (Exception ex)
{
Debug.WriteLine($"[库区] 加载仓库选项失败: {ex.Message}");
if (WarehouseOptions.Count == 0)
WarehouseOptions.Add(new KeyValuePair<string, string>("全部", ""));
}
}
public async Task LoadAsync()
{
try
{
IsLoading = true;
var result = await _warehouseAreaService.PageAsync(PageNo, PageSize, FilterAreaCode, FilterAreaName, FilterWarehouseId, FilterStatus);
Areas = new ObservableCollection<MesXslWarehouseArea>(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<WarehouseAreaEditDialogView>()
.Initialize<WarehouseAreaEditDialogViewModel>(vm => vm.InitializeForAdd())
.GetResultAsync<bool>();
if (result) await LoadAsync();
}
catch (Exception ex)
{
Growl.Error($"打开新增对话框失败:{ex.Message}");
}
}
private async Task ShowEditDialogAsync(MesXslWarehouseArea area)
{
if (area == null) return;
try
{
var result = await HandyControl.Controls.Dialog.Show<WarehouseAreaEditDialogView>()
.Initialize<WarehouseAreaEditDialogViewModel>(vm => vm.InitializeForEdit(area))
.GetResultAsync<bool>();
if (result) await LoadAsync();
}
catch (Exception ex)
{
Growl.Error($"打开编辑对话框失败:{ex.Message}");
}
}
private async Task DeleteAsync(MesXslWarehouseArea area)
{
if (area?.Id == null) return;
var confirm = System.Windows.MessageBox.Show($"确定删除库区「{area.AreaCode}」?此操作不可恢复!", "确认删除",
MessageBoxButton.OKCancel, MessageBoxImage.Question);
if (confirm != System.Windows.MessageBoxResult.OK) return;
var ok = await _warehouseAreaService.DeleteAsync(area.Id);
if (ok) { Growl.Success("删除成功!"); await LoadAsync(); }
else Growl.Error("删除失败!");
}
private async Task ToggleStatusAsync(MesXslWarehouseArea area)
{
if (area?.Id == null) return;
var newStatus = area.Status == "1" ? "0" : "1";
var ok = await _warehouseAreaService.UpdateStatusAsync(area.Id, newStatus);
if (ok) { area.Status = newStatus; RaisePropertyChanged(nameof(Areas)); }
else Growl.Error("状态切换失败!");
}
protected override void CleanUp()
{
base.CleanUp();
if (_changedToken != null)
{
_eventAggregator.GetEvent<WarehouseAreaChangedEvent>().Unsubscribe(_changedToken);
_changedToken = null;
}
if (_syncConflictToken != null)
{
_eventAggregator.GetEvent<SyncConflictEvent>().Unsubscribe(_syncConflictToken);
_syncConflictToken = null;
}
}
}

View File

@@ -119,8 +119,8 @@
</DataTemplate>
</DataGrid.RowHeaderTemplate>
<DataGrid.Columns>
<DataGridTextColumn Header="条码" Binding="{Binding Barcode}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="150"/>
<DataGridTextColumn Header="批次号" Binding="{Binding BatchNo}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="130"/>
<DataGridTextColumn Header="条码" Binding="{Binding Barcode}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="200"/>
<DataGridTextColumn Header="批次号" Binding="{Binding BatchNo}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="180"/>
<DataGridTextColumn Header="入场日期" Binding="{Binding EntryDateText}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="100"/>
<DataGridTextColumn Header="物料名称" Binding="{Binding MaterialName}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="150"/>
<DataGridTextColumn Header="供应商名称" Binding="{Binding SupplierName}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="140"/>

View File

@@ -116,8 +116,8 @@
</DataTemplate>
</DataGrid.RowHeaderTemplate>
<DataGrid.Columns>
<DataGridTextColumn Header="条码" Binding="{Binding Barcode}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="140"/>
<DataGridTextColumn Header="批次号" Binding="{Binding BatchNo}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="120"/>
<DataGridTextColumn Header="条码" Binding="{Binding Barcode}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="200"/>
<DataGridTextColumn Header="批次号" Binding="{Binding BatchNo}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="180"/>
<DataGridTextColumn Header="入场时间" Binding="{Binding EntryTime, StringFormat='yyyy-MM-dd HH:mm'}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="140"/>
<DataGridTextColumn Header="榜单号" Binding="{Binding BillNo}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="130"/>
<DataGridTextColumn Header="物料名称" Binding="{Binding MaterialName}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="140"/>

View File

@@ -82,14 +82,22 @@
<hc:ScrollViewer Grid.Column="0" IsInertiaEnabled="True" HorizontalScrollBarVisibility="Disabled">
<StackPanel x:Name="RootPanel" Margin="24,8,24,8">
<TextBlock Text="基础资料"
FontSize="13"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryTextBrush}"
Margin="0,0,0,6"/>
<DockPanel Margin="0,0,0,6">
<Button DockPanel.Dock="Right"
Content="重置"
Command="{Binding ResetCommand}"
Style="{StaticResource ButtonDefault}"
Width="80" Height="28" FontSize="12"/>
<TextBlock Text="基础资料"
FontSize="13"
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryTextBrush}"
VerticalAlignment="Center"/>
</DockPanel>
<!-- 带横竖线的表格式表单 -->
<Border BorderThickness="1" BorderBrush="{DynamicResource BorderBrush}" Margin="0,0,0,8">
<!-- 带横竖线的表格式表单已打印时整体禁用IsTotalWeightEditable=falseIsEnabled 向下继承到所有子控件 -->
<Border BorderThickness="1" BorderBrush="{DynamicResource BorderBrush}" Margin="0,0,0,8"
IsEnabled="{Binding IsTotalWeightEditable}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="36"/> <!-- 密炼物料 -->
@@ -97,7 +105,7 @@
<RowDefinition Height="36"/> <!-- 榜单号 -->
<RowDefinition Height="36"/> <!-- 供料客户 / 供应商名称 -->
<RowDefinition Height="36"/> <!-- 厂家物料名称 / 保质期 -->
<RowDefinition Height="36"/> <!-- 总重 / 总份数 -->
<RowDefinition Height="40"/> <!-- 总重 / 总份数 — 多 4px 给 NumericUpDown 上下间距,否则 spinner 箭头会被裁切 -->
<RowDefinition Height="36"/> <!-- 每份总重 / 每份包数 -->
<RowDefinition Height="36"/> <!-- 库位 / 卸货人 -->
<RowDefinition Height="60"/> <!-- 备注 -->
@@ -248,7 +256,7 @@
FontSize="12" Foreground="{DynamicResource PrimaryTextBrush}"/>
</Border>
<Border Grid.Row="3" Grid.Column="3"
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource BorderBrush}" Padding="4,0">
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource BorderBrush}" Padding="6,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
@@ -277,14 +285,15 @@
</TextBlock.Style>
</TextBlock>
</Grid>
<!-- 与「密炼物料 / 榜单号」两行的「选择 + 清除」按钮参数完全一致:右端齐平 + 内外边距统一 -->
<Button Grid.Column="1" Content="选 择"
Command="{Binding OpenSupplierPickerCommand}"
Style="{StaticResource ButtonPrimary}"
Height="26" Margin="4,0,0,0" FontSize="12" Padding="8,0"/>
Height="26" Margin="6,0,0,0" FontSize="12" Padding="10,0"/>
<Button Grid.Column="2"
Command="{Binding ClearSupplierCommand}"
Style="{StaticResource ButtonIcon}"
Height="26" Width="24" Margin="2,0,0,0" Padding="0"
Height="26" Width="24" Margin="4,0,0,0" Padding="0"
ToolTip="清除供应商"
Foreground="{DynamicResource SecondaryTextBrush}"
hc:IconElement.Geometry="{StaticResource ErrorGeometry}"/>
@@ -324,9 +333,14 @@
FontSize="12" Foreground="{DynamicResource PrimaryTextBrush}"/>
</Border>
<Border Grid.Row="5" Grid.Column="1"
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}" Padding="4,4">
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}" Padding="4,2">
<!--
显式 Height + VerticalAlignment=Center 避免被 Border 拉伸到 36+px
否则 HandyControl 的 NumericUpDownPlus 内置 spinner 会被压扁导致箭头显示不全。
-->
<hc:NumericUpDown Value="{Binding TotalWeightInput}"
Minimum="0" DecimalPlaces="2"
Height="30" VerticalAlignment="Center"
Style="{StaticResource NumericUpDownPlus}"
hc:InfoElement.Placeholder="请输入总重"/>
</Border>
@@ -338,8 +352,26 @@
</Border>
<Border Grid.Row="5" Grid.Column="3"
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource BorderBrush}" Padding="6,0">
<TextBlock Text="{Binding SplitTotalPortionsDisplay}" VerticalAlignment="Center"
FontSize="13" Foreground="{DynamicResource SecondaryTextBrush}"/>
<!-- 由拆码明细聚合而来:未维护明细时显示灰色占位提示,提示用户操作路径 -->
<Grid>
<TextBlock Text="{Binding SplitTotalPortionsDisplay}" VerticalAlignment="Center"
FontSize="13" Foreground="{DynamicResource SecondaryTextBrush}"
TextTrimming="CharacterEllipsis"/>
<TextBlock Text="由拆码明细自动生成" VerticalAlignment="Center"
FontSize="12" Foreground="#BFBFBF" FontStyle="Italic"
IsHitTestVisible="False">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SplitTotalPortionsDisplay}" Value="">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Border>
<!-- ===== Row 6: 每份总重(KG) / 每份包数 ===== -->
@@ -352,8 +384,25 @@
</Border>
<Border Grid.Row="6" Grid.Column="1"
BorderThickness="0,0,1,1" BorderBrush="{DynamicResource BorderBrush}" Padding="6,0">
<TextBlock Text="{Binding SplitPortionWeightDisplay}" VerticalAlignment="Center"
FontSize="13" Foreground="{DynamicResource SecondaryTextBrush}"/>
<Grid>
<TextBlock Text="{Binding SplitPortionWeightDisplay}" VerticalAlignment="Center"
FontSize="13" Foreground="{DynamicResource SecondaryTextBrush}"
TextTrimming="CharacterEllipsis"/>
<TextBlock Text="由拆码明细自动生成" VerticalAlignment="Center"
FontSize="12" Foreground="#BFBFBF" FontStyle="Italic"
IsHitTestVisible="False">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SplitPortionWeightDisplay}" Value="">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Border>
<Border Grid.Row="6" Grid.Column="2"
Background="{DynamicResource SecondaryRegionBrush}"
@@ -363,8 +412,25 @@
</Border>
<Border Grid.Row="6" Grid.Column="3"
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource BorderBrush}" Padding="6,0">
<TextBlock Text="{Binding SplitPortionPackagesDisplay}" VerticalAlignment="Center"
FontSize="13" Foreground="{DynamicResource SecondaryTextBrush}"/>
<Grid>
<TextBlock Text="{Binding SplitPortionPackagesDisplay}" VerticalAlignment="Center"
FontSize="13" Foreground="{DynamicResource SecondaryTextBrush}"
TextTrimming="CharacterEllipsis"/>
<TextBlock Text="由拆码明细自动生成" VerticalAlignment="Center"
FontSize="12" Foreground="#BFBFBF" FontStyle="Italic"
IsHitTestVisible="False">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SplitPortionPackagesDisplay}" Value="">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Border>
<!-- ===== Row 7: 库位 / 卸货人 ===== -->
@@ -528,13 +594,29 @@
FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryTextBrush}"
VerticalAlignment="Center"/>
<Button DockPanel.Dock="Right"
Content="新增明细"
Command="{Binding AddSplitDetailCommand}"
Style="{StaticResource ButtonPrimary}"
Height="30"
Padding="10,0"
FontSize="12"/>
<!-- 右侧按钮组:新增明细(始终可用,支持「继续拆码」) + 重新拆码(仅编辑态可用) -->
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" HorizontalAlignment="Right">
<!--
「新增明细」:始终可用。
已打印记录上点击 → 追加一行 HasCard=false 的可编辑行;
再点「生成原材料卡片」时按 HasCard 过滤,只对新增的行加卡(条码从已有数 +1 续编)。
-->
<Button Content="新增明细"
Command="{Binding AddSplitDetailCommand}"
Style="{StaticResource ButtonPrimary}"
Height="30"
Padding="10,0"
FontSize="12"
ToolTip="新增一行待拆码明细。已打印记录追加新行时支持「继续拆码」"/>
<Button Content="重新拆码"
Command="{Binding ResplitCommand}"
Style="{StaticResource ButtonWarning}"
Height="30"
Padding="10,0"
Margin="8,0,0,0"
FontSize="12"
ToolTip="清除该入场记录已生成的原材料卡片并清空明细,重新进行拆码"/>
</StackPanel>
</DockPanel>
<StackPanel>
@@ -546,6 +628,7 @@
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="90"/>
<ColumnDefinition Width="90"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="份数"
HorizontalAlignment="Center" VerticalAlignment="Center"
@@ -559,11 +642,17 @@
HorizontalAlignment="Center" VerticalAlignment="Center"
FontWeight="SemiBold" FontSize="13"
Foreground="{DynamicResource PrimaryTextBrush}"/>
<!-- 库位列保存时非必填点击「生成原材料卡片」时必填ToolTip 给出说明 -->
<TextBlock Grid.Column="3" Text="库位"
HorizontalAlignment="Center" VerticalAlignment="Center"
FontWeight="SemiBold" FontSize="13"
Foreground="{DynamicResource PrimaryTextBrush}"
ToolTip="生成原材料卡片时为必填项"/>
<TextBlock Grid.Column="4" Text="打印标记"
HorizontalAlignment="Center" VerticalAlignment="Center"
FontWeight="SemiBold" FontSize="13"
Foreground="{DynamicResource PrimaryTextBrush}"/>
<TextBlock Grid.Column="4" Text="操作"
<TextBlock Grid.Column="5" Text="操作"
HorizontalAlignment="Center" VerticalAlignment="Center"
FontWeight="SemiBold" FontSize="13"
Foreground="{DynamicResource PrimaryTextBrush}"/>
@@ -591,6 +680,35 @@
<ItemsControl ItemsSource="{Binding SplitCodeDetails}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<!--
已生成卡片的「行」锁定样式HasCard==true
把份数 / 每份重量 / 每份包数 三个 TextBox 设为只读 + 淡灰背景。
配合"该行隐藏删除按钮 + 库位按钮禁用"形成完整防护,避免改了行内数值后
与已生成的原材料卡片数据脱节。
业务流程:用户在已打印记录上想继续拆码 → 新增行HasCard=false 可编辑)
→ 点「生成原材料卡片」只对未生成行加卡(条码续号续接)。
-->
<Style x:Key="LockableSplitTextBoxStyle" TargetType="TextBox">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Height" Value="32"/>
<Setter Property="BorderBrush" Value="#D9D9D9"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Background" Value="White"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="Margin" Value="4,0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding HasCard}" Value="True">
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="Background" Value="#F5F5F5"/>
<Setter Property="Foreground" Value="#8C8C8C"/>
<Setter Property="ToolTip" Value="该行已生成原材料卡片,不可修改。如需调整请先点「重新拆码」清空全部卡片"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataTemplate.Resources>
<Grid Height="44">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
@@ -598,42 +716,156 @@
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="90"/>
<ColumnDefinition Width="90"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
<TextBox Text="{Binding Portions, UpdateSourceTrigger=PropertyChanged}"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
VerticalAlignment="Center" Height="32"
BorderBrush="#D9D9D9" BorderThickness="1"
Background="White" FontSize="13" Margin="4,0"/>
Style="{StaticResource LockableSplitTextBoxStyle}"/>
</Border>
<Border Grid.Column="1" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
<TextBox Text="{Binding PortionWeight, UpdateSourceTrigger=PropertyChanged}"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
VerticalAlignment="Center" Height="32"
BorderBrush="#D9D9D9" BorderThickness="1"
Background="White" FontSize="13" Margin="4,0"/>
Style="{StaticResource LockableSplitTextBoxStyle}"/>
</Border>
<Border Grid.Column="2" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
<TextBox Text="{Binding PortionPackages, UpdateSourceTrigger=PropertyChanged}"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
VerticalAlignment="Center" Height="32"
BorderBrush="#D9D9D9" BorderThickness="1"
Background="White" FontSize="13" Margin="4,0"/>
Style="{StaticResource LockableSplitTextBoxStyle}"/>
</Border>
<Border Grid.Column="3" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
<TextBox Text="{Binding WarehouseLocation, UpdateSourceTrigger=PropertyChanged}"
HorizontalContentAlignment="Stretch" VerticalContentAlignment="Center"
VerticalAlignment="Center" Height="32"
BorderBrush="#D9D9D9" BorderThickness="1"
Background="White" FontSize="13" Padding="8,0" Margin="4,0"/>
<!--
InputBindings 内 RelativeSource 不在可视树中,查找会静默失败。
改用 Button + ControlTemplateCommand 写在 Button 元素上可视树正常RelativeSource 可靠。
-->
<Button Command="{Binding DataContext.OpenWarehouseAreaPickerCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding}"
Cursor="Hand"
VerticalAlignment="Center"
Height="32"
Margin="4,0"
Focusable="False">
<!-- 行级锁定HasCard==true该行已生成卡片时禁用与三个数字字段一致 -->
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="True"/>
<Setter Property="ToolTip" Value="点击选择库区"/>
<Style.Triggers>
<DataTrigger Binding="{Binding HasCard}" Value="True">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="ToolTip" Value="该行已生成原材料卡片,不可修改。如需调整请先点「重新拆码」清空全部卡片"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<Button.Template>
<!-- 视觉与同行的 TextBox 对齐:相同高度/边框色/圆角/字号,内容水平居中 -->
<ControlTemplate TargetType="Button">
<Border x:Name="Bd"
BorderBrush="#D9D9D9" BorderThickness="1"
CornerRadius="2"
Background="White">
<Grid>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
FontSize="13" TextTrimming="CharacterEllipsis"
Text="{Binding WarehouseLocation}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="#1F1F1F"/>
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding WarehouseLocation}" Value="">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding WarehouseLocation}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
FontSize="13" Text="点击选择库区" Foreground="#BFBFBF">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding WarehouseLocation}" Value="">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding WarehouseLocation}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="BorderBrush" Value="#4096FF"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="Bd" Property="Background" Value="#F0F7FF"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
</Border>
<!--
打印标记列行级状态HasCard与「继续拆码」流程契合。
旧行(已生成卡片)显示绿色「已打印」;新增的待生成行显示灰色「未打印」。
-->
<Border Grid.Column="4" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
<Grid>
<Border CornerRadius="2" Padding="6,2" VerticalAlignment="Center" HorizontalAlignment="Center">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="#F5F5F5"/>
<Setter Property="BorderBrush" Value="#D9D9D9"/>
<Setter Property="BorderThickness" Value="1"/>
<Style.Triggers>
<DataTrigger Binding="{Binding HasCard}" Value="True">
<Setter Property="Background" Value="#F6FFED"/>
<Setter Property="BorderBrush" Value="#B7EB8F"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock VerticalAlignment="Center" FontSize="12">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="未打印"/>
<Setter Property="Foreground" Value="#8C8C8C"/>
<Style.Triggers>
<DataTrigger Binding="{Binding HasCard}" Value="True">
<Setter Property="Text" Value="已打印"/>
<Setter Property="Foreground" Value="#52C41A"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
</Grid>
</Border>
<Border Grid.Column="5" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
<!-- 行级HasCard==true该行已生成卡片时隐藏删除按钮新增的待生成行可正常删除 -->
<Button Content="删除"
Command="{Binding DataContext.RemoveSplitDetailCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding}"
Style="{StaticResource ButtonDanger}"
VerticalAlignment="Center" Height="28"
Padding="6,0" Margin="12,0" FontSize="11"/>
Padding="6,0" Margin="12,0" FontSize="11">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource ButtonDanger}">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding HasCard}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Border>
</Grid>
</DataTemplate>
@@ -662,70 +894,115 @@
BorderThickness="1,0,0,0"
Background="{DynamicResource RegionBrush}">
<DockPanel LastChildFill="True">
<Border DockPanel.Dock="Top" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1" Padding="8,6">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Vertical">
<TextBlock Text="今日入场" FontWeight="SemiBold" FontSize="13"
<!-- ===== 标题 + 日期筛选 ===== -->
<Border DockPanel.Dock="Top" BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="0,0,0,1" Padding="8,6">
<StackPanel>
<DockPanel>
<StackPanel>
<TextBlock Text="原料入场记录" FontWeight="SemiBold" FontSize="13"
Foreground="{DynamicResource PrimaryTextBrush}"/>
<TextBlock Text="按入场/创建日期为本日的记录" FontSize="11" Opacity="0.65"
Foreground="{DynamicResource SecondaryTextBrush}" TextWrapping="Wrap"/>
<TextBlock Text="点击记录可回填到左侧表单" FontSize="11" Opacity="0.65"
Foreground="{DynamicResource SecondaryTextBrush}"/>
</StackPanel>
<Button Grid.Column="1" Content="刷新" Margin="4,0"
<Button DockPanel.Dock="Right" Content="刷新" Margin="4,0,0,0"
Command="{Binding RefreshTodayEntriesCommand}"
Style="{StaticResource ButtonPrimary}" Padding="8,2" FontSize="11" Height="26"/>
</Grid>
</Border>
Style="{StaticResource ButtonPrimary}"
Padding="8,2" FontSize="11" Height="26"
VerticalAlignment="Top"/>
</DockPanel>
<ComboBox ItemsSource="{Binding DateRangeOptions}"
SelectedItem="{Binding SelectedDateRange}"
Margin="0,6,0,0" Height="26" FontSize="12"/>
</StackPanel>
</Border>
<Border DockPanel.Dock="Top" Background="{DynamicResource SecondaryRegionBrush}" Height="28" Padding="8,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="条码" FontWeight="SemiBold" FontSize="11"
VerticalAlignment="Center"
Foreground="{DynamicResource PrimaryTextBrush}"/>
<TextBlock Grid.Column="1" Text="物料" FontWeight="SemiBold" FontSize="11"
Margin="6,0,0,0" VerticalAlignment="Center"
Foreground="{DynamicResource PrimaryTextBrush}"/>
</Grid>
</Border>
<!-- ===== 入场记录列表(卡片式,支持换行) ===== -->
<ListBox ItemsSource="{Binding TodayEntries}"
SelectedItem="{Binding SelectedTodayEntry, Mode=TwoWay}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
BorderThickness="0"
Background="Transparent"
ItemContainerStyle="{StaticResource TodayEntryListBoxItemStyle}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type core:MesXslRawMaterialEntry}">
<Border BorderBrush="{DynamicResource BorderBrush}"
BorderThickness="0,0,0,1" Padding="8,8">
<StackPanel>
<ListBox ItemsSource="{Binding TodayEntries}"
SelectedItem="{Binding SelectedTodayEntry, Mode=TwoWay}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
BorderThickness="0"
Background="Transparent"
ItemContainerStyle="{StaticResource TodayEntryListBoxItemStyle}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type core:MesXslRawMaterialEntry}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Barcode}" FontSize="12"
TextTrimming="CharacterEllipsis"
<!-- 第一行:条码 + 打印状态 -->
<DockPanel>
<TextBlock DockPanel.Dock="Right"
Text="{Binding PrintFlagText}"
FontSize="13" Margin="6,0,0,0"
Foreground="{DynamicResource SecondaryTextBrush}"
VerticalAlignment="Top"/>
<TextBlock FontSize="14" TextWrapping="Wrap"
Foreground="{DynamicResource PrimaryTextBrush}"
ToolTip="{Binding Barcode}">
<Run Text="条码:" FontWeight="SemiBold"/>
<Run Text="{Binding Barcode}"/>
</TextBlock>
</DockPanel>
<!-- 第二行:榜单号 -->
<TextBlock FontSize="14" TextWrapping="Wrap"
Margin="0,5,0,0"
Foreground="{DynamicResource PrimaryTextBrush}"
ToolTip="{Binding Barcode}"/>
<TextBlock Grid.Column="1" Text="{Binding MaterialName}" FontSize="12"
TextTrimming="CharacterEllipsis" Margin="6,0,0,0"
ToolTip="{Binding BillNo}">
<Run Text="榜单号:" FontWeight="SemiBold"/>
<Run Text="{Binding BillNo}"/>
</TextBlock>
<!-- 第三行:批次号(加黑加粗) -->
<TextBlock FontSize="14" TextWrapping="Wrap"
FontWeight="Bold"
Margin="0,5,0,0"
Foreground="{DynamicResource PrimaryTextBrush}"
ToolTip="{Binding BatchNo}">
<Run Text="批次号:"/>
<Run Text="{Binding BatchNo}"/>
</TextBlock>
<!-- 物料 + 总重 -->
<DockPanel Margin="0,5,0,0">
<TextBlock DockPanel.Dock="Right"
FontSize="13" Margin="6,0,0,0"
Foreground="{DynamicResource SecondaryTextBrush}"
ToolTip="{Binding TotalWeight, StringFormat={}{0:0.##} KG}"
VerticalAlignment="Top">
<Run Text="总重:" FontWeight="SemiBold"/>
<Run Text="{Binding TotalWeight, StringFormat={}{0:0.##}KG}"/>
</TextBlock>
<TextBlock FontSize="14" TextWrapping="Wrap"
Foreground="{DynamicResource SecondaryTextBrush}"
ToolTip="{Binding MaterialName}">
<Run Text="物料:" FontWeight="SemiBold"/>
<Run Text="{Binding MaterialName}"/>
</TextBlock>
</DockPanel>
<!-- 供应商 -->
<TextBlock FontSize="13" TextWrapping="Wrap"
Margin="0,4,0,0" Opacity="0.7"
Foreground="{DynamicResource SecondaryTextBrush}"
ToolTip="{Binding MaterialName}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ToolTip="{Binding SupplierName}">
<Run Text="供应商:" FontWeight="SemiBold"/>
<Run Text="{Binding SupplierName}"/>
</TextBlock>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</Border>
</Grid>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,12,0,20">
<Button Content="重置" Command="{Binding ResetCommand}" Style="{StaticResource ButtonDefault}" Margin="0,0,15,0" Width="100"/>
<Button Content="保存" Command="{Binding SaveCommand}" Style="{StaticResource ButtonPrimary}" Width="100" Margin="0,0,15,0"/>
<Button Content="生成原材料卡片"
Command="{Binding GenerateRawMaterialCardsCommand}"

View File

@@ -0,0 +1,178 @@
<UserControl x:Class="YY.Admin.Views.RawMaterialEntry.WarehouseAreaPickerDialogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:hc="https://handyorg.github.io/handycontrol"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
Width="760">
<Grid Background="{DynamicResource ThirdlyRegionBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="360"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 标题 -->
<hc:SimplePanel Margin="20,16,20,12">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Border Width="32" Height="32" CornerRadius="6" Background="{DynamicResource PrimaryBrush}" Margin="0,0,10,0">
<!-- MaterialDesignThemes 的 PackIconKind 没有 WarehouseOutline仅 Warehouse 可用,否则 TypeConverter 解析时抛异常 -->
<md:PackIcon Kind="Warehouse" Width="18" Height="18" Foreground="White"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<TextBlock Text="选择库区" FontSize="16" FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryTextBrush}" VerticalAlignment="Center"/>
</StackPanel>
<Button Width="22" Height="22" Command="hc:ControlCommands.Close"
Style="{StaticResource ButtonIcon}"
Foreground="{DynamicResource PrimaryBrush}"
hc:IconElement.Geometry="{StaticResource ErrorGeometry}"
Padding="0" HorizontalAlignment="Right" VerticalAlignment="Center"/>
</hc:SimplePanel>
<!-- 搜索 -->
<Border Grid.Row="1" Background="{DynamicResource RegionBrush}" Padding="16,10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<hc:TextBox Text="{Binding SearchAreaName, UpdateSourceTrigger=PropertyChanged}"
hc:InfoElement.Placeholder="输入库区名称搜索..."
hc:InfoElement.ShowClearButton="True"
Margin="0,0,8,0">
<hc:TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding SearchCommand}"/>
</hc:TextBox.InputBindings>
</hc:TextBox>
<hc:TextBox Grid.Column="1"
Text="{Binding SearchAreaCode, UpdateSourceTrigger=PropertyChanged}"
hc:InfoElement.Placeholder="输入库区编码搜索..."
hc:InfoElement.ShowClearButton="True"
Margin="0,0,8,0">
<hc:TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding SearchCommand}"/>
</hc:TextBox.InputBindings>
</hc:TextBox>
<Button Grid.Column="2" Command="{Binding SearchCommand}"
IsEnabled="{Binding IsLoading, Converter={StaticResource Boolean2BooleanReConverter}}"
Style="{StaticResource ButtonPrimary}" Height="32">
<StackPanel Orientation="Horizontal">
<md:PackIcon Kind="Magnify" Width="14" Height="14" VerticalAlignment="Center" Margin="0,0,4,0"/>
<TextBlock Text="搜索" FontSize="13" VerticalAlignment="Center"/>
</StackPanel>
</Button>
</Grid>
</Border>
<!-- 列表 -->
<DataGrid x:Name="WarehouseAreasGrid"
Grid.Row="2"
Margin="16,8,16,0"
ItemsSource="{Binding Records}"
SelectedItem="{Binding SelectedRecord}"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeRows="False"
HeadersVisibility="Column"
SelectionMode="Single"
SelectionUnit="FullRow"
IsReadOnly="True"
GridLinesVisibility="Horizontal"
HorizontalGridLinesBrush="#FFEDEFF2"
VerticalGridLinesBrush="Transparent"
Background="White"
RowBackground="White"
AlternatingRowBackground="#FAFCFF"
RowHeight="32"
ColumnHeaderHeight="34"
RowHeaderWidth="0"
MouseDoubleClick="WarehouseAreasGrid_MouseDoubleClick">
<DataGrid.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="#EAF3FF"/>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="#1F1F1F"/>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="#EAF3FF"/>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}" Color="#1F1F1F"/>
</DataGrid.Resources>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Background" Value="#F5F7FA"/>
<Setter Property="Foreground" Value="#606266"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Padding" Value="8,0"/>
<Setter Property="BorderBrush" Value="#EBEEF5"/>
<Setter Property="BorderThickness" Value="0,0,0,1"/>
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background" Value="White"/>
<Setter Property="Foreground" Value="#262626"/>
<Setter Property="BorderBrush" Value="#FFEDEFF2"/>
<Setter Property="BorderThickness" Value="0,0,0,1"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="#EAF3FF"/>
<Setter Property="Foreground" Value="#1F1F1F"/>
<Setter Property="BorderBrush" Value="#D6E8FF"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#F5F9FF"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="8,0"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
</Style>
</DataGrid.CellStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="库区编码" Binding="{Binding AreaCode}" Width="130"/>
<DataGridTextColumn Header="库区名称" Binding="{Binding AreaName}" Width="*"/>
<DataGridTextColumn Header="所属仓库" Binding="{Binding WarehouseName}" Width="160"/>
<DataGridTextColumn Header="仓库分类" Binding="{Binding WarehouseCategoryName}" Width="100"/>
<DataGridTextColumn Header="状态" Binding="{Binding StatusText}" Width="70"/>
</DataGrid.Columns>
</DataGrid>
<!-- 底部 -->
<Border Grid.Row="3" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,1,0,0"
Padding="16,12">
<Grid>
<TextBlock VerticalAlignment="Center" FontSize="13" MaxWidth="420"
TextTrimming="CharacterEllipsis" HorizontalAlignment="Left"
Text="{Binding SelectedRecordDisplay}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{DynamicResource SecondaryTextBrush}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding HasSelectedRecord}" Value="True">
<Setter Property="Foreground" Value="{DynamicResource PrimaryBrush}"/>
<Setter Property="FontWeight" Value="SemiBold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="取 消" Command="{Binding CancelCommand}"
Style="{StaticResource ButtonDefault}" Width="88" Margin="0,0,12,0" Height="34"/>
<Button Content="确认选择" Command="{Binding ConfirmCommand}"
Style="{StaticResource ButtonPrimary}" Width="100" Height="34"/>
</StackPanel>
</Grid>
</Border>
</Grid>
</UserControl>

View File

@@ -0,0 +1,24 @@
using System.Windows.Controls;
using System.Windows.Input;
using YY.Admin.ViewModels.RawMaterialEntry;
namespace YY.Admin.Views.RawMaterialEntry;
public partial class WarehouseAreaPickerDialogView : UserControl
{
public WarehouseAreaPickerDialogView()
{
InitializeComponent();
}
/// <summary>
/// 双击数据行 = 确认选择,提升使用效率。
/// </summary>
private void WarehouseAreasGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (DataContext is WarehouseAreaPickerDialogViewModel vm && vm.ConfirmCommand.CanExecute())
{
vm.ConfirmCommand.Execute();
}
}
}

View File

@@ -136,9 +136,11 @@
</DataGrid.CellStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="榜单号" Binding="{Binding BillNo}" Width="180"/>
<DataGridTextColumn Header="车牌号" Binding="{Binding PlateNumber}" Width="120"/>
<DataGridTextColumn Header="车牌号" Binding="{Binding PlateNumber}" Width="100"/>
<DataGridTextColumn Header="供应商(发货单位)" Binding="{Binding SenderUnit}" Width="*"/>
<DataGridTextColumn Header="称重日期" Binding="{Binding WeighDate, StringFormat='yyyy-MM-dd'}" Width="120"/>
<DataGridTextColumn Header="净重(KG)" Binding="{Binding NetWeight, StringFormat=N2}" Width="90"/>
<DataGridTextColumn Header="已入场(KG)" Binding="{Binding EnteredWeight, StringFormat=N2}" Width="100"/>
<DataGridTextColumn Header="称重日期" Binding="{Binding WeighDate, StringFormat='yyyy-MM-dd'}" Width="110"/>
</DataGrid.Columns>
</DataGrid>

View File

@@ -0,0 +1,143 @@
<UserControl x:Class="YY.Admin.Views.WarehouseArea.WarehouseAreaEditDialogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:hc="https://handyorg.github.io/handycontrol"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
Width="640"
MinHeight="360">
<Grid Background="{DynamicResource ThirdlyRegionBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 标题栏 -->
<hc:SimplePanel Margin="20">
<TextBlock FontSize="18" Foreground="{DynamicResource PrimaryTextBrush}" Text="{Binding DialogTitle}" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Button Width="22" Height="22" Command="hc:ControlCommands.Close" Style="{StaticResource ButtonIcon}"
Foreground="{DynamicResource PrimaryBrush}" hc:IconElement.Geometry="{StaticResource ErrorGeometry}"
Padding="0" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,4,4,0"/>
</hc:SimplePanel>
<!-- 表单 -->
<hc:ScrollViewer Grid.Row="1" IsInertiaEnabled="True">
<StackPanel Margin="20,0,20,0">
<hc:Row Gutter="10">
<!-- 库区编码 -->
<hc:Col Span="12">
<hc:TextBox Text="{Binding Area.AreaCode, UpdateSourceTrigger=PropertyChanged}"
hc:InfoElement.Title="库区编码"
hc:InfoElement.TitleWidth="80"
hc:InfoElement.TitlePlacement="Left"
hc:InfoElement.Placeholder="请输入库区编码"
hc:InfoElement.Necessary="True"
hc:InfoElement.Symbol="*"
hc:InfoElement.ShowClearButton="True"
Margin="0,0,0,16"/>
</hc:Col>
<!-- 库区名称 -->
<hc:Col Span="12">
<hc:TextBox Text="{Binding Area.AreaName, UpdateSourceTrigger=PropertyChanged}"
hc:InfoElement.Title="库区名称"
hc:InfoElement.TitleWidth="80"
hc:InfoElement.TitlePlacement="Left"
hc:InfoElement.Placeholder="默认同库区编码"
hc:InfoElement.ShowClearButton="True"
Margin="0,0,0,16"/>
</hc:Col>
<!-- 所属仓库名称(只读显示) -->
<hc:Col Span="12">
<hc:TextBox Text="{Binding Area.WarehouseName}"
hc:InfoElement.Title="所属仓库"
hc:InfoElement.TitleWidth="80"
hc:InfoElement.TitlePlacement="Left"
IsReadOnly="True"
Margin="0,0,0,16"/>
</hc:Col>
<!-- 仓库分类(只读,由仓库带出) -->
<hc:Col Span="12">
<hc:TextBox Text="{Binding Area.WarehouseCategoryName}"
hc:InfoElement.Title="仓库分类"
hc:InfoElement.TitleWidth="80"
hc:InfoElement.TitlePlacement="Left"
IsReadOnly="True"
Margin="0,0,0,16"/>
</hc:Col>
<!-- 最大存放量 -->
<hc:Col Span="12">
<hc:NumericUpDown Value="{Binding Area.MaxCapacity}"
Minimum="0"
DecimalPlaces="0"
Style="{StaticResource NumericUpDownPlus}"
hc:InfoElement.Title="最大存放量"
hc:InfoElement.TitleWidth="80"
hc:InfoElement.TitlePlacement="Left"
hc:InfoElement.Placeholder="请输入最大存放量"
hc:InfoElement.ShowClearButton="True"
Margin="0,0,0,16"/>
</hc:Col>
<!-- 实际存放量 -->
<hc:Col Span="12">
<hc:NumericUpDown Value="{Binding Area.ActualCapacity}"
Minimum="0"
DecimalPlaces="0"
Style="{StaticResource NumericUpDownPlus}"
hc:InfoElement.Title="实际存放量"
hc:InfoElement.TitleWidth="80"
hc:InfoElement.TitlePlacement="Left"
hc:InfoElement.Placeholder="请输入实际存放量"
hc:InfoElement.ShowClearButton="True"
Margin="0,0,0,16"/>
</hc:Col>
<!-- 状态 -->
<hc:Col Span="12">
<hc:ComboBox hc:InfoElement.Title="状态"
hc:InfoElement.TitleWidth="80"
hc:InfoElement.TitlePlacement="Left"
SelectedValue="{Binding Area.Status}"
SelectedValuePath="Tag"
Margin="0,0,0,16">
<ComboBoxItem Content="启用" Tag="0"/>
<ComboBoxItem Content="停用" Tag="1"/>
</hc:ComboBox>
</hc:Col>
<!-- 备注 -->
<hc:Col Span="24">
<hc:TextBox Text="{Binding Area.Remark, UpdateSourceTrigger=PropertyChanged}"
hc:InfoElement.Title="备注"
hc:InfoElement.TitleWidth="80"
hc:InfoElement.TitlePlacement="Left"
hc:InfoElement.Placeholder="请输入备注"
hc:InfoElement.ShowClearButton="True"
TextWrapping="Wrap"
AcceptsReturn="True"
Height="80"
VerticalScrollBarVisibility="Auto"
Margin="0,0,0,16"/>
</hc:Col>
</hc:Row>
</StackPanel>
</hc:ScrollViewer>
<!-- 按钮区 -->
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="20">
<Button Content="取消" Command="{Binding CancelCommand}" Style="{StaticResource ButtonDefault}" Margin="0,0,15,0" Width="100"/>
<Button Content="确定" Command="{Binding SaveCommand}" Style="{StaticResource ButtonPrimary}" Width="100"/>
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,11 @@
using System.Windows.Controls;
namespace YY.Admin.Views.WarehouseArea;
public partial class WarehouseAreaEditDialogView : UserControl
{
public WarehouseAreaEditDialogView()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,159 @@
<UserControl x:Class="YY.Admin.Views.WarehouseArea.WarehouseAreaListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:hc="https://handyorg.github.io/handycontrol"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d">
<Grid Style="{StaticResource BaseViewStyle}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 搜索条件 -->
<Border Grid.Row="0" CornerRadius="4" Margin="0 0 -10 0">
<hc:Row>
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=8, Md=6, Lg=6, Xl=4}">
<hc:TextBox Text="{Binding FilterAreaCode, UpdateSourceTrigger=PropertyChanged}"
Margin="0 0 10 10"
hc:InfoElement.Title="库区编码"
hc:InfoElement.TitlePlacement="Left"
hc:InfoElement.TitleWidth="65"
hc:InfoElement.Placeholder="请输入库区编码"
hc:InfoElement.ShowClearButton="True"/>
</hc:Col>
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=8, Md=6, Lg=6, Xl=4}">
<hc:TextBox Text="{Binding FilterAreaName, UpdateSourceTrigger=PropertyChanged}"
Margin="0 0 10 10"
hc:InfoElement.Title="库区名称"
hc:InfoElement.TitlePlacement="Left"
hc:InfoElement.TitleWidth="65"
hc:InfoElement.Placeholder="请输入库区名称"
hc:InfoElement.ShowClearButton="True"/>
</hc:Col>
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=8, Md=6, Lg=6, Xl=4}">
<hc:ComboBox SelectedValuePath="Value"
DisplayMemberPath="Key"
ItemsSource="{Binding WarehouseOptions}"
SelectedValue="{Binding FilterWarehouseId}"
Margin="0 0 10 10"
hc:InfoElement.Title="所属仓库"
hc:InfoElement.TitlePlacement="Left"
hc:InfoElement.TitleWidth="65"
hc:InfoElement.Placeholder="请选择仓库"
hc:InfoElement.ShowClearButton="True"/>
</hc:Col>
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=8, Md=6, Lg=6, Xl=4}">
<hc:ComboBox SelectedValuePath="Value"
DisplayMemberPath="Key"
ItemsSource="{Binding StatusOptions}"
SelectedValue="{Binding FilterStatus}"
Margin="0 0 10 10"
hc:InfoElement.Title="状态"
hc:InfoElement.TitlePlacement="Left"
hc:InfoElement.TitleWidth="65"
hc:InfoElement.Placeholder="请选择状态"
hc:InfoElement.ShowClearButton="True"/>
</hc:Col>
</hc:Row>
</Border>
<!-- 工具栏 -->
<Border Grid.Row="1" Margin="0,10">
<hc:UniformSpacingPanel Spacing="10">
<Button Style="{StaticResource ButtonPrimary}" Command="{Binding SearchCommand}">
<StackPanel Orientation="Horizontal">
<md:PackIcon Kind="Search"/>
<TextBlock Text="搜索" Style="{StaticResource IconButtonStyle}"/>
</StackPanel>
</Button>
<Button Style="{StaticResource ButtonDefault}" Command="{Binding ResetCommand}">
<StackPanel Orientation="Horizontal">
<md:PackIcon Kind="Refresh"/>
<TextBlock Text="重置" Style="{StaticResource IconButtonStyle}"/>
</StackPanel>
</Button>
<Button Style="{StaticResource ButtonPrimary}" Command="{Binding AddCommand}">
<StackPanel Orientation="Horizontal">
<md:PackIcon Kind="Plus"/>
<TextBlock Text="新增" Style="{StaticResource IconButtonStyle}"/>
</StackPanel>
</Button>
</hc:UniformSpacingPanel>
</Border>
<!-- 数据表格 -->
<DataGrid Grid.Row="2"
ItemsSource="{Binding Areas}"
AutoGenerateColumns="False"
IsReadOnly="True"
CanUserAddRows="False"
SelectionMode="Extended"
SelectionUnit="FullRow"
RowHeaderWidth="55"
GridLinesVisibility="Horizontal"
HorizontalGridLinesBrush="#FFEDEDED"
VerticalGridLinesBrush="Transparent"
HeadersVisibility="All"
ColumnHeaderStyle="{StaticResource CusDataGridColumnHeaderStyle}"
Style="{StaticResource CusDataGridStyle}"
hc:DataGridAttach.ShowSelectAllButton="True"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
<DataGrid.RowHeaderTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=DataGridRow}}"/>
</DataTemplate>
</DataGrid.RowHeaderTemplate>
<DataGrid.Columns>
<DataGridTextColumn Header="库区编码" Binding="{Binding AreaCode}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="120"/>
<DataGridTextColumn Header="库区名称" Binding="{Binding AreaName}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="160"/>
<DataGridTextColumn Header="所属仓库" Binding="{Binding WarehouseName}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="160"/>
<DataGridTextColumn Header="仓库分类" Binding="{Binding WarehouseCategoryName}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="120"/>
<DataGridTextColumn Header="最大存放量" Binding="{Binding MaxCapacity}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="100"/>
<DataGridTextColumn Header="实际存放量" Binding="{Binding ActualCapacity}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="100"/>
<DataGridTextColumn Header="备注" Binding="{Binding Remark}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="180"/>
<DataGridTextColumn Header="状态" Binding="{Binding StatusText}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="70"/>
<DataGridTextColumn Header="创建时间" Binding="{Binding CreateTime, StringFormat=yyyy-MM-dd HH:mm}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="140"/>
<DataGridTemplateColumn Header="操作" Width="160" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<hc:UniformSpacingPanel Spacing="6">
<Button Content="编辑" Style="{StaticResource ButtonInfo}"
Command="{Binding DataContext.EditCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
CommandParameter="{Binding}"
Padding="8,2"/>
<Button Style="{StaticResource ButtonWarning}"
Content="{Binding StatusText}"
Command="{Binding DataContext.ToggleStatusCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
CommandParameter="{Binding}"
Padding="8,2"/>
<Button Content="删除" Style="{StaticResource ButtonDanger}"
Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
CommandParameter="{Binding}"
Padding="8,2"/>
</hc:UniformSpacingPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<!-- 分页 -->
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0">
<TextBlock Text="{Binding Total, StringFormat=共 {0} 条}" VerticalAlignment="Center" Margin="0,0,16,0"
Foreground="{DynamicResource SecondaryTextBrush}"/>
<Button Content="上一页" Command="{Binding PrevPageCommand}" Style="{StaticResource ButtonDefault}" Margin="0,0,4,0" Width="80"/>
<TextBlock Text="{Binding PageNo, StringFormat=第 {0} 页}" VerticalAlignment="Center" Margin="8,0"
Foreground="{DynamicResource PrimaryTextBrush}"/>
<Button Content="下一页" Command="{Binding NextPageCommand}" Style="{StaticResource ButtonDefault}" Width="80"/>
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,11 @@
using System.Windows.Controls;
namespace YY.Admin.Views.WarehouseArea;
public partial class WarehouseAreaListView : UserControl
{
public WarehouseAreaListView()
{
InitializeComponent();
}
}

View File

@@ -147,6 +147,7 @@
<DataGridTextColumn Header="毛重(KG)" Binding="{Binding GrossWeight, StringFormat=N2}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="100"/>
<DataGridTextColumn Header="皮重(KG)" Binding="{Binding TareWeight, StringFormat=N2}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="100"/>
<DataGridTextColumn Header="净重(KG)" Binding="{Binding NetWeight, StringFormat=N2}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="100"/>
<DataGridTextColumn Header="已入场重量(KG)" Binding="{Binding EnteredWeight, StringFormat=N2}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="130"/>
<DataGridTextColumn Header="司机" Binding="{Binding DriverName}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="90"/>
<DataGridTextColumn Header="手机号" Binding="{Binding DriverPhone}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="120"/>
<DataGridTextColumn Header="创建时间" Binding="{Binding CreateTime, StringFormat='yyyy-MM-dd HH:mm'}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="130"/>