Merge branch 'main' of http://27.223.88.102:33000/chenx/qhmes
This commit is contained in:
@@ -43,6 +43,7 @@ import org.jeecg.modules.xslmes.service.MesXslStompNotifyService;
|
|||||||
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialCardBriefVO;
|
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialCardBriefVO;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -405,12 +406,7 @@ public class MesXslDesktopAnonController {
|
|||||||
if (oConvertUtils.isEmpty(mesXslWeightRecord.getPlateNumber())) {
|
if (oConvertUtils.isEmpty(mesXslWeightRecord.getPlateNumber())) {
|
||||||
return Result.error("车牌号不能为空");
|
return Result.error("车牌号不能为空");
|
||||||
}
|
}
|
||||||
// 净重自动计算
|
applyWeightNetAndBillType(mesXslWeightRecord);
|
||||||
if (mesXslWeightRecord.getGrossWeight() != null && mesXslWeightRecord.getTareWeight() != null) {
|
|
||||||
mesXslWeightRecord.setNetWeight(
|
|
||||||
mesXslWeightRecord.getGrossWeight().subtract(mesXslWeightRecord.getTareWeight()));
|
|
||||||
}
|
|
||||||
applyWeightBillType(mesXslWeightRecord);
|
|
||||||
applyMaterialTypeDefault(mesXslWeightRecord);
|
applyMaterialTypeDefault(mesXslWeightRecord);
|
||||||
weightRecordService.save(mesXslWeightRecord);
|
weightRecordService.save(mesXslWeightRecord);
|
||||||
stompNotify.publishWeightRecordChanged("add", mesXslWeightRecord.getId());
|
stompNotify.publishWeightRecordChanged("add", mesXslWeightRecord.getId());
|
||||||
@@ -423,12 +419,7 @@ public class MesXslDesktopAnonController {
|
|||||||
if (oConvertUtils.isEmpty(mesXslWeightRecord.getId())) {
|
if (oConvertUtils.isEmpty(mesXslWeightRecord.getId())) {
|
||||||
return Result.error("主键不能为空");
|
return Result.error("主键不能为空");
|
||||||
}
|
}
|
||||||
// 净重自动计算
|
applyWeightNetAndBillType(mesXslWeightRecord);
|
||||||
if (mesXslWeightRecord.getGrossWeight() != null && mesXslWeightRecord.getTareWeight() != null) {
|
|
||||||
mesXslWeightRecord.setNetWeight(
|
|
||||||
mesXslWeightRecord.getGrossWeight().subtract(mesXslWeightRecord.getTareWeight()));
|
|
||||||
}
|
|
||||||
applyWeightBillType(mesXslWeightRecord);
|
|
||||||
applyMaterialTypeDefault(mesXslWeightRecord);
|
applyMaterialTypeDefault(mesXslWeightRecord);
|
||||||
boolean ok = weightRecordService.updateById(mesXslWeightRecord);
|
boolean ok = weightRecordService.updateById(mesXslWeightRecord);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
@@ -839,18 +830,47 @@ public class MesXslDesktopAnonController {
|
|||||||
|
|
||||||
// ─────────────────────────── 车辆私有辅助 ────────────────────────────
|
// ─────────────────────────── 车辆私有辅助 ────────────────────────────
|
||||||
|
|
||||||
|
private void applyWeightNetAndBillType(MesXslWeightRecord record) {
|
||||||
|
sanitizeNonPositiveWeightsToNull(record);
|
||||||
|
if (isEffectiveWeight(record.getGrossWeight()) && isEffectiveWeight(record.getTareWeight())) {
|
||||||
|
BigDecimal net = record.getGrossWeight().subtract(record.getTareWeight());
|
||||||
|
record.setNetWeight(net.compareTo(BigDecimal.ZERO) >= 0 ? net : BigDecimal.ZERO);
|
||||||
|
} else {
|
||||||
|
record.setNetWeight(null);
|
||||||
|
}
|
||||||
|
applyWeightBillType(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将 ≤0 的重量视为未录入,避免 JSON/Numeric 占位 0 被当成已称皮重、误判称重完成 */
|
||||||
|
private static void sanitizeNonPositiveWeightsToNull(MesXslWeightRecord record) {
|
||||||
|
if (record.getGrossWeight() != null && record.getGrossWeight().compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
record.setGrossWeight(null);
|
||||||
|
}
|
||||||
|
if (record.getTareWeight() != null && record.getTareWeight().compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
record.setTareWeight(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isEffectiveWeight(BigDecimal w) {
|
||||||
|
return w != null && w.compareTo(BigDecimal.ZERO) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
private void applyWeightBillType(MesXslWeightRecord record) {
|
private void applyWeightBillType(MesXslWeightRecord record) {
|
||||||
if (record.getGrossWeight() != null && record.getTareWeight() != null) {
|
boolean g = isEffectiveWeight(record.getGrossWeight());
|
||||||
|
boolean t = isEffectiveWeight(record.getTareWeight());
|
||||||
|
if (g && t) {
|
||||||
record.setBillType("2");
|
record.setBillType("2");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (record.getGrossWeight() != null) {
|
if (g) {
|
||||||
record.setBillType("1");
|
record.setBillType("1");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (record.getTareWeight() != null) {
|
if (t) {
|
||||||
record.setBillType("3");
|
record.setBillType("3");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
record.setBillType(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyMaterialTypeDefault(MesXslWeightRecord record) {
|
private void applyMaterialTypeDefault(MesXslWeightRecord record) {
|
||||||
|
|||||||
@@ -72,9 +72,9 @@ public class MesXslWeightRecordController extends JeecgController<MesXslWeightRe
|
|||||||
String seq = String.format("%03d", new Random().nextInt(1000));
|
String seq = String.format("%03d", new Random().nextInt(1000));
|
||||||
mesXslWeightRecord.setBillNo("BDH-" + dateStr + seq);
|
mesXslWeightRecord.setBillNo("BDH-" + dateStr + seq);
|
||||||
}
|
}
|
||||||
computeBillType(mesXslWeightRecord);
|
|
||||||
applyMaterialTypeDefault(mesXslWeightRecord);
|
applyMaterialTypeDefault(mesXslWeightRecord);
|
||||||
computeNetWeight(mesXslWeightRecord);
|
computeNetWeight(mesXslWeightRecord);
|
||||||
|
computeBillType(mesXslWeightRecord);
|
||||||
mesXslWeightRecordService.save(mesXslWeightRecord);
|
mesXslWeightRecordService.save(mesXslWeightRecord);
|
||||||
stompNotifyService.publishWeightRecordChanged("add", mesXslWeightRecord.getId());
|
stompNotifyService.publishWeightRecordChanged("add", mesXslWeightRecord.getId());
|
||||||
return Result.OK("添加成功!");
|
return Result.OK("添加成功!");
|
||||||
@@ -85,9 +85,9 @@ public class MesXslWeightRecordController extends JeecgController<MesXslWeightRe
|
|||||||
@RequiresPermissions("xslmes:mes_xsl_weight_record:edit")
|
@RequiresPermissions("xslmes:mes_xsl_weight_record:edit")
|
||||||
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
|
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
|
||||||
public Result<String> edit(@RequestBody MesXslWeightRecord mesXslWeightRecord) {
|
public Result<String> edit(@RequestBody MesXslWeightRecord mesXslWeightRecord) {
|
||||||
computeBillType(mesXslWeightRecord);
|
|
||||||
applyMaterialTypeDefault(mesXslWeightRecord);
|
applyMaterialTypeDefault(mesXslWeightRecord);
|
||||||
computeNetWeight(mesXslWeightRecord);
|
computeNetWeight(mesXslWeightRecord);
|
||||||
|
computeBillType(mesXslWeightRecord);
|
||||||
mesXslWeightRecordService.updateById(mesXslWeightRecord);
|
mesXslWeightRecordService.updateById(mesXslWeightRecord);
|
||||||
stompNotifyService.publishWeightRecordChanged("edit", mesXslWeightRecord.getId());
|
stompNotifyService.publishWeightRecordChanged("edit", mesXslWeightRecord.getId());
|
||||||
return Result.OK("编辑成功!");
|
return Result.OK("编辑成功!");
|
||||||
@@ -137,26 +137,47 @@ public class MesXslWeightRecordController extends JeecgController<MesXslWeightRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void computeNetWeight(MesXslWeightRecord record) {
|
private void computeNetWeight(MesXslWeightRecord record) {
|
||||||
|
sanitizeNonPositiveWeightsToNull(record);
|
||||||
BigDecimal gross = record.getGrossWeight();
|
BigDecimal gross = record.getGrossWeight();
|
||||||
BigDecimal tare = record.getTareWeight();
|
BigDecimal tare = record.getTareWeight();
|
||||||
if (gross != null && tare != null) {
|
if (isEffectiveWeight(gross) && isEffectiveWeight(tare)) {
|
||||||
BigDecimal net = gross.subtract(tare);
|
BigDecimal net = gross.subtract(tare);
|
||||||
record.setNetWeight(net.compareTo(BigDecimal.ZERO) >= 0 ? net : BigDecimal.ZERO);
|
record.setNetWeight(net.compareTo(BigDecimal.ZERO) >= 0 ? net : BigDecimal.ZERO);
|
||||||
|
} else {
|
||||||
|
record.setNetWeight(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void computeBillType(MesXslWeightRecord record) {
|
private void computeBillType(MesXslWeightRecord record) {
|
||||||
if (record.getGrossWeight() != null && record.getTareWeight() != null) {
|
boolean g = isEffectiveWeight(record.getGrossWeight());
|
||||||
|
boolean t = isEffectiveWeight(record.getTareWeight());
|
||||||
|
if (g && t) {
|
||||||
record.setBillType("2");
|
record.setBillType("2");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (record.getGrossWeight() != null) {
|
if (g) {
|
||||||
record.setBillType("1");
|
record.setBillType("1");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (record.getTareWeight() != null) {
|
if (t) {
|
||||||
record.setBillType("3");
|
record.setBillType("3");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
record.setBillType(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将 ≤0 的重量视为未录入,避免占位 0 误判称重完成 */
|
||||||
|
private static void sanitizeNonPositiveWeightsToNull(MesXslWeightRecord record) {
|
||||||
|
if (record.getGrossWeight() != null && record.getGrossWeight().compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
record.setGrossWeight(null);
|
||||||
|
}
|
||||||
|
if (record.getTareWeight() != null && record.getTareWeight().compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
record.setTareWeight(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isEffectiveWeight(BigDecimal w) {
|
||||||
|
return w != null && w.compareTo(BigDecimal.ZERO) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyMaterialTypeDefault(MesXslWeightRecord record) {
|
private void applyMaterialTypeDefault(MesXslWeightRecord record) {
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ namespace YY.Admin.Core.Services;
|
|||||||
|
|
||||||
public interface IWarehouseService
|
public interface IWarehouseService
|
||||||
{
|
{
|
||||||
/// <summary>获取全部仓库列表(在线时拉取远端并刷新缓存,离线时返回本地缓存)</summary>
|
/// <summary>获取全部仓库列表(优先内存/磁盘缓存;仅缓存为空且在线时拉取远端)</summary>
|
||||||
Task<List<MesXslWarehouse>> GetAllAsync(CancellationToken ct = default);
|
Task<List<MesXslWarehouse>> GetAllAsync(CancellationToken ct = default);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,12 @@ public partial class SysMenu : EntityBase
|
|||||||
[SugarColumn(ColumnDescription = "是否固定")]
|
[SugarColumn(ColumnDescription = "是否固定")]
|
||||||
public bool IsAffix { get; set; }
|
public bool IsAffix { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 桌面端登录后默认打开的首页(全库仅一项建议为 true,保存时由服务层互斥)
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnDescription = "桌面端默认首页")]
|
||||||
|
public bool IsDefaultDesktopHome { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 排序
|
/// 排序
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
|
|||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
new SysMenu{ Id=1300100000101, Pid=0, Title="工作台", Path="", Name="dashboard", Component="Layout", Icon="", Type=MenuTypeEnum.Dir, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=0 },
|
new SysMenu{ Id=1300100000101, Pid=0, Title="工作台", Path="", Name="dashboard", Component="Layout", Icon="", Type=MenuTypeEnum.Dir, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=0 },
|
||||||
new SysMenu{ Id=1300100010101, Pid=1300100000101, Title="仪表盘", Path="DashboardView", Name="home", Component="/home/index", IsAffix=true, Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
|
new SysMenu{ Id=1300100010101, Pid=1300100000101, Title="仪表盘", Path="DashboardView", Name="home", Component="/home/index", IsAffix=true, IsDefaultDesktopHome=true, Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
|
||||||
new SysMenu{ Id=1300100010201, Pid=1300100000101, Title="站内信", Path="/dashboard/notice", Name="notice", Component="/home/notice/index", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=101 },
|
new SysMenu{ Id=1300100010201, Pid=1300100000101, Title="站内信", Path="/dashboard/notice", Name="notice", Component="/home/notice/index", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=101 },
|
||||||
|
|
||||||
// 建议此处Id范围之间放置具体业务应用菜单
|
// 建议此处Id范围之间放置具体业务应用菜单
|
||||||
|
|||||||
@@ -251,6 +251,8 @@ namespace YY.Admin.Core.SqlSugar
|
|||||||
|
|
||||||
// 兼容旧库:曾关闭表初始化或升级前库无 Jeecg 盐列时补齐,避免脱网按 Jeecg 规则验密时读列失败
|
// 兼容旧库:曾关闭表初始化或升级前库无 Jeecg 盐列时补齐,避免脱网按 Jeecg 规则验密时读列失败
|
||||||
EnsureSysUserJeecgPasswordSaltColumn(dbProvider, config);
|
EnsureSysUserJeecgPasswordSaltColumn(dbProvider, config);
|
||||||
|
// 兼容旧库:菜单表增加「桌面默认首页」标记列
|
||||||
|
EnsureSysMenuDefaultDesktopHomeColumn(dbProvider, config);
|
||||||
// 兜底:确保 Jeecg 用户同构表存在(登录页“同步 Jeecg 用户”写入此表)
|
// 兜底:确保 Jeecg 用户同构表存在(登录页“同步 Jeecg 用户”写入此表)
|
||||||
EnsureJeecgSysUserMirrorTable(dbProvider, config);
|
EnsureJeecgSysUserMirrorTable(dbProvider, config);
|
||||||
// 兜底:确保 Jeecg 数据字典同构表存在(数据字典页面“同步数据字典”写入此表)
|
// 兜底:确保 Jeecg 数据字典同构表存在(数据字典页面“同步数据字典”写入此表)
|
||||||
@@ -352,8 +354,57 @@ namespace YY.Admin.Core.SqlSugar
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 若 sys_user 缺少 jeecg_password_salt,则执行 ALTER 补列(与实体 SysUser.JeecgPasswordSalt 一致)
|
/// 若 sys_menu 缺少桌面默认首页标记列,则 ALTER 补齐(与实体 SysMenu.IsDefaultDesktopHome 一致)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
private static void EnsureSysMenuDefaultDesktopHomeColumn(SqlSugarScopeProvider dbProvider, DbConnectionConfig config)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var entityInfo = dbProvider.EntityMaintenance.GetEntityInfo(typeof(SysMenu));
|
||||||
|
var tableName = entityInfo.DbTableName;
|
||||||
|
if (!dbProvider.DbMaintenance.IsAnyTable(tableName, false))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var col = entityInfo.Columns.FirstOrDefault(c => c.PropertyName == nameof(SysMenu.IsDefaultDesktopHome));
|
||||||
|
if (col == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var columns = dbProvider.DbMaintenance.GetColumnInfosByTableName(tableName, false);
|
||||||
|
if (columns != null &&
|
||||||
|
columns.Any(c => string.Equals(c.DbColumnName, col.DbColumnName, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cName = col.DbColumnName;
|
||||||
|
var sql = config.DbType switch
|
||||||
|
{
|
||||||
|
DbType.Sqlite => $"ALTER TABLE {tableName} ADD COLUMN {cName} INTEGER NOT NULL DEFAULT 0;",
|
||||||
|
DbType.MySql => $"ALTER TABLE `{tableName}` ADD COLUMN `{cName}` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '桌面端默认首页';",
|
||||||
|
DbType.PostgreSQL => $"ALTER TABLE \"{tableName}\" ADD COLUMN IF NOT EXISTS {cName} BOOLEAN NOT NULL DEFAULT FALSE;",
|
||||||
|
DbType.SqlServer => $"ALTER TABLE [{tableName}] ADD [{cName}] BIT NOT NULL DEFAULT 0;",
|
||||||
|
DbType.Kdbndp => $"ALTER TABLE \"{tableName}\" ADD COLUMN IF NOT EXISTS {cName} BOOLEAN NOT NULL DEFAULT FALSE;",
|
||||||
|
DbType.Dm => $"ALTER TABLE {tableName} ADD {cName} NUMBER(1) DEFAULT 0 NOT NULL;",
|
||||||
|
_ => (string?)null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(sql))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Retry(() => dbProvider.Ado.ExecuteCommand(sql), maxRetry: 3, retryIntervalMs: 1000);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 无权限、从库只读等场景不阻断启动
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void EnsureSysUserJeecgPasswordSaltColumn(SqlSugarScopeProvider dbProvider, DbConnectionConfig config)
|
private static void EnsureSysUserJeecgPasswordSaltColumn(SqlSugarScopeProvider dbProvider, DbConnectionConfig config)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -121,6 +121,11 @@ namespace YY.Admin.Services
|
|||||||
/// 是否固定
|
/// 是否固定
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsAffix { get; set; }
|
public bool IsAffix { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 桌面端登录后作为默认首页打开的菜单
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDefaultDesktopHome { get; set; }
|
||||||
#endregion
|
#endregion
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 菜单子项
|
/// 菜单子项
|
||||||
|
|||||||
@@ -157,6 +157,9 @@ namespace YY.Admin.Services.Service.Menu
|
|||||||
if (n <= 0)
|
if (n <= 0)
|
||||||
return (false, "保存失败", 0);
|
return (false, "保存失败", 0);
|
||||||
|
|
||||||
|
if (input.IsDefaultDesktopHome)
|
||||||
|
await ClearDefaultDesktopHomeExceptAsync(input.Id);
|
||||||
|
|
||||||
await TryLinkCurrentTenantMenuAsync(id);
|
await TryLinkCurrentTenantMenuAsync(id);
|
||||||
return (true, "保存成功", id);
|
return (true, "保存成功", id);
|
||||||
}
|
}
|
||||||
@@ -180,6 +183,9 @@ namespace YY.Admin.Services.Service.Menu
|
|||||||
if (NewParentIsInsideMenuSubtree(input.Id, input.Pid, all))
|
if (NewParentIsInsideMenuSubtree(input.Id, input.Pid, all))
|
||||||
return (false, "不能将父级设为当前菜单或其子菜单");
|
return (false, "不能将父级设为当前菜单或其子菜单");
|
||||||
|
|
||||||
|
if (input.IsDefaultDesktopHome)
|
||||||
|
await ClearDefaultDesktopHomeExceptAsync(input.Id);
|
||||||
|
|
||||||
existing.Pid = input.Pid;
|
existing.Pid = input.Pid;
|
||||||
existing.Type = input.Type;
|
existing.Type = input.Type;
|
||||||
existing.Name = input.Name;
|
existing.Name = input.Name;
|
||||||
@@ -194,13 +200,17 @@ namespace YY.Admin.Services.Service.Menu
|
|||||||
existing.IsHide = input.IsHide;
|
existing.IsHide = input.IsHide;
|
||||||
existing.IsKeepAlive = input.IsKeepAlive;
|
existing.IsKeepAlive = input.IsKeepAlive;
|
||||||
existing.IsAffix = input.IsAffix;
|
existing.IsAffix = input.IsAffix;
|
||||||
|
existing.IsDefaultDesktopHome = input.IsDefaultDesktopHome;
|
||||||
existing.OrderNo = input.OrderNo;
|
existing.OrderNo = input.OrderNo;
|
||||||
existing.Status = input.Status;
|
existing.Status = input.Status;
|
||||||
existing.Remark = input.Remark;
|
existing.Remark = input.Remark;
|
||||||
existing.UpdateTime = DateTime.Now;
|
existing.UpdateTime = DateTime.Now;
|
||||||
|
|
||||||
var n = await _dbContext.Updateable(existing).ExecuteCommandAsync();
|
var n = await _dbContext.Updateable(existing).ExecuteCommandAsync();
|
||||||
return n > 0 ? (true, "保存成功") : (false, "更新失败");
|
if (n <= 0)
|
||||||
|
return (false, "更新失败");
|
||||||
|
await TryLinkCurrentTenantMenuAsync(input.Id);
|
||||||
|
return (true, "保存成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -219,6 +229,17 @@ namespace YY.Admin.Services.Service.Menu
|
|||||||
return n > 0 ? (true, "已删除") : (false, "删除失败");
|
return n > 0 ? (true, "已删除") : (false, "删除失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 保证全表仅一条菜单为桌面默认首页(当前保留项除外全部置 false)
|
||||||
|
/// </summary>
|
||||||
|
private async Task ClearDefaultDesktopHomeExceptAsync(long keepMenuId)
|
||||||
|
{
|
||||||
|
await _dbContext.Updateable<SysMenu>()
|
||||||
|
.SetColumns(m => new SysMenu { IsDefaultDesktopHome = false })
|
||||||
|
.Where(m => m.Id != keepMenuId && m.IsDefaultDesktopHome)
|
||||||
|
.ExecuteCommandAsync();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task TryLinkCurrentTenantMenuAsync(long menuId)
|
private async Task TryLinkCurrentTenantMenuAsync(long menuId)
|
||||||
{
|
{
|
||||||
var tenantId = AppSession.CurrentUser?.TenantId;
|
var tenantId = AppSession.CurrentUser?.TenantId;
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ using YY.Admin.Core.Services;
|
|||||||
namespace YY.Admin.Services.Service.Warehouse;
|
namespace YY.Admin.Services.Service.Warehouse;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 仓库数据只读服务:启动时从后端拉取全量列表并缓存到磁盘,断网时回退本地缓存。
|
/// 仓库数据只读服务:启动与重连时后台刷新远端写入缓存;GetAllAsync 优先读缓存,避免库区等业务每次打开都全量拉仓库。
|
||||||
/// 仅供其他模块的下拉筛选使用,不提供 CRUD。
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WarehouseService : IWarehouseService, ISingletonDependency
|
public class WarehouseService : IWarehouseService, ISingletonDependency
|
||||||
{
|
{
|
||||||
@@ -20,6 +19,7 @@ public class WarehouseService : IWarehouseService, ISingletonDependency
|
|||||||
private readonly INetworkMonitor _networkMonitor;
|
private readonly INetworkMonitor _networkMonitor;
|
||||||
private readonly ILoggerService _logger;
|
private readonly ILoggerService _logger;
|
||||||
private readonly object _cacheLock = new();
|
private readonly object _cacheLock = new();
|
||||||
|
private readonly SemaphoreSlim _emptyLoadGate = new(1, 1);
|
||||||
private readonly string _cacheFilePath;
|
private readonly string _cacheFilePath;
|
||||||
private List<MesXslWarehouse> _localCache = new();
|
private List<MesXslWarehouse> _localCache = new();
|
||||||
|
|
||||||
@@ -64,19 +64,44 @@ public class WarehouseService : IWarehouseService, ISingletonDependency
|
|||||||
|
|
||||||
public async Task<List<MesXslWarehouse>> GetAllAsync(CancellationToken ct = default)
|
public async Task<List<MesXslWarehouse>> GetAllAsync(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
if (_networkMonitor.IsOnline)
|
lock (_cacheLock)
|
||||||
{
|
{
|
||||||
|
if (_localCache.Count > 0 || !_networkMonitor.IsOnline)
|
||||||
|
return _localCache.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存为空且在线:仅此时阻塞拉远端(首次安装或清缓存后)
|
||||||
|
await _emptyLoadGate.WaitAsync(ct).ConfigureAwait(false);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock (_cacheLock)
|
||||||
|
{
|
||||||
|
if (_localCache.Count > 0)
|
||||||
|
return _localCache.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_networkMonitor.IsOnline)
|
||||||
|
{
|
||||||
|
lock (_cacheLock)
|
||||||
|
return _localCache.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await RefreshFromRemoteAsync(ct).ConfigureAwait(false);
|
await RefreshFromRemoteAsync(ct).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch { }
|
catch
|
||||||
|
{
|
||||||
|
/* 登录前拉取失败等场景,仍返回当前缓存(可能仍为空) */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_emptyLoadGate.Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (_cacheLock)
|
lock (_cacheLock)
|
||||||
{
|
|
||||||
return _localCache.ToList();
|
return _localCache.ToList();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RefreshFromRemoteAsync(CancellationToken ct)
|
private async Task RefreshFromRemoteAsync(CancellationToken ct)
|
||||||
|
|||||||
@@ -197,12 +197,12 @@ public class WeightRecordService : IWeightRecordService, ISingletonDependency
|
|||||||
|
|
||||||
public async Task<bool> AddAsync(MesXslWeightRecord entity, CancellationToken ct = default)
|
public async Task<bool> AddAsync(MesXslWeightRecord entity, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
SanitizeWeightsBeforePersist(entity);
|
||||||
if (!entity.TenantId.HasValue || entity.TenantId.Value <= 0)
|
if (!entity.TenantId.HasValue || entity.TenantId.Value <= 0)
|
||||||
entity.TenantId = DefaultTenantId;
|
entity.TenantId = DefaultTenantId;
|
||||||
if (string.IsNullOrWhiteSpace(entity.BillNo))
|
if (string.IsNullOrWhiteSpace(entity.BillNo))
|
||||||
entity.BillNo = GenerateBillNo();
|
entity.BillNo = GenerateBillNo();
|
||||||
if (string.IsNullOrWhiteSpace(entity.BillType))
|
entity.BillType = ResolveBillType(entity);
|
||||||
entity.BillType = ResolveBillType(entity);
|
|
||||||
|
|
||||||
var local = Clone(entity);
|
var local = Clone(entity);
|
||||||
if (string.IsNullOrWhiteSpace(local.Id))
|
if (string.IsNullOrWhiteSpace(local.Id))
|
||||||
@@ -246,21 +246,41 @@ public class WeightRecordService : IWeightRecordService, ISingletonDependency
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 根据称重数据推导单据类型:仅毛重=已称毛重;仅皮重=已称皮重;毛重+皮重=称重完成。
|
/// 根据称重数据推导单据类型:仅毛重=已称毛重;仅皮重=已称皮重;毛重+皮重=称重完成。
|
||||||
|
/// 注意:皮重/毛重为 0 时按「未称」处理(避免 NumericUpDown/JSON 占位 0 误判为称重完成)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static string? ResolveBillType(MesXslWeightRecord entity)
|
private static string? ResolveBillType(MesXslWeightRecord entity)
|
||||||
{
|
{
|
||||||
if (entity.GrossWeight.HasValue && entity.TareWeight.HasValue) return "2";
|
var g = HasEffectiveWeighKg(entity.GrossWeight);
|
||||||
if (entity.GrossWeight.HasValue) return "1";
|
var t = HasEffectiveWeighKg(entity.TareWeight);
|
||||||
if (entity.TareWeight.HasValue) return "3";
|
if (g && t) return "2";
|
||||||
|
if (g) return "1";
|
||||||
|
if (t) return "3";
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool HasEffectiveWeighKg(double? kg) => kg.HasValue && kg.Value > 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将 ≤0 的重量视为未录入并清空净重,防止上传 payload 带 0 误判。
|
||||||
|
/// </summary>
|
||||||
|
private static void SanitizeWeightsBeforePersist(MesXslWeightRecord entity)
|
||||||
|
{
|
||||||
|
if (entity.GrossWeight.HasValue && entity.GrossWeight.Value <= 0)
|
||||||
|
entity.GrossWeight = null;
|
||||||
|
if (entity.TareWeight.HasValue && entity.TareWeight.Value <= 0)
|
||||||
|
entity.TareWeight = null;
|
||||||
|
if (HasEffectiveWeighKg(entity.GrossWeight) && HasEffectiveWeighKg(entity.TareWeight))
|
||||||
|
entity.NetWeight = Math.Round(entity.GrossWeight!.Value - entity.TareWeight!.Value, 2);
|
||||||
|
else
|
||||||
|
entity.NetWeight = null;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> EditAsync(MesXslWeightRecord entity, CancellationToken ct = default)
|
public async Task<bool> EditAsync(MesXslWeightRecord entity, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
SanitizeWeightsBeforePersist(entity);
|
||||||
if (!entity.TenantId.HasValue || entity.TenantId.Value <= 0)
|
if (!entity.TenantId.HasValue || entity.TenantId.Value <= 0)
|
||||||
entity.TenantId = DefaultTenantId;
|
entity.TenantId = DefaultTenantId;
|
||||||
if (string.IsNullOrWhiteSpace(entity.BillType))
|
entity.BillType = ResolveBillType(entity);
|
||||||
entity.BillType = ResolveBillType(entity);
|
|
||||||
|
|
||||||
var local = Clone(entity);
|
var local = Clone(entity);
|
||||||
if (IsLocalTempId(local.Id))
|
if (IsLocalTempId(local.Id))
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ namespace YY.Admin.ViewModels.Control
|
|||||||
public MenuItem? Parent { get; set; } // 父节点引用
|
public MenuItem? Parent { get; set; } // 父节点引用
|
||||||
public ObservableCollection<MenuItem> Children { get; set; } = [];
|
public ObservableCollection<MenuItem> Children { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否为桌面端配置的默认首页
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDefaultDesktopHome { get; set; }
|
||||||
|
|
||||||
|
|
||||||
private bool _isExpanded;
|
private bool _isExpanded;
|
||||||
|
|
||||||
@@ -300,6 +305,7 @@ namespace YY.Admin.ViewModels.Control
|
|||||||
target.Icon = ConvertHtmlEntityToUnicode(source?.Icon ?? "");
|
target.Icon = ConvertHtmlEntityToUnicode(source?.Icon ?? "");
|
||||||
target.ViewName = ResolveViewName(source); // 将菜单路由映射到已注册的WPF视图
|
target.ViewName = ResolveViewName(source); // 将菜单路由映射到已注册的WPF视图
|
||||||
target.Parent = parent; // 设置父节点
|
target.Parent = parent; // 设置父节点
|
||||||
|
target.IsDefaultDesktopHome = source?.IsDefaultDesktopHome == true;
|
||||||
|
|
||||||
// 添加子菜单(如果有)
|
// 添加子菜单(如果有)
|
||||||
if (source.Children != null && source.Children.Any())
|
if (source.Children != null && source.Children.Any())
|
||||||
@@ -389,8 +395,9 @@ namespace YY.Admin.ViewModels.Control
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 默认菜单
|
// 优先使用菜单管理中勾选的「默认首页」,否则取排序后的第一个叶子菜单
|
||||||
var defaultMenuItem = GetFirstLeaf(MenuItems.FirstOrDefault());
|
var defaultMenuItem = FindDefaultHomeLeaf(MenuItems)
|
||||||
|
?? GetFirstLeaf(MenuItems.FirstOrDefault());
|
||||||
if (defaultMenuItem != null)
|
if (defaultMenuItem != null)
|
||||||
{
|
{
|
||||||
// Tab不允许关闭
|
// Tab不允许关闭
|
||||||
@@ -419,6 +426,31 @@ namespace YY.Admin.ViewModels.Control
|
|||||||
return GetFirstLeaf(menu.Children.FirstOrDefault());
|
return GetFirstLeaf(menu.Children.FirstOrDefault());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 按先序遍历查找第一个标记为默认首页的叶子节点(与左侧树展示顺序一致)
|
||||||
|
/// </summary>
|
||||||
|
private static MenuItem? FindDefaultHomeLeaf(IEnumerable<MenuItem>? nodes)
|
||||||
|
{
|
||||||
|
if (nodes == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
foreach (var m in nodes)
|
||||||
|
{
|
||||||
|
if (m.Children is not { Count: > 0 })
|
||||||
|
{
|
||||||
|
if (m.IsDefaultDesktopHome)
|
||||||
|
return m;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sub = FindDefaultHomeLeaf(m.Children);
|
||||||
|
if (sub != null)
|
||||||
|
return sub;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public void ToggleParents(MenuItem? item, bool IsExpanded)
|
public void ToggleParents(MenuItem? item, bool IsExpanded)
|
||||||
{
|
{
|
||||||
if (item == null)
|
if (item == null)
|
||||||
|
|||||||
@@ -306,16 +306,7 @@ namespace YY.Admin.ViewModels
|
|||||||
ViewName = "MenuTreeView",
|
ViewName = "MenuTreeView",
|
||||||
Command = new DelegateCommand<NavItem>(it => _ = NavigateToViewAsync(CommonConst.MenuRegion, it.ViewName))
|
Command = new DelegateCommand<NavItem>(it => _ = NavigateToViewAsync(CommonConst.MenuRegion, it.ViewName))
|
||||||
},
|
},
|
||||||
new NavItem {
|
// 已隐藏开发调试用「菜单区域」「Tab区域」,日常仅保留「功能菜单」
|
||||||
Icon = "GamepadVariantOutline",
|
|
||||||
Name = "菜单区域",
|
|
||||||
Command = new DelegateCommand<NavItem>(it => _ = NavigateToViewAsync(CommonConst.MenuRegion, it.ViewName))
|
|
||||||
},
|
|
||||||
new NavItem {
|
|
||||||
Icon = "FoodAppleOutline",
|
|
||||||
Name = "Tab区域",
|
|
||||||
Command = new DelegateCommand<NavItem>(OnOpenOrActivateTab)
|
|
||||||
},
|
|
||||||
(_menuTreeToggleNavItem = new NavItem {
|
(_menuTreeToggleNavItem = new NavItem {
|
||||||
Icon = "ChevronDoubleLeft",
|
Icon = "ChevronDoubleLeft",
|
||||||
Name = "折叠菜单",
|
Name = "折叠菜单",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
using HandyControl.Controls;
|
using HandyControl.Controls;
|
||||||
using Prism.Commands;
|
using Prism.Commands;
|
||||||
using Prism.Mvvm;
|
using Prism.Mvvm;
|
||||||
@@ -11,21 +12,28 @@ using YY.Admin.Services.Service.Menu;
|
|||||||
namespace YY.Admin.ViewModels.SysManage;
|
namespace YY.Admin.ViewModels.SysManage;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 左侧列表一行(树形扁平展示)
|
/// 左侧列表一行(树形扁平展示,可见行受折叠状态控制)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class MenuFlatRow
|
public sealed class MenuFlatRow
|
||||||
{
|
{
|
||||||
public SysMenu Menu { get; }
|
public SysMenu Menu { get; }
|
||||||
public int Depth { get; }
|
public int Depth { get; }
|
||||||
public string IndentTitle { get; }
|
/// <summary>是否存在子节点(用于显示展开按钮)</summary>
|
||||||
|
public bool HasChildren { get; }
|
||||||
|
/// <summary>子节点当前是否展开</summary>
|
||||||
|
public bool IsExpanded { get; }
|
||||||
|
public Thickness LeadingMargin => new(Depth * 14, 0, 0, 0);
|
||||||
|
public string TitleText { get; }
|
||||||
|
|
||||||
public MenuFlatRow(SysMenu menu, int depth)
|
public MenuFlatRow(SysMenu menu, int depth, bool hasChildren, bool isExpanded)
|
||||||
{
|
{
|
||||||
Menu = menu;
|
Menu = menu;
|
||||||
Depth = depth;
|
Depth = depth;
|
||||||
var pad = new string(' ', depth);
|
HasChildren = hasChildren;
|
||||||
|
IsExpanded = isExpanded;
|
||||||
var tag = menu.Type == MenuTypeEnum.Dir ? "[目录] " : menu.Type == MenuTypeEnum.Btn ? "[按钮] " : "[菜单] ";
|
var tag = menu.Type == MenuTypeEnum.Dir ? "[目录] " : menu.Type == MenuTypeEnum.Btn ? "[按钮] " : "[菜单] ";
|
||||||
IndentTitle = pad + tag + menu.Title;
|
var homeMark = menu.IsDefaultDesktopHome ? "[默认首页] " : "";
|
||||||
|
TitleText = tag + homeMark + menu.Title;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,13 +57,25 @@ public class MenuEditorModel : BindableBase
|
|||||||
private bool _isHide;
|
private bool _isHide;
|
||||||
private bool _isKeepAlive = true;
|
private bool _isKeepAlive = true;
|
||||||
private bool _isAffix;
|
private bool _isAffix;
|
||||||
|
private bool _isDefaultDesktopHome;
|
||||||
private int _orderNo = 100;
|
private int _orderNo = 100;
|
||||||
private StatusEnum _status = StatusEnum.Enable;
|
private StatusEnum _status = StatusEnum.Enable;
|
||||||
private string? _remark;
|
private string? _remark;
|
||||||
|
|
||||||
public long Id { get => _id; set => SetProperty(ref _id, value); }
|
public long Id { get => _id; set => SetProperty(ref _id, value); }
|
||||||
public long Pid { get => _pid; set => SetProperty(ref _pid, value); }
|
public long Pid { get => _pid; set => SetProperty(ref _pid, value); }
|
||||||
public MenuTypeEnum Type { get => _type; set => SetProperty(ref _type, value); }
|
public MenuTypeEnum Type
|
||||||
|
{
|
||||||
|
get => _type;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!SetProperty(ref _type, value))
|
||||||
|
return;
|
||||||
|
RaisePropertyChanged(nameof(CanSetDefaultDesktopHome));
|
||||||
|
if (value != MenuTypeEnum.Menu)
|
||||||
|
IsDefaultDesktopHome = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
public string? Name { get => _name; set => SetProperty(ref _name, value); }
|
public string? Name { get => _name; set => SetProperty(ref _name, value); }
|
||||||
public string? Path { get => _path; set => SetProperty(ref _path, value); }
|
public string? Path { get => _path; set => SetProperty(ref _path, value); }
|
||||||
public string? Component { get => _component; set => SetProperty(ref _component, value); }
|
public string? Component { get => _component; set => SetProperty(ref _component, value); }
|
||||||
@@ -68,12 +88,20 @@ public class MenuEditorModel : BindableBase
|
|||||||
public bool IsHide { get => _isHide; set => SetProperty(ref _isHide, value); }
|
public bool IsHide { get => _isHide; set => SetProperty(ref _isHide, value); }
|
||||||
public bool IsKeepAlive { get => _isKeepAlive; set => SetProperty(ref _isKeepAlive, value); }
|
public bool IsKeepAlive { get => _isKeepAlive; set => SetProperty(ref _isKeepAlive, value); }
|
||||||
public bool IsAffix { get => _isAffix; set => SetProperty(ref _isAffix, value); }
|
public bool IsAffix { get => _isAffix; set => SetProperty(ref _isAffix, value); }
|
||||||
|
public bool IsDefaultDesktopHome
|
||||||
|
{
|
||||||
|
get => _isDefaultDesktopHome;
|
||||||
|
set => SetProperty(ref _isDefaultDesktopHome, value);
|
||||||
|
}
|
||||||
public int OrderNo { get => _orderNo; set => SetProperty(ref _orderNo, value); }
|
public int OrderNo { get => _orderNo; set => SetProperty(ref _orderNo, value); }
|
||||||
public StatusEnum Status { get => _status; set => SetProperty(ref _status, value); }
|
public StatusEnum Status { get => _status; set => SetProperty(ref _status, value); }
|
||||||
public string? Remark { get => _remark; set => SetProperty(ref _remark, value); }
|
public string? Remark { get => _remark; set => SetProperty(ref _remark, value); }
|
||||||
|
|
||||||
public bool IsNew => Id == 0;
|
public bool IsNew => Id == 0;
|
||||||
|
|
||||||
|
/// <summary>仅「菜单」类型可设为桌面默认首页</summary>
|
||||||
|
public bool CanSetDefaultDesktopHome => Type == MenuTypeEnum.Menu;
|
||||||
|
|
||||||
public void LoadFrom(SysMenu m)
|
public void LoadFrom(SysMenu m)
|
||||||
{
|
{
|
||||||
Id = m.Id;
|
Id = m.Id;
|
||||||
@@ -91,6 +119,7 @@ public class MenuEditorModel : BindableBase
|
|||||||
IsHide = m.IsHide;
|
IsHide = m.IsHide;
|
||||||
IsKeepAlive = m.IsKeepAlive;
|
IsKeepAlive = m.IsKeepAlive;
|
||||||
IsAffix = m.IsAffix;
|
IsAffix = m.IsAffix;
|
||||||
|
IsDefaultDesktopHome = m.Type == MenuTypeEnum.Menu && m.IsDefaultDesktopHome;
|
||||||
OrderNo = m.OrderNo;
|
OrderNo = m.OrderNo;
|
||||||
Status = m.Status;
|
Status = m.Status;
|
||||||
Remark = m.Remark;
|
Remark = m.Remark;
|
||||||
@@ -115,6 +144,7 @@ public class MenuEditorModel : BindableBase
|
|||||||
IsHide = IsHide,
|
IsHide = IsHide,
|
||||||
IsKeepAlive = IsKeepAlive,
|
IsKeepAlive = IsKeepAlive,
|
||||||
IsAffix = IsAffix,
|
IsAffix = IsAffix,
|
||||||
|
IsDefaultDesktopHome = Type == MenuTypeEnum.Menu && IsDefaultDesktopHome,
|
||||||
OrderNo = OrderNo,
|
OrderNo = OrderNo,
|
||||||
Status = Status,
|
Status = Status,
|
||||||
Remark = Remark
|
Remark = Remark
|
||||||
@@ -138,6 +168,7 @@ public class MenuEditorModel : BindableBase
|
|||||||
IsHide = false;
|
IsHide = false;
|
||||||
IsKeepAlive = true;
|
IsKeepAlive = true;
|
||||||
IsAffix = false;
|
IsAffix = false;
|
||||||
|
IsDefaultDesktopHome = false;
|
||||||
OrderNo = 100;
|
OrderNo = 100;
|
||||||
Status = StatusEnum.Enable;
|
Status = StatusEnum.Enable;
|
||||||
Remark = null;
|
Remark = null;
|
||||||
@@ -147,6 +178,10 @@ public class MenuEditorModel : BindableBase
|
|||||||
public class MenuManagementViewModel : BaseViewModel
|
public class MenuManagementViewModel : BaseViewModel
|
||||||
{
|
{
|
||||||
private readonly ISysMenuService _menuService;
|
private readonly ISysMenuService _menuService;
|
||||||
|
/// <summary>最近一次加载的全量菜单(折叠切换时仅重算列表,不重复读库)</summary>
|
||||||
|
private List<SysMenu> _allMenusCache = new();
|
||||||
|
/// <summary>已折叠节点 Id(其子级不在扁平列表中展示)</summary>
|
||||||
|
private readonly HashSet<long> _collapsedMenuIds = new();
|
||||||
|
|
||||||
public ObservableCollection<MenuFlatRow> FlatRows { get; } = new();
|
public ObservableCollection<MenuFlatRow> FlatRows { get; } = new();
|
||||||
public ObservableCollection<KeyValuePair<long, string>> ParentOptions { get; } = new();
|
public ObservableCollection<KeyValuePair<long, string>> ParentOptions { get; } = new();
|
||||||
@@ -208,6 +243,9 @@ public class MenuManagementViewModel : BaseViewModel
|
|||||||
public DelegateCommand AddChildCommand { get; }
|
public DelegateCommand AddChildCommand { get; }
|
||||||
public DelegateCommand SaveCommand { get; }
|
public DelegateCommand SaveCommand { get; }
|
||||||
public DelegateCommand DeleteCommand { get; }
|
public DelegateCommand DeleteCommand { get; }
|
||||||
|
public DelegateCommand<MenuFlatRow?> ToggleExpandCommand { get; }
|
||||||
|
public DelegateCommand ExpandAllCommand { get; }
|
||||||
|
public DelegateCommand CollapseAllCommand { get; }
|
||||||
|
|
||||||
public MenuManagementViewModel(
|
public MenuManagementViewModel(
|
||||||
ISysMenuService menuService,
|
ISysMenuService menuService,
|
||||||
@@ -224,6 +262,9 @@ public class MenuManagementViewModel : BaseViewModel
|
|||||||
.ObservesProperty(() => Editor);
|
.ObservesProperty(() => Editor);
|
||||||
DeleteCommand = new DelegateCommand(async () => await DeleteAsync(), () => SelectedRow != null && SelectedRow.Menu.Id != 0)
|
DeleteCommand = new DelegateCommand(async () => await DeleteAsync(), () => SelectedRow != null && SelectedRow.Menu.Id != 0)
|
||||||
.ObservesProperty(() => SelectedRow);
|
.ObservesProperty(() => SelectedRow);
|
||||||
|
ToggleExpandCommand = new DelegateCommand<MenuFlatRow?>(OnToggleExpand);
|
||||||
|
ExpandAllCommand = new DelegateCommand(OnExpandAll);
|
||||||
|
CollapseAllCommand = new DelegateCommand(OnCollapseAll);
|
||||||
|
|
||||||
_ = RefreshAsync();
|
_ = RefreshAsync();
|
||||||
}
|
}
|
||||||
@@ -234,8 +275,11 @@ public class MenuManagementViewModel : BaseViewModel
|
|||||||
{
|
{
|
||||||
IsLoading = true;
|
IsLoading = true;
|
||||||
var all = await _menuService.GetAllMenusForManageAsync();
|
var all = await _menuService.GetAllMenusForManageAsync();
|
||||||
|
PruneCollapsedState(all);
|
||||||
|
_allMenusCache = all;
|
||||||
RebuildFlat(all);
|
RebuildFlat(all);
|
||||||
RebuildParentOptions(all);
|
RebuildParentOptions(all);
|
||||||
|
ResyncSelectionAfterRebuild();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -250,18 +294,90 @@ public class MenuManagementViewModel : BaseViewModel
|
|||||||
private void RebuildFlat(List<SysMenu> all)
|
private void RebuildFlat(List<SysMenu> all)
|
||||||
{
|
{
|
||||||
FlatRows.Clear();
|
FlatRows.Clear();
|
||||||
|
bool HasChild(long id) => all.Any(x => x.Pid == id);
|
||||||
|
|
||||||
void Walk(long pid, int depth)
|
void Walk(long pid, int depth)
|
||||||
{
|
{
|
||||||
foreach (var m in all.Where(x => x.Pid == pid).OrderBy(x => x.OrderNo).ThenBy(x => x.Id))
|
foreach (var m in all.Where(x => x.Pid == pid).OrderBy(x => x.OrderNo).ThenBy(x => x.Id))
|
||||||
{
|
{
|
||||||
FlatRows.Add(new MenuFlatRow(m, depth));
|
var hasCh = HasChild(m.Id);
|
||||||
Walk(m.Id, depth + 1);
|
var expanded = !_collapsedMenuIds.Contains(m.Id);
|
||||||
|
FlatRows.Add(new MenuFlatRow(m, depth, hasCh, expanded));
|
||||||
|
if (hasCh && expanded)
|
||||||
|
Walk(m.Id, depth + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Walk(0, 0);
|
Walk(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>删除已不存在的菜单 Id,并去掉已无子级的折叠记录</summary>
|
||||||
|
private void PruneCollapsedState(List<SysMenu> all)
|
||||||
|
{
|
||||||
|
var validIds = new HashSet<long>(all.Select(m => m.Id));
|
||||||
|
foreach (var id in _collapsedMenuIds.ToList())
|
||||||
|
{
|
||||||
|
if (!validIds.Contains(id) || !all.Any(x => x.Pid == id))
|
||||||
|
_collapsedMenuIds.Remove(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnToggleExpand(MenuFlatRow? row)
|
||||||
|
{
|
||||||
|
if (row?.HasChildren != true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var id = row.Menu.Id;
|
||||||
|
if (_collapsedMenuIds.Contains(id))
|
||||||
|
_collapsedMenuIds.Remove(id);
|
||||||
|
else
|
||||||
|
_collapsedMenuIds.Add(id);
|
||||||
|
|
||||||
|
RebuildFlat(_allMenusCache);
|
||||||
|
ResyncSelectionAfterRebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnExpandAll()
|
||||||
|
{
|
||||||
|
_collapsedMenuIds.Clear();
|
||||||
|
RebuildFlat(_allMenusCache);
|
||||||
|
ResyncSelectionAfterRebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCollapseAll()
|
||||||
|
{
|
||||||
|
_collapsedMenuIds.Clear();
|
||||||
|
foreach (var m in _allMenusCache.Where(m => _allMenusCache.Any(c => c.Pid == m.Id)))
|
||||||
|
_collapsedMenuIds.Add(m.Id);
|
||||||
|
|
||||||
|
RebuildFlat(_allMenusCache);
|
||||||
|
ResyncSelectionAfterRebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>重建列表后,按 Id 恢复选中;若当前项被折叠隐藏则选中其可见祖先</summary>
|
||||||
|
private void ResyncSelectionAfterRebuild()
|
||||||
|
{
|
||||||
|
if (_selectedRow == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var id = _selectedRow.Menu.Id;
|
||||||
|
MenuFlatRow? row = FlatRows.FirstOrDefault(r => r.Menu.Id == id);
|
||||||
|
var curId = id;
|
||||||
|
while (row == null && curId != 0)
|
||||||
|
{
|
||||||
|
var m = _allMenusCache.FirstOrDefault(x => x.Id == curId);
|
||||||
|
if (m == null)
|
||||||
|
break;
|
||||||
|
curId = m.Pid;
|
||||||
|
if (curId == 0)
|
||||||
|
break;
|
||||||
|
row = FlatRows.FirstOrDefault(r => r.Menu.Id == curId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row != null)
|
||||||
|
SelectedRow = row;
|
||||||
|
}
|
||||||
|
|
||||||
private void RebuildParentOptions(List<SysMenu> all)
|
private void RebuildParentOptions(List<SysMenu> all)
|
||||||
{
|
{
|
||||||
ParentOptions.Clear();
|
ParentOptions.Clear();
|
||||||
|
|||||||
@@ -168,9 +168,12 @@ public class WeightRecordEditDialogViewModel : BaseViewModel, IDialogResultable<
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 净重自动计算
|
// 净重自动计算(0 视为未称,不计算净重)
|
||||||
if (Record.GrossWeight.HasValue && Record.TareWeight.HasValue)
|
if (Record.GrossWeight.HasValue && Record.GrossWeight.Value > 0
|
||||||
|
&& Record.TareWeight.HasValue && Record.TareWeight.Value > 0)
|
||||||
Record.NetWeight = Math.Round(Record.GrossWeight.Value - Record.TareWeight.Value, 2);
|
Record.NetWeight = Math.Round(Record.GrossWeight.Value - Record.TareWeight.Value, 2);
|
||||||
|
else
|
||||||
|
Record.NetWeight = null;
|
||||||
|
|
||||||
// 写入密炼物料
|
// 写入密炼物料
|
||||||
Record.MixerMaterialIds = _mixerMaterialIds;
|
Record.MixerMaterialIds = _mixerMaterialIds;
|
||||||
|
|||||||
@@ -98,6 +98,21 @@ public class WeightRecordOperationViewModel : BaseViewModel
|
|||||||
private bool _tareWeightCaptured;
|
private bool _tareWeightCaptured;
|
||||||
public bool TareWeightCaptured { get => _tareWeightCaptured; set => SetProperty(ref _tareWeightCaptured, value); }
|
public bool TareWeightCaptured { get => _tareWeightCaptured; set => SetProperty(ref _tareWeightCaptured, value); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 手动输入毛重/皮重(仅用于测试联调,正式过磅请关闭)。
|
||||||
|
/// </summary>
|
||||||
|
private bool _isManualWeightEntry;
|
||||||
|
public bool IsManualWeightEntry
|
||||||
|
{
|
||||||
|
get => _isManualWeightEntry;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!SetProperty(ref _isManualWeightEntry, value)) return;
|
||||||
|
CaptureGrossWeightCommand.RaiseCanExecuteChanged();
|
||||||
|
CaptureTareWeightCommand.RaiseCanExecuteChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ─── 表单绑定属性 ───
|
// ─── 表单绑定属性 ───
|
||||||
|
|
||||||
private DateTime _weighDate = DateTime.Today;
|
private DateTime _weighDate = DateTime.Today;
|
||||||
@@ -234,14 +249,27 @@ public class WeightRecordOperationViewModel : BaseViewModel
|
|||||||
public double? GrossWeight
|
public double? GrossWeight
|
||||||
{
|
{
|
||||||
get => _grossWeight;
|
get => _grossWeight;
|
||||||
set { SetProperty(ref _grossWeight, value); RecalcNetWeight(); }
|
set
|
||||||
|
{
|
||||||
|
if (!SetProperty(ref _grossWeight, value)) return;
|
||||||
|
// 测试用手动录入:有有效数值即视为已填写(用于「已采集」提示与保存)
|
||||||
|
if (IsManualWeightEntry)
|
||||||
|
GrossWeightCaptured = value.HasValue && value.Value > 0;
|
||||||
|
RecalcNetWeight();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private double? _tareWeight;
|
private double? _tareWeight;
|
||||||
public double? TareWeight
|
public double? TareWeight
|
||||||
{
|
{
|
||||||
get => _tareWeight;
|
get => _tareWeight;
|
||||||
set { SetProperty(ref _tareWeight, value); RecalcNetWeight(); }
|
set
|
||||||
|
{
|
||||||
|
if (!SetProperty(ref _tareWeight, value)) return;
|
||||||
|
if (IsManualWeightEntry)
|
||||||
|
TareWeightCaptured = value.HasValue && value.Value > 0;
|
||||||
|
RecalcNetWeight();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private double? _netWeight;
|
private double? _netWeight;
|
||||||
@@ -312,10 +340,12 @@ public class WeightRecordOperationViewModel : BaseViewModel
|
|||||||
|
|
||||||
CaptureGrossWeightCommand = new DelegateCommand(CaptureGrossWeight, CanCaptureGrossWeight)
|
CaptureGrossWeightCommand = new DelegateCommand(CaptureGrossWeight, CanCaptureGrossWeight)
|
||||||
.ObservesProperty(() => IsWeightStable)
|
.ObservesProperty(() => IsWeightStable)
|
||||||
|
.ObservesProperty(() => IsManualWeightEntry)
|
||||||
.ObservesProperty(() => GrossWeightCaptured)
|
.ObservesProperty(() => GrossWeightCaptured)
|
||||||
.ObservesProperty(() => TareWeightCaptured);
|
.ObservesProperty(() => TareWeightCaptured);
|
||||||
CaptureTareWeightCommand = new DelegateCommand(CaptureTareWeight, CanCaptureTareWeight)
|
CaptureTareWeightCommand = new DelegateCommand(CaptureTareWeight, CanCaptureTareWeight)
|
||||||
.ObservesProperty(() => IsWeightStable)
|
.ObservesProperty(() => IsWeightStable)
|
||||||
|
.ObservesProperty(() => IsManualWeightEntry)
|
||||||
.ObservesProperty(() => GrossWeightCaptured)
|
.ObservesProperty(() => GrossWeightCaptured)
|
||||||
.ObservesProperty(() => TareWeightCaptured);
|
.ObservesProperty(() => TareWeightCaptured);
|
||||||
SaveCommand = new DelegateCommand(async () => await SaveAsync());
|
SaveCommand = new DelegateCommand(async () => await SaveAsync());
|
||||||
@@ -386,7 +416,8 @@ public class WeightRecordOperationViewModel : BaseViewModel
|
|||||||
{
|
{
|
||||||
_vehiclePresent = true;
|
_vehiclePresent = true;
|
||||||
_stableCountdown = 0;
|
_stableCountdown = 0;
|
||||||
_baseWeight = _rnd.Next(18000, 65000); // 18~65吨随机
|
// 同一张单第二次上磅:模拟重量与已采值协调(先毛后皮 → 皮重小于毛重;先皮后毛 → 毛重大于皮重)
|
||||||
|
PickBaseWeightForCurrentWeighingScenario();
|
||||||
AddLog("检测到车辆入场");
|
AddLog("检测到车辆入场");
|
||||||
|
|
||||||
// 模拟摄像头识别车牌(2秒后回传)
|
// 模拟摄像头识别车牌(2秒后回传)
|
||||||
@@ -411,6 +442,40 @@ public class WeightRecordOperationViewModel : BaseViewModel
|
|||||||
leaveTimer.Start();
|
leaveTimer.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 按当前单据已填毛重/皮重生成模拟磅台基准重量,保证净重合理。
|
||||||
|
/// </summary>
|
||||||
|
private void PickBaseWeightForCurrentWeighingScenario()
|
||||||
|
{
|
||||||
|
if (HasEffectiveWeighValue(GrossWeight) && !HasEffectiveWeighValue(TareWeight))
|
||||||
|
{
|
||||||
|
var g = GrossWeight!.Value;
|
||||||
|
// 皮重需明显小于毛重,留出净重空间
|
||||||
|
var maxTare = Math.Max(0, g - 30);
|
||||||
|
var minTare = Math.Min(maxTare * 0.22, maxTare - 80);
|
||||||
|
minTare = Math.Max(0, minTare);
|
||||||
|
if (maxTare - minTare < 25)
|
||||||
|
minTare = Math.Max(0, maxTare * 0.45);
|
||||||
|
if (maxTare - minTare < 1)
|
||||||
|
maxTare = Math.Max(minTare + 1, g - 5);
|
||||||
|
_baseWeight = minTare + _rnd.NextDouble() * Math.Max(1e-6, maxTare - minTare);
|
||||||
|
}
|
||||||
|
else if (HasEffectiveWeighValue(TareWeight) && !HasEffectiveWeighValue(GrossWeight))
|
||||||
|
{
|
||||||
|
var t = TareWeight!.Value;
|
||||||
|
var lower = t + Math.Max(100, t * 0.05);
|
||||||
|
var span = 8000 + _rnd.NextDouble() * 12000;
|
||||||
|
var upper = Math.Min(65000, lower + span);
|
||||||
|
if (upper - lower < 500)
|
||||||
|
upper = lower + 500;
|
||||||
|
_baseWeight = lower + _rnd.NextDouble() * (upper - lower);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_baseWeight = _rnd.Next(18000, 65000); // 18~65 吨随机(首次过磅或两称已齐)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void SimulateVehicleLeave()
|
private void SimulateVehicleLeave()
|
||||||
{
|
{
|
||||||
_vehiclePresent = false;
|
_vehiclePresent = false;
|
||||||
@@ -446,19 +511,25 @@ public class WeightRecordOperationViewModel : BaseViewModel
|
|||||||
|
|
||||||
private bool CanCaptureGrossWeight()
|
private bool CanCaptureGrossWeight()
|
||||||
{
|
{
|
||||||
return IsWeightStable && !GrossWeightCaptured;
|
if (GrossWeightCaptured) return false;
|
||||||
|
// 手动测试模式:无需等稳定即可点采集
|
||||||
|
if (IsManualWeightEntry) return true;
|
||||||
|
return IsWeightStable;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CanCaptureTareWeight()
|
private bool CanCaptureTareWeight()
|
||||||
{
|
{
|
||||||
return IsWeightStable && !TareWeightCaptured;
|
if (TareWeightCaptured) return false;
|
||||||
|
if (IsManualWeightEntry) return true;
|
||||||
|
return IsWeightStable;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RecalcNetWeight()
|
private void RecalcNetWeight()
|
||||||
{
|
{
|
||||||
if (GrossWeight.HasValue && TareWeight.HasValue)
|
// 与后端一致:0 或占位视为“未称”,避免仅毛重时 net=tare(0) 即毛重、单据误判完成
|
||||||
|
if (HasEffectiveWeighValue(GrossWeight) && HasEffectiveWeighValue(TareWeight))
|
||||||
{
|
{
|
||||||
NetWeight = Math.Round(GrossWeight.Value - TareWeight.Value, 2);
|
NetWeight = Math.Round(GrossWeight!.Value - TareWeight!.Value, 2);
|
||||||
RaisePropertyChanged(nameof(NetWeightDisplay));
|
RaisePropertyChanged(nameof(NetWeightDisplay));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -468,6 +539,8 @@ public class WeightRecordOperationViewModel : BaseViewModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool HasEffectiveWeighValue(double? kg) => kg.HasValue && kg.Value > 0;
|
||||||
|
|
||||||
private async Task SaveAsync()
|
private async Task SaveAsync()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(PlateNumber))
|
if (string.IsNullOrWhiteSpace(PlateNumber))
|
||||||
@@ -481,12 +554,12 @@ public class WeightRecordOperationViewModel : BaseViewModel
|
|||||||
var isOutbound = string.Equals(InoutDirection, "2", StringComparison.Ordinal);
|
var isOutbound = string.Equals(InoutDirection, "2", StringComparison.Ordinal);
|
||||||
if (!isCompleteSelectedRecord)
|
if (!isCompleteSelectedRecord)
|
||||||
{
|
{
|
||||||
if (isOutbound && !TareWeight.HasValue)
|
if (isOutbound && !HasEffectiveWeighValue(TareWeight))
|
||||||
{
|
{
|
||||||
HandyControl.Controls.MessageBox.Warning("出厂流程请先采集皮重!");
|
HandyControl.Controls.MessageBox.Warning("出厂流程请先采集皮重!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!isOutbound && !GrossWeight.HasValue)
|
if (!isOutbound && !HasEffectiveWeighValue(GrossWeight))
|
||||||
{
|
{
|
||||||
HandyControl.Controls.MessageBox.Warning("进厂流程请先采集毛重!");
|
HandyControl.Controls.MessageBox.Warning("进厂流程请先采集毛重!");
|
||||||
return;
|
return;
|
||||||
@@ -494,7 +567,7 @@ public class WeightRecordOperationViewModel : BaseViewModel
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var needSecondWeightCaptured = isOutbound ? GrossWeight.HasValue : TareWeight.HasValue;
|
var needSecondWeightCaptured = isOutbound ? HasEffectiveWeighValue(GrossWeight) : HasEffectiveWeighValue(TareWeight);
|
||||||
if (!needSecondWeightCaptured)
|
if (!needSecondWeightCaptured)
|
||||||
{
|
{
|
||||||
HandyControl.Controls.MessageBox.Warning(isOutbound
|
HandyControl.Controls.MessageBox.Warning(isOutbound
|
||||||
@@ -558,6 +631,7 @@ public class WeightRecordOperationViewModel : BaseViewModel
|
|||||||
ClearCustomerSelection();
|
ClearCustomerSelection();
|
||||||
ClearMixerMaterialSelection();
|
ClearMixerMaterialSelection();
|
||||||
ClearVehicleMatch();
|
ClearVehicleMatch();
|
||||||
|
IsManualWeightEntry = false;
|
||||||
// 保存后立即按当前方向回填缓存,避免必须重进页面才显示
|
// 保存后立即按当前方向回填缓存,避免必须重进页面才显示
|
||||||
TryApplyCachedUnitByDirection(InoutDirection);
|
TryApplyCachedUnitByDirection(InoutDirection);
|
||||||
RaisePropertyChanged(nameof(NetWeightDisplay));
|
RaisePropertyChanged(nameof(NetWeightDisplay));
|
||||||
@@ -592,8 +666,9 @@ public class WeightRecordOperationViewModel : BaseViewModel
|
|||||||
IsPlateNumberLocked = false;
|
IsPlateNumberLocked = false;
|
||||||
ClearSupplierSelection();
|
ClearSupplierSelection();
|
||||||
ClearCustomerSelection();
|
ClearCustomerSelection();
|
||||||
ClearMixerMaterialSelection();
|
ClearMixerMaterialSelection();
|
||||||
ClearVehicleMatch();
|
ClearVehicleMatch();
|
||||||
|
IsManualWeightEntry = false;
|
||||||
RaisePropertyChanged(nameof(NetWeightDisplay));
|
RaisePropertyChanged(nameof(NetWeightDisplay));
|
||||||
AddLog("表单已清空");
|
AddLog("表单已清空");
|
||||||
}
|
}
|
||||||
@@ -845,15 +920,16 @@ public class WeightRecordOperationViewModel : BaseViewModel
|
|||||||
// 按进出方向查询当天待补称单据:进厂=已称毛重,出厂=已称皮重。
|
// 按进出方向查询当天待补称单据:进厂=已称毛重,出厂=已称皮重。
|
||||||
var page = await _weightRecordService.PageAsync(1, 100, filterPlateNumber: plate);
|
var page = await _weightRecordService.PageAsync(1, 100, filterPlateNumber: plate);
|
||||||
var today = DateTime.Today;
|
var today = DateTime.Today;
|
||||||
var isOutbound = string.Equals(InoutDirection, "2", StringComparison.Ordinal);
|
var isOutbound = string.Equals(InoutDirection, "2", StringComparison.Ordinal);
|
||||||
var candidates = page.Records
|
bool effT(double? w) => w.HasValue && w.Value > 0;
|
||||||
.Where(r =>
|
var candidates = page.Records
|
||||||
string.Equals((r.PlateNumber ?? string.Empty).Trim(), plate, StringComparison.OrdinalIgnoreCase) &&
|
.Where(r =>
|
||||||
r.WeighDate.HasValue &&
|
string.Equals((r.PlateNumber ?? string.Empty).Trim(), plate, StringComparison.OrdinalIgnoreCase) &&
|
||||||
r.WeighDate.Value.Date == today &&
|
r.WeighDate.HasValue &&
|
||||||
(isOutbound
|
r.WeighDate.Value.Date == today &&
|
||||||
? (r.TareWeight.HasValue && !r.GrossWeight.HasValue)
|
(isOutbound
|
||||||
: (r.GrossWeight.HasValue && !r.TareWeight.HasValue)))
|
? (effT(r.TareWeight) && !effT(r.GrossWeight))
|
||||||
|
: (effT(r.GrossWeight) && !effT(r.TareWeight))))
|
||||||
.OrderByDescending(r => r.CreateTime ?? DateTime.MinValue)
|
.OrderByDescending(r => r.CreateTime ?? DateTime.MinValue)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
@@ -865,8 +941,8 @@ public class WeightRecordOperationViewModel : BaseViewModel
|
|||||||
BillNo = record.BillNo ?? "-",
|
BillNo = record.BillNo ?? "-",
|
||||||
PlateNumber = record.PlateNumber ?? "-",
|
PlateNumber = record.PlateNumber ?? "-",
|
||||||
FirstWeightDisplay = isOutbound
|
FirstWeightDisplay = isOutbound
|
||||||
? (record.TareWeight.HasValue ? $"{record.TareWeight.Value:N2}" : "-")
|
? (HasEffectiveWeighValue(record.TareWeight) ? $"{record.TareWeight!.Value:N2}" : "-")
|
||||||
: (record.GrossWeight.HasValue ? $"{record.GrossWeight.Value:N2}" : "-")
|
: (HasEffectiveWeighValue(record.GrossWeight) ? $"{record.GrossWeight!.Value:N2}" : "-")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -914,14 +990,14 @@ public class WeightRecordOperationViewModel : BaseViewModel
|
|||||||
if (isOutbound)
|
if (isOutbound)
|
||||||
{
|
{
|
||||||
TareWeight = selected.Source.TareWeight;
|
TareWeight = selected.Source.TareWeight;
|
||||||
TareWeightCaptured = TareWeight.HasValue;
|
TareWeightCaptured = HasEffectiveWeighValue(TareWeight);
|
||||||
GrossWeight = null;
|
GrossWeight = null;
|
||||||
GrossWeightCaptured = false;
|
GrossWeightCaptured = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
GrossWeight = selected.Source.GrossWeight;
|
GrossWeight = selected.Source.GrossWeight;
|
||||||
GrossWeightCaptured = GrossWeight.HasValue;
|
GrossWeightCaptured = HasEffectiveWeighValue(GrossWeight);
|
||||||
TareWeight = null;
|
TareWeight = null;
|
||||||
TareWeightCaptured = false;
|
TareWeightCaptured = false;
|
||||||
}
|
}
|
||||||
@@ -953,6 +1029,7 @@ public class WeightRecordOperationViewModel : BaseViewModel
|
|||||||
ClearCustomerSelection();
|
ClearCustomerSelection();
|
||||||
ClearMixerMaterialSelection();
|
ClearMixerMaterialSelection();
|
||||||
ClearVehicleMatch();
|
ClearVehicleMatch();
|
||||||
|
IsManualWeightEntry = false;
|
||||||
RaisePropertyChanged(nameof(NetWeightDisplay));
|
RaisePropertyChanged(nameof(NetWeightDisplay));
|
||||||
AddLog("已切换进出方向,当前表单数据已清空");
|
AddLog("已切换进出方向,当前表单数据已清空");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
|
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
xmlns:consts="clr-namespace:YY.Admin.Core.Const;assembly=YY.Admin.Core"
|
xmlns:consts="clr-namespace:YY.Admin.Core.Const;assembly=YY.Admin.Core"
|
||||||
xmlns:ctls="clr-namespace:YY.Admin.Core.Controls;assembly=YY.Admin.Core"
|
xmlns:ctls="clr-namespace:YY.Admin.Core.Controls;assembly=YY.Admin.Core"
|
||||||
|
xmlns:shell="clr-namespace:System.Windows.Shell;assembly=PresentationFramework"
|
||||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||||
Icon="/Resources/Icon/logo.ico"
|
Icon="/Resources/Icon/logo.ico"
|
||||||
Title="{Binding Title}"
|
Title="{Binding Title}"
|
||||||
@@ -17,6 +18,28 @@
|
|||||||
KeyDown="Window_KeyDown"
|
KeyDown="Window_KeyDown"
|
||||||
FontSize="{StaticResource FontSize }">
|
FontSize="{StaticResource FontSize }">
|
||||||
|
|
||||||
|
<hc:Window.NonClientAreaContent>
|
||||||
|
<!-- 与最小化/关闭同一标题栏行,靠右紧贴系统按钮左侧 -->
|
||||||
|
<Grid HorizontalAlignment="Stretch">
|
||||||
|
<Button
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="0,0,2,0"
|
||||||
|
Width="34"
|
||||||
|
Height="26"
|
||||||
|
Padding="0"
|
||||||
|
Background="Transparent"
|
||||||
|
BorderThickness="0"
|
||||||
|
Cursor="Hand"
|
||||||
|
Focusable="False"
|
||||||
|
shell:WindowChrome.IsHitTestVisibleInChrome="True"
|
||||||
|
Command="{Binding OpenServerSettingsCommand}"
|
||||||
|
ToolTip="服务器设置">
|
||||||
|
<ctls:FontAwesomeIcon Icon="" IconFamily="Solid" Foreground="{DynamicResource PrimaryTextBrush}"/>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</hc:Window.NonClientAreaContent>
|
||||||
|
|
||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<ResourceDictionary.MergedDictionaries>
|
<ResourceDictionary.MergedDictionaries>
|
||||||
@@ -38,16 +61,6 @@
|
|||||||
|
|
||||||
<Border Grid.Column="1">
|
<Border Grid.Column="1">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Button
|
|
||||||
Command="{Binding OpenServerSettingsCommand}"
|
|
||||||
Width="34"
|
|
||||||
Height="34"
|
|
||||||
HorizontalAlignment="Right"
|
|
||||||
VerticalAlignment="Top"
|
|
||||||
Margin="0,8,8,0"
|
|
||||||
ToolTip="服务器设置">
|
|
||||||
<ctls:FontAwesomeIcon Icon="" IconFamily="Solid"/>
|
|
||||||
</Button>
|
|
||||||
<StackPanel Margin="20 0 20 20">
|
<StackPanel Margin="20 0 20 20">
|
||||||
<!-- Logo和标题 -->
|
<!-- Logo和标题 -->
|
||||||
<StackPanel HorizontalAlignment="Center" Margin="0,20,0,40">
|
<StackPanel HorizontalAlignment="Center" Margin="0,20,0,40">
|
||||||
|
|||||||
@@ -61,11 +61,53 @@
|
|||||||
|
|
||||||
<Border Grid.Column="0" CornerRadius="4" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1" Padding="4">
|
<Border Grid.Column="0" CornerRadius="4" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1" Padding="4">
|
||||||
<DockPanel LastChildFill="True">
|
<DockPanel LastChildFill="True">
|
||||||
<TextBlock DockPanel.Dock="Top" Margin="4,0,4,8" Text="菜单树(点击选中后在右侧编辑)" TextWrapping="Wrap" Opacity="0.85"/>
|
<StackPanel DockPanel.Dock="Top" Margin="4,0,4,8">
|
||||||
|
<TextBlock Text="菜单树(点击选中后在右侧编辑,有子节点时可点箭头折叠/展开)" TextWrapping="Wrap" Opacity="0.85"/>
|
||||||
|
<hc:UniformSpacingPanel Spacing="6" Margin="0,6,0,0">
|
||||||
|
<Button Content="全部展开" Padding="8,2" Style="{StaticResource ButtonInfo}"
|
||||||
|
Command="{Binding ExpandAllCommand}"/>
|
||||||
|
<Button Content="全部折叠" Padding="8,2" Style="{StaticResource ButtonInfo}"
|
||||||
|
Command="{Binding CollapseAllCommand}"/>
|
||||||
|
</hc:UniformSpacingPanel>
|
||||||
|
</StackPanel>
|
||||||
<ListBox ItemsSource="{Binding FlatRows}"
|
<ListBox ItemsSource="{Binding FlatRows}"
|
||||||
SelectedItem="{Binding SelectedRow, Mode=TwoWay}"
|
SelectedItem="{Binding SelectedRow, Mode=TwoWay}"
|
||||||
DisplayMemberPath="IndentTitle"
|
VirtualizingStackPanel.IsVirtualizing="True">
|
||||||
VirtualizingStackPanel.IsVirtualizing="True"/>
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Grid Margin="{Binding LeadingMargin}">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="22"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Button Grid.Column="0"
|
||||||
|
Width="20" Height="20" Padding="0"
|
||||||
|
Focusable="False"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Cursor="Hand"
|
||||||
|
Background="Transparent"
|
||||||
|
BorderThickness="0"
|
||||||
|
Command="{Binding DataContext.ToggleExpandCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
|
||||||
|
CommandParameter="{Binding}"
|
||||||
|
Visibility="{Binding HasChildren, Converter={StaticResource Boolean2VisibilityConverter}}">
|
||||||
|
<md:PackIcon Width="16" Height="16" VerticalAlignment="Center">
|
||||||
|
<md:PackIcon.Style>
|
||||||
|
<Style TargetType="md:PackIcon">
|
||||||
|
<Setter Property="Kind" Value="ChevronRight"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsExpanded}" Value="True">
|
||||||
|
<Setter Property="Kind" Value="ChevronDown"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</md:PackIcon.Style>
|
||||||
|
</md:PackIcon>
|
||||||
|
</Button>
|
||||||
|
<TextBlock Grid.Column="1" Text="{Binding TitleText}" VerticalAlignment="Center" TextTrimming="CharacterEllipsis"/>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
@@ -199,6 +241,12 @@
|
|||||||
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=12, Md=4, Lg=4, Xl=4}">
|
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=12, Md=4, Lg=4, Xl=4}">
|
||||||
<CheckBox Content="固定页签" IsChecked="{Binding IsAffix}" Margin="0,6,0,0"/>
|
<CheckBox Content="固定页签" IsChecked="{Binding IsAffix}" Margin="0,6,0,0"/>
|
||||||
</hc:Col>
|
</hc:Col>
|
||||||
|
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=12, Md=8, Lg=8, Xl=8}">
|
||||||
|
<CheckBox Content="默认首页(桌面登录后自动打开此菜单)"
|
||||||
|
IsChecked="{Binding IsDefaultDesktopHome}"
|
||||||
|
IsEnabled="{Binding CanSetDefaultDesktopHome}"
|
||||||
|
Margin="0,6,0,0"/>
|
||||||
|
</hc:Col>
|
||||||
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=12, Md=4, Lg=4, Xl=4}">
|
<hc:Col Layout="{hc:ColLayout Xs=12, Sm=12, Md=4, Lg=4, Xl=4}">
|
||||||
<CheckBox Content="缓存页面" IsChecked="{Binding IsKeepAlive}" Margin="0,6,0,0"/>
|
<CheckBox Content="缓存页面" IsChecked="{Binding IsKeepAlive}" Margin="0,6,0,0"/>
|
||||||
</hc:Col>
|
</hc:Col>
|
||||||
|
|||||||
@@ -540,9 +540,14 @@
|
|||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="*"/>
|
<RowDefinition Height="*"/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,4">
|
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,4" VerticalAlignment="Center">
|
||||||
<Border Width="4" Height="18" CornerRadius="2" Background="{StaticResource SectionBorderBrush}"/>
|
<Border Width="4" Height="18" CornerRadius="2" Background="{StaticResource SectionBorderBrush}"/>
|
||||||
<TextBlock Text="重量信息" Style="{StaticResource SectionTitleStyle}"/>
|
<TextBlock Text="重量信息" Style="{StaticResource SectionTitleStyle}"/>
|
||||||
|
<CheckBox Margin="16,0,0,0" VerticalAlignment="Center"
|
||||||
|
Content="手动输入重量"
|
||||||
|
IsChecked="{Binding IsManualWeightEntry, Mode=TwoWay}"/>
|
||||||
|
<TextBlock Margin="8,0,0,0" VerticalAlignment="Center"
|
||||||
|
FontSize="11" Foreground="#ff9800" Text="(仅限测试)"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- 毛重 / 皮重并排 -->
|
<!-- 毛重 / 皮重并排 -->
|
||||||
@@ -568,20 +573,44 @@
|
|||||||
<TextBlock Text="毛 重(KG)" FontSize="12" Foreground="{DynamicResource SecondaryTextBrush}"
|
<TextBlock Text="毛 重(KG)" FontSize="12" Foreground="{DynamicResource SecondaryTextBrush}"
|
||||||
Margin="0,0,0,4" HorizontalAlignment="Center"/>
|
Margin="0,0,0,4" HorizontalAlignment="Center"/>
|
||||||
<Border CornerRadius="8" Padding="10,10" Background="{DynamicResource RegionBrush}">
|
<Border CornerRadius="8" Padding="10,10" Background="{DynamicResource RegionBrush}">
|
||||||
<TextBlock Text="{Binding GrossWeight, StringFormat=N2, FallbackValue=—, TargetNullValue=—}"
|
<Grid MinHeight="40">
|
||||||
HorizontalAlignment="Center" FontSize="20" FontWeight="Bold"
|
<TextBlock HorizontalAlignment="Center" FontSize="20" FontWeight="Bold"
|
||||||
FontFamily="Consolas">
|
FontFamily="Consolas" VerticalAlignment="Center">
|
||||||
<TextBlock.Style>
|
<TextBlock.Text>
|
||||||
<Style TargetType="TextBlock">
|
<Binding Path="GrossWeight" StringFormat="N2" TargetNullValue="—"/>
|
||||||
<Setter Property="Foreground" Value="{DynamicResource SecondaryTextBrush}"/>
|
</TextBlock.Text>
|
||||||
<Style.Triggers>
|
<TextBlock.Style>
|
||||||
<DataTrigger Binding="{Binding GrossWeightCaptured}" Value="True">
|
<Style TargetType="TextBlock">
|
||||||
<Setter Property="Foreground" Value="#4caf50"/>
|
<Setter Property="Foreground" Value="{DynamicResource SecondaryTextBrush}"/>
|
||||||
</DataTrigger>
|
<Setter Property="Visibility" Value="Visible"/>
|
||||||
</Style.Triggers>
|
<Style.Triggers>
|
||||||
</Style>
|
<DataTrigger Binding="{Binding GrossWeightCaptured}" Value="True">
|
||||||
</TextBlock.Style>
|
<Setter Property="Foreground" Value="#4caf50"/>
|
||||||
</TextBlock>
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding IsManualWeightEntry}" Value="True">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
<hc:NumericUpDown Value="{Binding GrossWeight, Mode=TwoWay}"
|
||||||
|
Minimum="0"
|
||||||
|
DecimalPlaces="2"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<hc:NumericUpDown.Style>
|
||||||
|
<Style TargetType="hc:NumericUpDown" BasedOn="{StaticResource NumericUpDownPlus}">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsManualWeightEntry}" Value="True">
|
||||||
|
<Setter Property="Visibility" Value="Visible"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</hc:NumericUpDown.Style>
|
||||||
|
</hc:NumericUpDown>
|
||||||
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
<Border Margin="0,6,0,0" Height="32" CornerRadius="6">
|
<Border Margin="0,6,0,0" Height="32" CornerRadius="6">
|
||||||
<Border.Style>
|
<Border.Style>
|
||||||
@@ -632,12 +661,44 @@
|
|||||||
<TextBlock Text="皮 重(KG)" FontSize="12" Foreground="{DynamicResource SecondaryTextBrush}"
|
<TextBlock Text="皮 重(KG)" FontSize="12" Foreground="{DynamicResource SecondaryTextBrush}"
|
||||||
Margin="0,0,0,4" HorizontalAlignment="Center"/>
|
Margin="0,0,0,4" HorizontalAlignment="Center"/>
|
||||||
<Border CornerRadius="8" Padding="10,10" Background="{DynamicResource RegionBrush}">
|
<Border CornerRadius="8" Padding="10,10" Background="{DynamicResource RegionBrush}">
|
||||||
<TextBlock HorizontalAlignment="Center" FontSize="20" FontWeight="Bold"
|
<Grid MinHeight="40">
|
||||||
FontFamily="Consolas" Foreground="{DynamicResource SecondaryTextBrush}">
|
<TextBlock HorizontalAlignment="Center" FontSize="20" FontWeight="Bold"
|
||||||
<TextBlock.Text>
|
FontFamily="Consolas" VerticalAlignment="Center">
|
||||||
<Binding Path="TareWeight" StringFormat="N2" FallbackValue="—" TargetNullValue="—"/>
|
<TextBlock.Text>
|
||||||
</TextBlock.Text>
|
<Binding Path="TareWeight" StringFormat="N2" TargetNullValue="—"/>
|
||||||
</TextBlock>
|
</TextBlock.Text>
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource SecondaryTextBrush}"/>
|
||||||
|
<Setter Property="Visibility" Value="Visible"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding TareWeightCaptured}" Value="True">
|
||||||
|
<Setter Property="Foreground" Value="#4caf50"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding IsManualWeightEntry}" Value="True">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
<hc:NumericUpDown Value="{Binding TareWeight, Mode=TwoWay}"
|
||||||
|
Minimum="0"
|
||||||
|
DecimalPlaces="2"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<hc:NumericUpDown.Style>
|
||||||
|
<Style TargetType="hc:NumericUpDown" BasedOn="{StaticResource NumericUpDownPlus}">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsManualWeightEntry}" Value="True">
|
||||||
|
<Setter Property="Visibility" Value="Visible"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</hc:NumericUpDown.Style>
|
||||||
|
</hc:NumericUpDown>
|
||||||
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
<Border Margin="0,6,0,0" Height="32" CornerRadius="6">
|
<Border Margin="0,6,0,0" Height="32" CornerRadius="6">
|
||||||
<Border.Style>
|
<Border.Style>
|
||||||
|
|||||||
Reference in New Issue
Block a user