完善 MCS 桌面代理与开炼机动作同步,修复原料相关菜单权限,桌面端新增上辅机 MES 菜单。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
namespace YY.Admin.Core.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 设备库代理 STOMP 发送扩展(由 StompWebSocketService 实现)。
|
||||
/// </summary>
|
||||
public interface IEquipmentDbStompSender
|
||||
{
|
||||
Task SendAgentStatusAsync(object status, CancellationToken cancellationToken = default);
|
||||
Task SendAgentResultAsync(object result, CancellationToken cancellationToken = default);
|
||||
string CurrentDeviceId { get; }
|
||||
}
|
||||
@@ -32,7 +32,7 @@ public class SysConfigSeedData : ISqlSugarEntitySeedData<SysConfig>
|
||||
new SysConfig{ Id=1300000000191, Name="RefreshToken过期时间", Code=ConfigConst.SysRefreshTokenExpire, Value="20160", SysFlag=YesNoEnum.Y, Remark="刷新Token过期时间(分钟)(一般 refresh_token 的有效时间 > 2 * access_token 的有效时间)", OrderNo=110, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
|
||||
new SysConfig{ Id=1300000000192, Name="会话续期阈值", Code=ConfigConst.SysTokenIdleExtendMinutes, Value="20", SysFlag=YesNoEnum.Y, Remark="桌面端:用户有操作时,若会话剩余不足该分钟数则续期为「Token过期时间」整段", OrderNo=115, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
|
||||
new SysConfig{ Id=1300000000193, Name="登录状态检查间隔", Code=ConfigConst.SysTokenCheckIntervalMinutes, Value="1", SysFlag=YesNoEnum.Y, Remark="桌面端:定时检查登录是否过期的间隔(分钟)", OrderNo=118, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
|
||||
new SysConfig{ Id=1300000000194, Name="登录永不过期", Code=ConfigConst.SysTokenNeverExpire, Value="False", SysFlag=YesNoEnum.Y, Remark="桌面端:开启后不触发登录过期提示与自动踢回登录页", OrderNo=119, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
|
||||
new SysConfig{ Id=1300000000194, Name="登录永不过期", Code=ConfigConst.SysTokenNeverExpire, Value="True", SysFlag=YesNoEnum.Y, Remark="桌面端:开启后不触发登录过期提示与自动踢回登录页", OrderNo=119, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
|
||||
new SysConfig{ Id=1300000000201, Name="发送异常日志邮件", Code=ConfigConst.SysErrorMail, Value="False", SysFlag=YesNoEnum.Y, Remark="是否发送异常日志邮件", OrderNo=120, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
|
||||
new SysConfig{ Id=1300000000211, Name="域登录验证", Code=ConfigConst.SysDomainLogin, Value="False", SysFlag=YesNoEnum.Y, Remark="是否开启域登录验证", OrderNo=130, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
|
||||
new SysConfig{ Id=1300000000221, Name="数据校验日志", Code=ConfigConst.SysValidationLog, Value="True", SysFlag=YesNoEnum.Y, Remark="是否数据校验日志", OrderNo=140, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
|
||||
|
||||
@@ -54,6 +54,21 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
|
||||
new SysMenu{ Id=1300150011201, Pid=1300150000101, Title="胶料快检记录", Path="/xslmes/rubberQuickTestRecord", Name="rubberQuickTestRecord", Component="RubberQuickTestRecordListView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=113 },
|
||||
// 密炼计划
|
||||
new SysMenu{ Id=1300150011401, Pid=1300150000101, Title="密炼计划", Path="/xslmes/mesXslMixingProductionPlan", Name="mesXslMixingProductionPlan", Component="MixingProductionPlanListView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=114 },
|
||||
// 开炼机动作中间表(设备库 MCSToMES_Act_Mill 只读)
|
||||
new SysMenu{ Id=1300150011501, Pid=1300150000101, Title="开炼机动作中间表", Path="/xslmes/mcsActMill", Name="mcsActMill", Component="McsActMillListView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=115 },
|
||||
|
||||
#endregion
|
||||
|
||||
#region 上辅机mes功能
|
||||
|
||||
new SysMenu{ Id=1300160000101, Pid=0, Title="上辅机mes功能", Path="/upperMes", Name="upperMes", Component="Layout", Icon="", Type=MenuTypeEnum.Dir, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=9500 },
|
||||
new SysMenu{ Id=1300160010101, Pid=1300160000101, Title="交接班", Path="/xslmes/shiftHandover", Name="shiftHandover", Component="ShiftHandoverListView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
|
||||
new SysMenu{ Id=1300160010201, Pid=1300160000101, Title="bom验证", Path="/xslmes/bomValidation", Name="bomValidation", Component="BomValidationListView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=101 },
|
||||
new SysMenu{ Id=1300160010301, Pid=1300160000101, Title="配方维护", Path="/xslmes/formulaMaintain", Name="formulaMaintain", Component="FormulaMaintainListView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=102 },
|
||||
new SysMenu{ Id=1300160010401, Pid=1300160000101, Title="计划管理", Path="/xslmes/planManage", Name="planManage", Component="PlanManageListView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=103 },
|
||||
new SysMenu{ Id=1300160010501, Pid=1300160000101, Title="报表查询", Path="/xslmes/reportQuery", Name="reportQuery", Component="ReportQueryListView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=104 },
|
||||
new SysMenu{ Id=1300160010601, Pid=1300160000101, Title="设备停机记录", Path="/xslmes/equipmentDowntime", Name="equipmentDowntime", Component="EquipmentDowntimeListView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=105 },
|
||||
new SysMenu{ Id=1300160010701, Pid=1300160000101, Title="生产日志", Path="/xslmes/productionLog", Name="productionLog", Component="ProductionLogListView", Icon="", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=106 },
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -35,6 +35,15 @@ public class SysTenantMenuSeedData : ISqlSugarEntitySeedData<SysTenantMenu>
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300150011201},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300150011301},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300150011401},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300150011501},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300160000101},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300160010101},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300160010201},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300160010301},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300160010401},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300160010501},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300160010601},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300160010701},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300200012101},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300200012111},
|
||||
new SysTenantMenu(){ TenantId=1300000000001,MenuId=1300200012121},
|
||||
|
||||
@@ -95,5 +95,15 @@
|
||||
"DefaultTenantId": 1002, // 自动创建本地用户时使用的默认租户ID
|
||||
"Captcha": "", // 如启用登录验证码,在此传入验证码
|
||||
"CheckKey": "" // 如启用登录验证码,在此传入验证码key
|
||||
},
|
||||
"EquipmentDb": {
|
||||
"Enabled": true,
|
||||
"DeviceId": "",
|
||||
"ServerHost": "43.108.8.218",
|
||||
"ServerPort": 1433,
|
||||
"DbName": "MESNAC_MCS_ShareDB",
|
||||
"Username": "sa",
|
||||
"Password": "xsl@123456",
|
||||
"ConnectTimeoutSeconds": 15
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2331,11 +2331,23 @@ namespace YY.Admin.Services.Service.Auth
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否开启永不过期
|
||||
/// 是否开启永不过期(缺省或读失败时视为开启,避免误踢登录)
|
||||
/// </summary>
|
||||
private async Task<bool> getSysTokenNeverExpireAsync()
|
||||
{
|
||||
return await _sysConfigService.GetConfigValue<bool>(ConfigConst.SysTokenNeverExpire);
|
||||
try
|
||||
{
|
||||
var raw = await _sysConfigService.GetConfigValue<string>(ConfigConst.SysTokenNeverExpire);
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
return true;
|
||||
if (bool.TryParse(raw, out var b))
|
||||
return b;
|
||||
return raw.Trim() is "1" or "Y" or "y" or "yes" or "YES" or "on" or "ON";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -27,7 +27,37 @@ namespace YY.Admin.Services.Service.Config
|
||||
_sysCacheService.Set($"{CacheConst.KeyConfig}{code}", value);
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(value)) return default;
|
||||
return (T)Convert.ChangeType(value, typeof(T));
|
||||
|
||||
// bool:Convert.ChangeType 对 "1"/"Y" 等会抛异常,统一稳健解析
|
||||
var targetType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
|
||||
if (targetType == typeof(bool))
|
||||
{
|
||||
var b = ParseConfigBool(value);
|
||||
return (T)(object)b;
|
||||
}
|
||||
|
||||
return (T)Convert.ChangeType(value, targetType);
|
||||
}
|
||||
|
||||
private static bool ParseConfigBool(string value)
|
||||
{
|
||||
if (bool.TryParse(value, out var b))
|
||||
return b;
|
||||
switch (value.Trim().ToLowerInvariant())
|
||||
{
|
||||
case "1":
|
||||
case "y":
|
||||
case "yes":
|
||||
case "on":
|
||||
return true;
|
||||
case "0":
|
||||
case "n":
|
||||
case "no":
|
||||
case "off":
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -0,0 +1,486 @@
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Prism.Events;
|
||||
using YY.Admin.Core.Events;
|
||||
using YY.Admin.Core.Services;
|
||||
|
||||
namespace YY.Admin.Services.Service.EquipmentDb;
|
||||
|
||||
/// <summary>
|
||||
/// 订阅 MES 下发的 EQ_* 命令,执行设备库 SQL 并回传结果。
|
||||
/// </summary>
|
||||
public sealed class EquipmentDbCommandHandler
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
private readonly IEquipmentDbConnectionFactory _connectionFactory;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ISignalRService _signalRService;
|
||||
private readonly HashSet<string> _processedRequestIds = new();
|
||||
private readonly object _dedupeLock = new();
|
||||
|
||||
public EquipmentDbCommandHandler(
|
||||
IEventAggregator eventAggregator,
|
||||
IEquipmentDbConnectionFactory connectionFactory,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IConfiguration configuration,
|
||||
ISignalRService signalRService)
|
||||
{
|
||||
_connectionFactory = connectionFactory;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_configuration = configuration;
|
||||
_signalRService = signalRService;
|
||||
|
||||
eventAggregator.GetEvent<RemoteCommandReceivedEvent>()
|
||||
.Subscribe(OnRemoteCommand, ThreadOption.BackgroundThread);
|
||||
}
|
||||
|
||||
private void OnRemoteCommand(RemoteCommandPayload payload)
|
||||
{
|
||||
if (payload == null || string.IsNullOrWhiteSpace(payload.CommandJson))
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(payload.CommandJson);
|
||||
if (!doc.RootElement.TryGetProperty("cmd", out var cmdEl))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var cmd = cmdEl.GetString() ?? string.Empty;
|
||||
if (!cmd.StartsWith("EQ_", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var request = JsonSerializer.Deserialize<EquipmentDbCommandDto>(payload.CommandJson, JsonOptions);
|
||||
if (request == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_ = Task.Run(() => HandleAsync(request, CancellationToken.None));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore non-EQ payloads
|
||||
}
|
||||
}
|
||||
|
||||
public async Task HandleAsync(EquipmentDbCommandDto request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (request == null || string.IsNullOrWhiteSpace(request.RequestId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
lock (_dedupeLock)
|
||||
{
|
||||
if (!_processedRequestIds.Add(request.RequestId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (_processedRequestIds.Count > 2000)
|
||||
{
|
||||
_processedRequestIds.Clear();
|
||||
_processedRequestIds.Add(request.RequestId);
|
||||
}
|
||||
}
|
||||
|
||||
var result = new EquipmentDbResultDto
|
||||
{
|
||||
RequestId = request.RequestId,
|
||||
DeviceId = ResolveDeviceId(request.DeviceId),
|
||||
Cmd = request.Cmd,
|
||||
RespondedAt = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
switch ((request.Cmd ?? string.Empty).ToUpperInvariant())
|
||||
{
|
||||
case "EQ_TEST":
|
||||
await HandleTestAsync(result, cancellationToken).ConfigureAwait(false);
|
||||
break;
|
||||
case "EQ_QUERY":
|
||||
case "EQ_SYNC_PULL":
|
||||
await HandleQueryAsync(request, result, cancellationToken).ConfigureAwait(false);
|
||||
break;
|
||||
case "EQ_WRITE":
|
||||
await HandleWriteAsync(request, result, cancellationToken).ConfigureAwait(false);
|
||||
break;
|
||||
case "EQ_SYNC_PUSH":
|
||||
await HandleSyncPushAsync(request, result, cancellationToken).ConfigureAwait(false);
|
||||
break;
|
||||
default:
|
||||
result.Success = false;
|
||||
result.Error = "未知命令: " + request.Cmd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.Success = false;
|
||||
result.Error = ex.Message;
|
||||
result.DbConnected = false;
|
||||
result.DbMessage = ex.Message;
|
||||
}
|
||||
|
||||
await SendResultAsync(result, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task HandleTestAsync(EquipmentDbResultDto result, CancellationToken cancellationToken)
|
||||
{
|
||||
var (ok, message) = await _connectionFactory.TestAsync(cancellationToken).ConfigureAwait(false);
|
||||
result.Success = ok;
|
||||
result.DbConnected = ok;
|
||||
result.DbMessage = message;
|
||||
result.Error = ok ? null : message;
|
||||
await ReportStatusAsync(ok, message, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task HandleQueryAsync(EquipmentDbCommandDto request, EquipmentDbResultDto result, CancellationToken cancellationToken)
|
||||
{
|
||||
EquipmentDbWhitelist.ValidateSql(request.Sql, request.Table);
|
||||
await using var conn = _connectionFactory.CreateConnection();
|
||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = request.Sql!;
|
||||
cmd.CommandTimeout = 60;
|
||||
BindParams(cmd, request.Params);
|
||||
var rows = new List<Dictionary<string, object?>>();
|
||||
await using var reader = await cmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
|
||||
while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
var row = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
|
||||
for (var i = 0; i < reader.FieldCount; i++)
|
||||
{
|
||||
object? val = reader.IsDBNull(i) ? null : reader.GetValue(i);
|
||||
if (val is DateTime dt)
|
||||
{
|
||||
val = dt.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
row[reader.GetName(i)] = val;
|
||||
}
|
||||
rows.Add(row);
|
||||
}
|
||||
result.Success = true;
|
||||
result.Rows = rows;
|
||||
result.Affected = rows.Count;
|
||||
result.DbConnected = true;
|
||||
result.DbMessage = "ok";
|
||||
}
|
||||
|
||||
private async Task HandleWriteAsync(EquipmentDbCommandDto request, EquipmentDbResultDto result, CancellationToken cancellationToken)
|
||||
{
|
||||
EquipmentDbWhitelist.ValidateSql(request.Sql, request.Table);
|
||||
await using var conn = _connectionFactory.CreateConnection();
|
||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = request.Sql!;
|
||||
cmd.CommandTimeout = 60;
|
||||
BindParams(cmd, request.Params);
|
||||
var affected = await cmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
result.Success = true;
|
||||
result.Affected = affected;
|
||||
result.DbConnected = true;
|
||||
result.DbMessage = "ok";
|
||||
}
|
||||
|
||||
private async Task HandleSyncPushAsync(EquipmentDbCommandDto request, EquipmentDbResultDto result, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Table) || !EquipmentDbWhitelist.IsAllowedTable(request.Table))
|
||||
{
|
||||
throw new InvalidOperationException("EQ_SYNC_PUSH 表不在白名单");
|
||||
}
|
||||
if (request.Payload == null || request.Payload.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("EQ_SYNC_PUSH payload 为空");
|
||||
}
|
||||
|
||||
request.Payload.TryGetValue("mode", out var modeObj);
|
||||
var mode = Convert.ToString(modeObj)?.ToLowerInvariant() ?? "insert";
|
||||
var rows = ExtractRows(request.Payload);
|
||||
if (rows.Count == 0)
|
||||
{
|
||||
result.Success = true;
|
||||
result.Affected = 0;
|
||||
result.DbConnected = true;
|
||||
return;
|
||||
}
|
||||
|
||||
List<string>? keys = null;
|
||||
if (request.Payload.TryGetValue("keys", out var keysObj) && keysObj != null)
|
||||
{
|
||||
keys = JsonSerializer.Deserialize<List<string>>(JsonSerializer.Serialize(keysObj), JsonOptions);
|
||||
}
|
||||
|
||||
await using var conn = _connectionFactory.CreateConnection();
|
||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
||||
var affected = 0;
|
||||
foreach (var row in rows)
|
||||
{
|
||||
if (mode == "upsert" && keys != null && keys.Count > 0)
|
||||
{
|
||||
affected += await UpsertRowAsync(conn, request.Table!, row, keys, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else if (mode == "update" && keys != null && keys.Count > 0)
|
||||
{
|
||||
affected += await UpdateRowAsync(conn, request.Table!, row, keys, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
affected += await InsertRowAsync(conn, request.Table!, row, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
result.Success = true;
|
||||
result.Affected = affected;
|
||||
result.DbConnected = true;
|
||||
result.DbMessage = "ok";
|
||||
}
|
||||
|
||||
private static List<Dictionary<string, object?>> ExtractRows(Dictionary<string, object?> payload)
|
||||
{
|
||||
if (!payload.TryGetValue("rows", out var rowsObj) || rowsObj == null)
|
||||
{
|
||||
throw new InvalidOperationException("EQ_SYNC_PUSH 缺少 rows");
|
||||
}
|
||||
if (rowsObj is JsonElement je)
|
||||
{
|
||||
return JsonSerializer.Deserialize<List<Dictionary<string, object?>>>(je.GetRawText(), JsonOptions)
|
||||
?? new List<Dictionary<string, object?>>();
|
||||
}
|
||||
return JsonSerializer.Deserialize<List<Dictionary<string, object?>>>(JsonSerializer.Serialize(rowsObj), JsonOptions)
|
||||
?? new List<Dictionary<string, object?>>();
|
||||
}
|
||||
|
||||
private static async Task<int> InsertRowAsync(
|
||||
SqlConnection conn, string table, Dictionary<string, object?> row, CancellationToken cancellationToken)
|
||||
{
|
||||
var cols = row.Keys.ToList();
|
||||
foreach (var c in cols)
|
||||
{
|
||||
if (!Regex.IsMatch(c, @"^[A-Za-z0-9_]+$"))
|
||||
{
|
||||
throw new InvalidOperationException("非法列名: " + c);
|
||||
}
|
||||
}
|
||||
var colList = string.Join(",", cols.Select(c => "[" + c + "]"));
|
||||
var paramList = string.Join(",", cols.Select((_, i) => "@p" + i));
|
||||
await using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = $"INSERT INTO [{table}] ({colList}) VALUES ({paramList})";
|
||||
for (var i = 0; i < cols.Count; i++)
|
||||
{
|
||||
cmd.Parameters.AddWithValue("@p" + i, ToDbValue(row[cols[i]]));
|
||||
}
|
||||
return await cmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task<int> UpdateRowAsync(
|
||||
SqlConnection conn, string table, Dictionary<string, object?> row, List<string> keys, CancellationToken cancellationToken)
|
||||
{
|
||||
var setCols = row.Keys.Where(k => !keys.Contains(k, StringComparer.OrdinalIgnoreCase)).ToList();
|
||||
if (setCols.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
var sets = string.Join(",", setCols.Select((c, i) => $"[{c}]=@s{i}"));
|
||||
var wheres = string.Join(" AND ", keys.Select((k, i) => $"[{k}]=@k{i}"));
|
||||
await using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = $"UPDATE [{table}] SET {sets} WHERE {wheres}";
|
||||
for (var i = 0; i < setCols.Count; i++)
|
||||
{
|
||||
cmd.Parameters.AddWithValue("@s" + i, ToDbValue(GetIgnoreCase(row, setCols[i])));
|
||||
}
|
||||
for (var i = 0; i < keys.Count; i++)
|
||||
{
|
||||
cmd.Parameters.AddWithValue("@k" + i, ToDbValue(GetIgnoreCase(row, keys[i])));
|
||||
}
|
||||
return await cmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task<int> UpsertRowAsync(
|
||||
SqlConnection conn, string table, Dictionary<string, object?> row, List<string> keys, CancellationToken cancellationToken)
|
||||
{
|
||||
var updated = await UpdateRowAsync(conn, table, row, keys, cancellationToken).ConfigureAwait(false);
|
||||
if (updated > 0)
|
||||
{
|
||||
return updated;
|
||||
}
|
||||
return await InsertRowAsync(conn, table, row, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static object? GetIgnoreCase(Dictionary<string, object?> row, string key)
|
||||
{
|
||||
if (row.TryGetValue(key, out var v))
|
||||
{
|
||||
return v;
|
||||
}
|
||||
foreach (var kv in row)
|
||||
{
|
||||
if (string.Equals(kv.Key, key, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return kv.Value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static object ToDbValue(object? value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return DBNull.Value;
|
||||
}
|
||||
if (value is JsonElement je)
|
||||
{
|
||||
return je.ValueKind switch
|
||||
{
|
||||
JsonValueKind.Null => DBNull.Value,
|
||||
JsonValueKind.String => je.GetString() ?? (object)DBNull.Value,
|
||||
JsonValueKind.Number => je.TryGetInt64(out var l) ? l : je.GetDouble(),
|
||||
JsonValueKind.True => true,
|
||||
JsonValueKind.False => false,
|
||||
_ => je.ToString()
|
||||
};
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void BindParams(SqlCommand cmd, List<object?>? parameters)
|
||||
{
|
||||
if (parameters == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < parameters.Count; i++)
|
||||
{
|
||||
cmd.Parameters.AddWithValue("@p" + i, ToDbValue(parameters[i]));
|
||||
}
|
||||
if (cmd.CommandText.Contains('?'))
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var idx = 0;
|
||||
foreach (var ch in cmd.CommandText)
|
||||
{
|
||||
if (ch == '?')
|
||||
{
|
||||
sb.Append("@p").Append(idx++);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(ch);
|
||||
}
|
||||
}
|
||||
cmd.CommandText = sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendResultAsync(EquipmentDbResultDto result, CancellationToken cancellationToken)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(result, JsonOptions);
|
||||
var useRest = json.Length > 200_000 || (result.Rows?.Count ?? 0) > 500;
|
||||
if (useRest)
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = _httpClientFactory.CreateClient("JeecgApi");
|
||||
result.LastChunk = true;
|
||||
result.ChunkIndex = 0;
|
||||
result.ChunkTotal = 1;
|
||||
var resp = await client.PostAsJsonAsync("/xslmes/deviceAgent/anon/resultChunk", result, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
resp.EnsureSuccessStatusCode();
|
||||
return;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
if (_signalRService is IEquipmentDbStompSender sender)
|
||||
{
|
||||
await sender.SendAgentResultAsync(result, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ReportStatusAsync(bool dbConnected, string? dbMessage, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var deviceId = ResolveDeviceId(null);
|
||||
var status = new Dictionary<string, object?>
|
||||
{
|
||||
["deviceId"] = deviceId,
|
||||
["hostName"] = Environment.MachineName,
|
||||
["dbConnected"] = dbConnected,
|
||||
["dbMessage"] = dbMessage,
|
||||
["ts"] = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
|
||||
};
|
||||
if (_signalRService is IEquipmentDbStompSender sender)
|
||||
{
|
||||
await sender.SendAgentStatusAsync(status, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
try
|
||||
{
|
||||
var client = _httpClientFactory.CreateClient("JeecgApi");
|
||||
await client.PostAsJsonAsync("/xslmes/deviceAgent/anon/heartbeat", status, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
private string ResolveDeviceId(string? fromRequest)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(fromRequest))
|
||||
{
|
||||
return fromRequest!;
|
||||
}
|
||||
var settings = _connectionFactory.CurrentSettings;
|
||||
if (!string.IsNullOrWhiteSpace(settings.DeviceId))
|
||||
{
|
||||
return settings.DeviceId;
|
||||
}
|
||||
return _configuration.GetValue<string>("EquipmentDb:DeviceId")
|
||||
?? Environment.MachineName;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class EquipmentDbCommandDto
|
||||
{
|
||||
public string? RequestId { get; set; }
|
||||
public string? Cmd { get; set; }
|
||||
public string? DeviceId { get; set; }
|
||||
public string? Table { get; set; }
|
||||
public string? Sql { get; set; }
|
||||
public string? SqlKey { get; set; }
|
||||
public List<object?>? Params { get; set; }
|
||||
public Dictionary<string, object?>? Payload { get; set; }
|
||||
public int? TimeoutMs { get; set; }
|
||||
}
|
||||
|
||||
public sealed class EquipmentDbResultDto
|
||||
{
|
||||
public string? RequestId { get; set; }
|
||||
public string? DeviceId { get; set; }
|
||||
public string? Cmd { get; set; }
|
||||
public bool Success { get; set; }
|
||||
public string? Error { get; set; }
|
||||
public int? Affected { get; set; }
|
||||
public List<Dictionary<string, object?>>? Rows { get; set; }
|
||||
public bool? DbConnected { get; set; }
|
||||
public string? DbMessage { get; set; }
|
||||
public long? RespondedAt { get; set; }
|
||||
public int? ChunkIndex { get; set; }
|
||||
public int? ChunkTotal { get; set; }
|
||||
public bool? LastChunk { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Microsoft.Data.SqlClient;
|
||||
|
||||
namespace YY.Admin.Services.Service.EquipmentDb;
|
||||
|
||||
public interface IEquipmentDbConnectionFactory
|
||||
{
|
||||
SqlConnection CreateConnection();
|
||||
Task<(bool Ok, string Message)> TestAsync(CancellationToken cancellationToken = default);
|
||||
EquipmentDbSettingsStore.EquipmentDbSettings CurrentSettings { get; }
|
||||
void Reload();
|
||||
}
|
||||
|
||||
public sealed class EquipmentDbConnectionFactory : IEquipmentDbConnectionFactory
|
||||
{
|
||||
private EquipmentDbSettingsStore.EquipmentDbSettings _settings = EquipmentDbSettingsStore.Load();
|
||||
|
||||
public EquipmentDbSettingsStore.EquipmentDbSettings CurrentSettings => _settings;
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
_settings = EquipmentDbSettingsStore.Load();
|
||||
}
|
||||
|
||||
public SqlConnection CreateConnection()
|
||||
{
|
||||
if (!_settings.Enabled)
|
||||
{
|
||||
throw new InvalidOperationException("设备库连接未启用");
|
||||
}
|
||||
return new SqlConnection(EquipmentDbSettingsStore.BuildConnectionString(_settings));
|
||||
}
|
||||
|
||||
public async Task<(bool Ok, string Message)> TestAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Reload();
|
||||
await using var conn = CreateConnection();
|
||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = "SELECT 1";
|
||||
cmd.CommandTimeout = Math.Max(5, _settings.ConnectTimeoutSeconds);
|
||||
var scalar = await cmd.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
|
||||
return (true, "连接成功: " + scalar);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (false, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.IO;
|
||||
using YY.Admin.Core.Util;
|
||||
|
||||
namespace YY.Admin.Services.Service.EquipmentDb;
|
||||
|
||||
/// <summary>
|
||||
/// 设备库(SQL Server 中间库)本地连接配置。
|
||||
/// </summary>
|
||||
public static class EquipmentDbSettingsStore
|
||||
{
|
||||
public class EquipmentDbSettings
|
||||
{
|
||||
public bool Enabled { get; set; } = true;
|
||||
public string DeviceId { get; set; } = string.Empty;
|
||||
public string ServerHost { get; set; } = "127.0.0.1";
|
||||
public int ServerPort { get; set; } = 1433;
|
||||
public string DbName { get; set; } = "MES_ShareDB";
|
||||
public string Username { get; set; } = "sa";
|
||||
public string Password { get; set; } = string.Empty;
|
||||
public int ConnectTimeoutSeconds { get; set; } = 15;
|
||||
}
|
||||
|
||||
public static string GetUserConfigPath()
|
||||
{
|
||||
var dir = AppWritablePaths.EnsureDirectoryExists(AppWritablePaths.ConfigurationDirectory);
|
||||
return Path.Combine(dir, "equipment-db.json");
|
||||
}
|
||||
|
||||
public static EquipmentDbSettings Load()
|
||||
{
|
||||
var path = GetUserConfigPath();
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return new EquipmentDbSettings
|
||||
{
|
||||
DeviceId = Environment.MachineName
|
||||
};
|
||||
}
|
||||
try
|
||||
{
|
||||
var settings = JsonConvert.DeserializeObject<EquipmentDbSettings>(File.ReadAllText(path))
|
||||
?? new EquipmentDbSettings();
|
||||
if (string.IsNullOrWhiteSpace(settings.DeviceId))
|
||||
{
|
||||
settings.DeviceId = Environment.MachineName;
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new EquipmentDbSettings { DeviceId = Environment.MachineName };
|
||||
}
|
||||
}
|
||||
|
||||
public static void Save(EquipmentDbSettings settings)
|
||||
{
|
||||
if (settings == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(settings));
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(settings.DeviceId))
|
||||
{
|
||||
settings.DeviceId = Environment.MachineName;
|
||||
}
|
||||
var path = GetUserConfigPath();
|
||||
File.WriteAllText(path, JsonConvert.SerializeObject(settings, Formatting.Indented));
|
||||
}
|
||||
|
||||
public static string BuildConnectionString(EquipmentDbSettings s)
|
||||
{
|
||||
var timeout = s.ConnectTimeoutSeconds > 0 ? s.ConnectTimeoutSeconds : 15;
|
||||
return $"Server={s.ServerHost},{s.ServerPort};Database={s.DbName};User Id={s.Username};Password={s.Password};" +
|
||||
$"Encrypt=False;TrustServerCertificate=True;Connect Timeout={timeout};";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 bundled appsettings 的 EquipmentDb 节点合并默认值(若有)。
|
||||
/// </summary>
|
||||
public static EquipmentDbSettings LoadMergedWithAppSettings(JObject? root)
|
||||
{
|
||||
var settings = Load();
|
||||
if (root?["EquipmentDb"] is not JObject eq)
|
||||
{
|
||||
return settings;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(settings.ServerHost) || settings.ServerHost == "127.0.0.1")
|
||||
{
|
||||
settings.ServerHost = eq.Value<string>("ServerHost") ?? settings.ServerHost;
|
||||
}
|
||||
if (settings.ServerPort == 1433 && eq.Value<int?>("ServerPort") is int p)
|
||||
{
|
||||
settings.ServerPort = p;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(settings.DbName) || settings.DbName == "MES_ShareDB")
|
||||
{
|
||||
settings.DbName = eq.Value<string>("DbName") ?? settings.DbName;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(settings.Username) || settings.Username == "sa")
|
||||
{
|
||||
settings.Username = eq.Value<string>("Username") ?? settings.Username;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(settings.Password))
|
||||
{
|
||||
settings.Password = eq.Value<string>("Password") ?? settings.Password;
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace YY.Admin.Services.Service.EquipmentDb;
|
||||
|
||||
/// <summary>
|
||||
/// 设备库 SQL 白名单:仅允许访问 MCS 中间表白名单表。
|
||||
/// </summary>
|
||||
public static class EquipmentDbWhitelist
|
||||
{
|
||||
private static readonly HashSet<string> AllowedTables = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"MCSToMES_Alarm", "MCSToMES_MixAlarm", "MCSToMES_MixAct", "MCSToMES_Act_Mill", "MCSToMES_MixBatch",
|
||||
"MCSToMES_MixCon", "MCSToMES_MixCurve", "MCSToMES_MixStep", "MCSToMES_MixWeight",
|
||||
"MCSToMES_MixExePlan", "MCSToMES_BinToMater", "MCSToMES_AutoXLLog", "MCSToMES_CheckScaleLog",
|
||||
"MESToMCS_Material", "MESToMCS_MixPlan", "MESToMCS_Recipe",
|
||||
"MESToMCS_Recipe_MixStep", "MESToMCS_Recipe_Weight"
|
||||
};
|
||||
|
||||
private static readonly Regex Dangerous = new(
|
||||
@"\b(DROP|ALTER|TRUNCATE|EXEC|EXECUTE|xp_|sp_|INTO\s+OUTFILE|OPENROWSET|OPENDATASOURCE)\b",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex TableRef = new(
|
||||
@"\b(?:FROM|INTO|UPDATE|JOIN)\s+\[?(?<t>[A-Za-z0-9_]+)\]?",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
public static bool IsAllowedTable(string? table)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(table))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return AllowedTables.Contains(table.Trim());
|
||||
}
|
||||
|
||||
public static void ValidateSql(string? sql, string? tableHint = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sql))
|
||||
{
|
||||
throw new InvalidOperationException("SQL 不能为空");
|
||||
}
|
||||
if (Dangerous.IsMatch(sql))
|
||||
{
|
||||
throw new InvalidOperationException("SQL 包含禁止关键字");
|
||||
}
|
||||
var trimmed = sql.TrimStart();
|
||||
var isSelect = trimmed.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase);
|
||||
var isInsert = trimmed.StartsWith("INSERT", StringComparison.OrdinalIgnoreCase);
|
||||
var isUpdate = trimmed.StartsWith("UPDATE", StringComparison.OrdinalIgnoreCase);
|
||||
var isDelete = trimmed.StartsWith("DELETE", StringComparison.OrdinalIgnoreCase);
|
||||
if (!(isSelect || isInsert || isUpdate || isDelete))
|
||||
{
|
||||
throw new InvalidOperationException("仅允许 SELECT/INSERT/UPDATE/DELETE");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tableHint) && !IsAllowedTable(tableHint))
|
||||
{
|
||||
throw new InvalidOperationException($"表不在白名单: {tableHint}");
|
||||
}
|
||||
|
||||
foreach (Match m in TableRef.Matches(sql))
|
||||
{
|
||||
var t = m.Groups["t"].Value;
|
||||
if (string.Equals(t, "TOP", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(t, "SELECT", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(t, "SET", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(t, "VALUES", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(t, "WHERE", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!IsAllowedTable(t))
|
||||
{
|
||||
throw new InvalidOperationException($"SQL 引用了未授权表: {t}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
using System.Data;
|
||||
using Microsoft.Data.SqlClient;
|
||||
|
||||
namespace YY.Admin.Services.Service.EquipmentDb;
|
||||
|
||||
/// <summary>
|
||||
/// 开炼机动作中间表 MCSToMES_Act_Mill 只读查询(直连设备库)。
|
||||
/// </summary>
|
||||
public interface IMcsActMillService
|
||||
{
|
||||
Task<(IReadOnlyList<McsActMillRow> Records, long Total)> PageAsync(
|
||||
int pageNo,
|
||||
int pageSize,
|
||||
string? actName = null,
|
||||
int? actAddr = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public sealed class McsActMillRow
|
||||
{
|
||||
public string? Guid { get; set; }
|
||||
public int? ActAddr { get; set; }
|
||||
public string? ActName { get; set; }
|
||||
public string? ActNameEn { get; set; }
|
||||
public string? ActMemo { get; set; }
|
||||
public int? ActRepaddr { get; set; }
|
||||
public DateTime? WriteTime { get; set; }
|
||||
public DateTime? ReadTime { get; set; }
|
||||
public int? RwFlag { get; set; }
|
||||
}
|
||||
|
||||
public sealed class McsActMillService : IMcsActMillService
|
||||
{
|
||||
private const string TableName = "MCSToMES_Act_Mill";
|
||||
private readonly IEquipmentDbConnectionFactory _factory;
|
||||
|
||||
public McsActMillService(IEquipmentDbConnectionFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
public async Task<(IReadOnlyList<McsActMillRow> Records, long Total)> PageAsync(
|
||||
int pageNo,
|
||||
int pageSize,
|
||||
string? actName = null,
|
||||
int? actAddr = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (pageNo < 1) pageNo = 1;
|
||||
if (pageSize < 1) pageSize = 20;
|
||||
|
||||
_factory.Reload();
|
||||
await using var conn = _factory.CreateConnection();
|
||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var where = new List<string>();
|
||||
var parameters = new List<SqlParameter>();
|
||||
if (!string.IsNullOrWhiteSpace(actName))
|
||||
{
|
||||
where.Add("act_name LIKE @actName");
|
||||
parameters.Add(new SqlParameter("@actName", SqlDbType.NVarChar, 50) { Value = "%" + actName.Trim() + "%" });
|
||||
}
|
||||
if (actAddr.HasValue)
|
||||
{
|
||||
where.Add("act_addr = @actAddr");
|
||||
parameters.Add(new SqlParameter("@actAddr", SqlDbType.Int) { Value = actAddr.Value });
|
||||
}
|
||||
var whereSql = where.Count == 0 ? "" : " WHERE " + string.Join(" AND ", where);
|
||||
|
||||
var countSql = $"SELECT COUNT(1) FROM [{TableName}]{whereSql}";
|
||||
EquipmentDbWhitelist.ValidateSql(countSql, TableName);
|
||||
long total;
|
||||
await using (var countCmd = conn.CreateCommand())
|
||||
{
|
||||
countCmd.CommandText = countSql;
|
||||
countCmd.Parameters.AddRange(CloneParams(parameters));
|
||||
var scalar = await countCmd.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
|
||||
total = Convert.ToInt64(scalar ?? 0);
|
||||
}
|
||||
|
||||
var offset = (pageNo - 1) * pageSize;
|
||||
var listSql = $@"
|
||||
SELECT [GUID], [act_addr], [act_name], [act_name_en], [act_memo], [act_Repaddr], [WriteTime], [ReadTime], [RW_Flag]
|
||||
FROM [{TableName}]{whereSql}
|
||||
ORDER BY [WriteTime] DESC, [GUID]
|
||||
OFFSET @offset ROWS FETCH NEXT @pageSize ROWS ONLY";
|
||||
EquipmentDbWhitelist.ValidateSql(listSql, TableName);
|
||||
|
||||
var rows = new List<McsActMillRow>();
|
||||
await using (var listCmd = conn.CreateCommand())
|
||||
{
|
||||
listCmd.CommandText = listSql;
|
||||
listCmd.Parameters.AddRange(CloneParams(parameters));
|
||||
listCmd.Parameters.Add(new SqlParameter("@offset", SqlDbType.Int) { Value = offset });
|
||||
listCmd.Parameters.Add(new SqlParameter("@pageSize", SqlDbType.Int) { Value = pageSize });
|
||||
await using var reader = await listCmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
|
||||
while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
rows.Add(new McsActMillRow
|
||||
{
|
||||
Guid = reader["GUID"] as string,
|
||||
ActAddr = reader["act_addr"] == DBNull.Value ? null : Convert.ToInt32(reader["act_addr"]),
|
||||
ActName = reader["act_name"] as string,
|
||||
ActNameEn = reader["act_name_en"] as string,
|
||||
ActMemo = reader["act_memo"] as string,
|
||||
ActRepaddr = reader["act_Repaddr"] == DBNull.Value ? null : Convert.ToInt32(reader["act_Repaddr"]),
|
||||
WriteTime = reader["WriteTime"] == DBNull.Value ? null : Convert.ToDateTime(reader["WriteTime"]),
|
||||
ReadTime = reader["ReadTime"] == DBNull.Value ? null : Convert.ToDateTime(reader["ReadTime"]),
|
||||
RwFlag = reader["RW_Flag"] == DBNull.Value ? null : Convert.ToInt32(reader["RW_Flag"]),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (rows, total);
|
||||
}
|
||||
|
||||
private static SqlParameter[] CloneParams(List<SqlParameter> source)
|
||||
{
|
||||
return source.Select(p => new SqlParameter(p.ParameterName, p.SqlDbType)
|
||||
{
|
||||
Size = p.Size,
|
||||
Value = p.Value ?? DBNull.Value
|
||||
}).ToArray();
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
|
||||
<PackageReference Include="QRCoder" Version="1.6.0" />
|
||||
<!-- 1D 条码渲染(与 web 端 jsbarcode 路径对齐:输出 SVG,由前端 CSS preserveAspectRatio 保比例显示) -->
|
||||
<PackageReference Include="ZXing.Net" Version="0.16.9" />
|
||||
|
||||
@@ -9,10 +9,11 @@ using YY.Admin.Core.Services;
|
||||
using YY.Admin.Core.Session;
|
||||
using YY.Admin.Helper;
|
||||
using YY.Admin.Infrastructure.Storage;
|
||||
using YY.Admin.Services.Service.EquipmentDb;
|
||||
|
||||
namespace YY.Admin.Infrastructure.Hubs;
|
||||
|
||||
public class StompWebSocketService : ISignalRService
|
||||
public class StompWebSocketService : ISignalRService, IEquipmentDbStompSender
|
||||
{
|
||||
// STOMP heart-beat: send \n every 10 s, declare we want to receive every 10 s
|
||||
private const int HeartbeatMs = 10_000;
|
||||
@@ -188,6 +189,11 @@ public class StompWebSocketService : ISignalRService
|
||||
BuildSubscribeFrame("sub-device-pong", $"/topic/device/{_deviceId}/pong"),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 设备库命令通道(匿名/登录均订阅)
|
||||
await SendFrameAsync(
|
||||
BuildSubscribeFrame("sub-device-eq-cmd", $"/topic/device/{_deviceId}/cmd"),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!anonymous && !string.IsNullOrWhiteSpace(_token))
|
||||
{
|
||||
await SendFrameAsync(
|
||||
@@ -207,6 +213,25 @@ public class StompWebSocketService : ISignalRService
|
||||
_networkMonitor.SetStompTransportOnline(true);
|
||||
|
||||
_ = Task.Run(() => ReceiveLoopAsync(cts.Token), cts.Token);
|
||||
// 连接成功后上报设备代理状态
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await SendAgentStatusAsync(new
|
||||
{
|
||||
deviceId = _deviceId,
|
||||
hostName = Environment.MachineName,
|
||||
dbConnected = false,
|
||||
dbMessage = "pending",
|
||||
ts = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
|
||||
}, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
catch
|
||||
@@ -232,6 +257,38 @@ public class StompWebSocketService : ISignalRService
|
||||
await SendFrameAsync(frame, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public string CurrentDeviceId => _deviceId;
|
||||
|
||||
public async Task SendAgentStatusAsync(object status, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (IsDisconnectedByUser() || _socket == null || _socket.State != WebSocketState.Open)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var json = System.Text.Json.JsonSerializer.Serialize(status);
|
||||
var frame = "SEND\n" +
|
||||
"destination:/app/device/agent/status\n" +
|
||||
"content-type:application/json\n" +
|
||||
$"content-length:{Encoding.UTF8.GetByteCount(json)}\n\n" +
|
||||
$"{json}\0";
|
||||
await SendFrameAsync(frame, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task SendAgentResultAsync(object result, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (IsDisconnectedByUser() || _socket == null || _socket.State != WebSocketState.Open)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var json = System.Text.Json.JsonSerializer.Serialize(result);
|
||||
var frame = "SEND\n" +
|
||||
"destination:/app/device/agent/result\n" +
|
||||
"content-type:application/json\n" +
|
||||
$"content-length:{Encoding.UTF8.GetByteCount(json)}\n\n" +
|
||||
$"{json}\0";
|
||||
await SendFrameAsync(frame, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var cts = Interlocked.Exchange(ref _connectionCts, null);
|
||||
@@ -437,8 +494,29 @@ public class StompWebSocketService : ISignalRService
|
||||
return "ws://" + baseUrl["http://".Length..] + "/ws/device";
|
||||
}
|
||||
|
||||
private static string ResolveDeviceId(string token)
|
||||
private string ResolveDeviceId(string token)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fromConfig = EquipmentDbSettingsStore.Load().DeviceId;
|
||||
if (!string.IsNullOrWhiteSpace(fromConfig))
|
||||
{
|
||||
return fromConfig.Trim();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
var configured = _configuration.GetValue<string>("EquipmentDb:DeviceId");
|
||||
if (!string.IsNullOrWhiteSpace(configured))
|
||||
{
|
||||
return configured.Trim();
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(Environment.MachineName))
|
||||
{
|
||||
return Environment.MachineName;
|
||||
}
|
||||
try
|
||||
{
|
||||
var parts = token.Split('.');
|
||||
|
||||
@@ -21,6 +21,8 @@ using YY.Admin.Views.MixerMaterialTareStrategy;
|
||||
using YY.Admin.Views.RubberQuickTest;
|
||||
using YY.Admin.Views.RubberQuickTestStd;
|
||||
using YY.Admin.Views.MixingProductionPlan;
|
||||
using YY.Admin.Views.McsActMill;
|
||||
using YY.Admin.Views.UpperMes;
|
||||
|
||||
namespace YY.Admin
|
||||
{
|
||||
@@ -105,6 +107,15 @@ namespace YY.Admin
|
||||
containerRegistry.RegisterForNavigation<RubberQuickTestStdListView>();
|
||||
// 密炼计划(只读)
|
||||
containerRegistry.RegisterForNavigation<MixingProductionPlanListView>();
|
||||
containerRegistry.RegisterForNavigation<McsActMillListView>();
|
||||
// 上辅机mes功能(占位)
|
||||
containerRegistry.RegisterForNavigation<ShiftHandoverListView>();
|
||||
containerRegistry.RegisterForNavigation<BomValidationListView>();
|
||||
containerRegistry.RegisterForNavigation<FormulaMaintainListView>();
|
||||
containerRegistry.RegisterForNavigation<PlanManageListView>();
|
||||
containerRegistry.RegisterForNavigation<ReportQueryListView>();
|
||||
containerRegistry.RegisterForNavigation<EquipmentDowntimeListView>();
|
||||
containerRegistry.RegisterForNavigation<ProductionLogListView>();
|
||||
// 打印设置
|
||||
containerRegistry.RegisterForNavigation<PrintSettingsView>();
|
||||
// 打印模板列表
|
||||
|
||||
@@ -30,6 +30,7 @@ using YY.Admin.Services.Service.Print;
|
||||
using YY.Admin.Services.Service.RubberQuickTest;
|
||||
using YY.Admin.Services.Service.RubberQuickTestStd;
|
||||
using YY.Admin.Services.Service.MixingProductionPlan;
|
||||
using YY.Admin.Services.Service.EquipmentDb;
|
||||
|
||||
namespace YY.Admin.Module;
|
||||
|
||||
@@ -103,6 +104,11 @@ public class SyncModule : IModule
|
||||
containerRegistry.RegisterSingleton<IMixingProductionPlanService, MixingProductionPlanService>();
|
||||
containerRegistry.RegisterSingleton<MixingProductionPlanSyncCoordinator>();
|
||||
|
||||
// 设备库代理(厂区 SQL Server 直连 + EQ_* 命令执行)
|
||||
containerRegistry.RegisterSingleton<IEquipmentDbConnectionFactory, EquipmentDbConnectionFactory>();
|
||||
containerRegistry.RegisterSingleton<EquipmentDbCommandHandler>();
|
||||
containerRegistry.RegisterSingleton<IMcsActMillService, McsActMillService>();
|
||||
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddTransient<DisconnectGuardHandler>();
|
||||
serviceCollection.AddHttpClient("JeecgApi", (sp, client) =>
|
||||
@@ -179,6 +185,21 @@ public class SyncModule : IModule
|
||||
_ = containerProvider.Resolve<RubberQuickTestStdSyncCoordinator>();
|
||||
// 密炼计划只读同步协调器
|
||||
_ = containerProvider.Resolve<MixingProductionPlanSyncCoordinator>();
|
||||
// 设备库命令处理器(订阅 EQ_*)
|
||||
var eqHandler = containerProvider.Resolve<EquipmentDbCommandHandler>();
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var factory = containerProvider.Resolve<IEquipmentDbConnectionFactory>();
|
||||
var (ok, msg) = await factory.TestAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
await eqHandler.ReportStatusAsync(ok, msg, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await eqHandler.ReportStatusAsync(false, ex.Message, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
|
||||
|
||||
@@ -110,33 +110,69 @@ namespace YY.Admin.ViewModels
|
||||
_tokenCheckTimer.Interval = TimeSpan.FromMinutes(minutes);
|
||||
}
|
||||
|
||||
/// <summary>登录永不过期缓存,避免定时器在 UI 线程同步读库失败后回落到 30 分钟过期。</summary>
|
||||
private static bool? _tokenNeverExpireCached;
|
||||
|
||||
/// <summary>
|
||||
/// 是否开启登录永不过期
|
||||
/// </summary>
|
||||
private static bool IsTokenNeverExpireEnabled()
|
||||
{
|
||||
if (_tokenNeverExpireCached.HasValue)
|
||||
return _tokenNeverExpireCached.Value;
|
||||
|
||||
try
|
||||
{
|
||||
var cfg = Prism.Ioc.ContainerLocator.Current.Resolve<ISysConfigService>();
|
||||
return cfg.GetConfigValue<bool>(ConfigConst.SysTokenNeverExpire).GetAwaiter().GetResult();
|
||||
// 用 string 读取再解析,避免 Convert.ChangeType(bool) 异常被吞掉后误判为未开启
|
||||
var raw = cfg.GetConfigValue<string>(ConfigConst.SysTokenNeverExpire).GetAwaiter().GetResult();
|
||||
var enabled = ParseNeverExpireFlag(raw);
|
||||
_tokenNeverExpireCached = enabled;
|
||||
return enabled;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
// 读配置失败时按「永不过期」处理,避免误踢回登录页
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ParseNeverExpireFlag(string? raw)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
return true;
|
||||
if (bool.TryParse(raw, out var b))
|
||||
return b;
|
||||
return raw.Trim() is "1" or "Y" or "y" or "yes" or "YES" or "on" or "ON";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登录设置保存后刷新检查间隔(已启动定时器时立即生效)
|
||||
/// 登录设置保存后刷新检查间隔 / 永不过期策略(已启动定时器时立即生效)
|
||||
/// </summary>
|
||||
public static void RefreshTokenCheckIntervalFromConfig()
|
||||
{
|
||||
_tokenNeverExpireCached = null;
|
||||
if (IsTokenNeverExpireEnabled())
|
||||
{
|
||||
StopTokenCheckTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyTokenCheckIntervalFromConfig();
|
||||
if (_tokenCheckTimer != null && !_tokenCheckTimer.IsEnabled && UserContext?.Token != null)
|
||||
_tokenCheckTimer.Start();
|
||||
}
|
||||
|
||||
// 启动定时器的方法
|
||||
public static void StartTokenCheckTimer()
|
||||
{
|
||||
_tokenNeverExpireCached = null;
|
||||
if (IsTokenNeverExpireEnabled())
|
||||
{
|
||||
StopTokenCheckTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_tokenCheckTimer == null)
|
||||
{
|
||||
_tokenCheckTimer = new DispatcherTimer();
|
||||
|
||||
@@ -170,6 +170,33 @@ namespace YY.Admin.ViewModels.Control
|
||||
["/xslmes/mesXslMixingProductionPlan"] = "MixingProductionPlanListView",
|
||||
["mesXslMixingProductionPlan"] = "MixingProductionPlanListView",
|
||||
|
||||
["McsActMillListView"] = "McsActMillListView",
|
||||
["/xslmes/mcsActMill"] = "McsActMillListView",
|
||||
["mcsActMill"] = "McsActMillListView",
|
||||
|
||||
// 上辅机mes功能(占位)
|
||||
["ShiftHandoverListView"] = "ShiftHandoverListView",
|
||||
["/xslmes/shiftHandover"] = "ShiftHandoverListView",
|
||||
["shiftHandover"] = "ShiftHandoverListView",
|
||||
["BomValidationListView"] = "BomValidationListView",
|
||||
["/xslmes/bomValidation"] = "BomValidationListView",
|
||||
["bomValidation"] = "BomValidationListView",
|
||||
["FormulaMaintainListView"] = "FormulaMaintainListView",
|
||||
["/xslmes/formulaMaintain"] = "FormulaMaintainListView",
|
||||
["formulaMaintain"] = "FormulaMaintainListView",
|
||||
["PlanManageListView"] = "PlanManageListView",
|
||||
["/xslmes/planManage"] = "PlanManageListView",
|
||||
["planManage"] = "PlanManageListView",
|
||||
["ReportQueryListView"] = "ReportQueryListView",
|
||||
["/xslmes/reportQuery"] = "ReportQueryListView",
|
||||
["reportQuery"] = "ReportQueryListView",
|
||||
["EquipmentDowntimeListView"] = "EquipmentDowntimeListView",
|
||||
["/xslmes/equipmentDowntime"] = "EquipmentDowntimeListView",
|
||||
["equipmentDowntime"] = "EquipmentDowntimeListView",
|
||||
["ProductionLogListView"] = "ProductionLogListView",
|
||||
["/xslmes/productionLog"] = "ProductionLogListView",
|
||||
["productionLog"] = "ProductionLogListView",
|
||||
|
||||
// 已实现页面:打印设置
|
||||
["PrintSettingsView"] = "PrintSettingsView",
|
||||
["/system/printSettings"] = "PrintSettingsView",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using YY.Admin.Helper;
|
||||
using YY.Admin.Services.Service.EquipmentDb;
|
||||
|
||||
namespace YY.Admin.ViewModels.Dialogs
|
||||
{
|
||||
@@ -45,9 +46,6 @@ namespace YY.Admin.ViewModels.Dialogs
|
||||
}
|
||||
|
||||
private bool _disconnectConnection;
|
||||
/// <summary>
|
||||
/// 是否断开连接(true=断开,false=连接)
|
||||
/// </summary>
|
||||
public bool DisconnectConnection
|
||||
{
|
||||
get => _disconnectConnection;
|
||||
@@ -60,11 +58,50 @@ namespace YY.Admin.ViewModels.Dialogs
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接参数是否可编辑(勾选断开连接后禁用)。
|
||||
/// </summary>
|
||||
public bool IsConnectionFieldsEnabled => !DisconnectConnection;
|
||||
|
||||
private string _eqDeviceId = string.Empty;
|
||||
public string EqDeviceId
|
||||
{
|
||||
get => _eqDeviceId;
|
||||
set => SetProperty(ref _eqDeviceId, value);
|
||||
}
|
||||
|
||||
private string _eqServerHost = "127.0.0.1";
|
||||
public string EqServerHost
|
||||
{
|
||||
get => _eqServerHost;
|
||||
set => SetProperty(ref _eqServerHost, value);
|
||||
}
|
||||
|
||||
private int _eqServerPort = 1433;
|
||||
public int EqServerPort
|
||||
{
|
||||
get => _eqServerPort;
|
||||
set => SetProperty(ref _eqServerPort, value);
|
||||
}
|
||||
|
||||
private string _eqDbName = "MES_ShareDB";
|
||||
public string EqDbName
|
||||
{
|
||||
get => _eqDbName;
|
||||
set => SetProperty(ref _eqDbName, value);
|
||||
}
|
||||
|
||||
private string _eqUsername = "sa";
|
||||
public string EqUsername
|
||||
{
|
||||
get => _eqUsername;
|
||||
set => SetProperty(ref _eqUsername, value);
|
||||
}
|
||||
|
||||
private string _eqPassword = string.Empty;
|
||||
public string EqPassword
|
||||
{
|
||||
get => _eqPassword;
|
||||
set => SetProperty(ref _eqPassword, value);
|
||||
}
|
||||
|
||||
public DelegateCommand SaveCommand { get; }
|
||||
public DelegateCommand CancelCommand { get; }
|
||||
public DialogCloseListener RequestClose { get; private set; }
|
||||
@@ -91,6 +128,14 @@ namespace YY.Admin.ViewModels.Dialogs
|
||||
BasePath = string.IsNullOrWhiteSpace(settings.BasePath) ? "/jeecg-boot" : settings.BasePath;
|
||||
DisconnectConnection = settings.DisconnectConnection;
|
||||
ErrorMessage = string.Empty;
|
||||
|
||||
var eq = EquipmentDbSettingsStore.Load();
|
||||
EqDeviceId = string.IsNullOrWhiteSpace(eq.DeviceId) ? Environment.MachineName : eq.DeviceId;
|
||||
EqServerHost = eq.ServerHost;
|
||||
EqServerPort = eq.ServerPort;
|
||||
EqDbName = eq.DbName;
|
||||
EqUsername = eq.Username;
|
||||
EqPassword = eq.Password;
|
||||
}
|
||||
|
||||
private void Save()
|
||||
@@ -106,6 +151,11 @@ namespace YY.Admin.ViewModels.Dialogs
|
||||
ErrorMessage = "端口号必须在 1-65535 之间";
|
||||
return;
|
||||
}
|
||||
if (EqServerPort <= 0 || EqServerPort > 65535)
|
||||
{
|
||||
ErrorMessage = "设备库端口号必须在 1-65535 之间";
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -127,6 +177,19 @@ namespace YY.Admin.ViewModels.Dialogs
|
||||
WebSocketPath = DefaultWebSocketPath,
|
||||
DisconnectConnection = DisconnectConnection
|
||||
});
|
||||
|
||||
EquipmentDbSettingsStore.Save(new EquipmentDbSettingsStore.EquipmentDbSettings
|
||||
{
|
||||
Enabled = true,
|
||||
DeviceId = string.IsNullOrWhiteSpace(EqDeviceId) ? Environment.MachineName : EqDeviceId.Trim(),
|
||||
ServerHost = EqServerHost?.Trim() ?? "127.0.0.1",
|
||||
ServerPort = EqServerPort,
|
||||
DbName = EqDbName?.Trim() ?? "MES_ShareDB",
|
||||
Username = EqUsername?.Trim() ?? "sa",
|
||||
Password = EqPassword ?? string.Empty,
|
||||
ConnectTimeoutSeconds = 15
|
||||
});
|
||||
|
||||
RequestClose.Invoke(new DialogResult(ButtonResult.OK));
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using HandyControl.Controls;
|
||||
using YY.Admin.Core;
|
||||
using YY.Admin.Core.Helper;
|
||||
using YY.Admin.Services.Service.EquipmentDb;
|
||||
|
||||
namespace YY.Admin.ViewModels.McsActMill;
|
||||
|
||||
public class McsActMillListViewModel : BaseViewModel
|
||||
{
|
||||
private readonly IMcsActMillService _service;
|
||||
|
||||
private ObservableCollection<McsActMillRow> _items = new();
|
||||
public ObservableCollection<McsActMillRow> Items
|
||||
{
|
||||
get => _items;
|
||||
set => SetProperty(ref _items, 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? _filterActName;
|
||||
public string? FilterActName { get => _filterActName; set => SetProperty(ref _filterActName, value); }
|
||||
|
||||
private string? _filterActAddrText;
|
||||
public string? FilterActAddrText { get => _filterActAddrText; set => SetProperty(ref _filterActAddrText, value); }
|
||||
|
||||
public DelegateCommand SearchCommand { get; }
|
||||
public DelegateCommand ResetCommand { get; }
|
||||
public DelegateCommand PrevPageCommand { get; }
|
||||
public DelegateCommand NextPageCommand { get; }
|
||||
|
||||
public McsActMillListViewModel(
|
||||
IMcsActMillService service,
|
||||
IContainerExtension container,
|
||||
IRegionManager regionManager) : base(container, regionManager)
|
||||
{
|
||||
_service = service;
|
||||
|
||||
SearchCommand = new DelegateCommand(async () => { PageNo = 1; await LoadAsync(); });
|
||||
ResetCommand = new DelegateCommand(async () =>
|
||||
{
|
||||
FilterActName = null;
|
||||
FilterActAddrText = null;
|
||||
PageNo = 1;
|
||||
await LoadAsync();
|
||||
});
|
||||
PrevPageCommand = new DelegateCommand(async () => { if (PageNo > 1) { PageNo--; await LoadAsync(); } });
|
||||
NextPageCommand = new DelegateCommand(async () => { if ((long)PageNo * PageSize < Total) { PageNo++; await LoadAsync(); } });
|
||||
|
||||
_ = InitializeAsync();
|
||||
}
|
||||
|
||||
private async Task InitializeAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await UIHelper.WaitForRenderAsync();
|
||||
await LoadAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"开炼机动作中间表初始化失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task LoadAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
int? actAddr = null;
|
||||
if (!string.IsNullOrWhiteSpace(FilterActAddrText)
|
||||
&& int.TryParse(FilterActAddrText.Trim(), out var parsed))
|
||||
{
|
||||
actAddr = parsed;
|
||||
}
|
||||
|
||||
var result = await _service.PageAsync(PageNo, PageSize, FilterActName, actAddr);
|
||||
Items = new ObservableCollection<McsActMillRow>(result.Records);
|
||||
Total = result.Total;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Growl.Error($"加载开炼机动作中间表失败:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Prism.Ioc;
|
||||
using Prism.Navigation.Regions;
|
||||
|
||||
namespace YY.Admin.ViewModels.UpperMes;
|
||||
|
||||
public class BomValidationListViewModel : BaseViewModel
|
||||
{
|
||||
public BomValidationListViewModel(IContainerExtension container, IRegionManager regionManager)
|
||||
: base(container, regionManager)
|
||||
{
|
||||
Title = "bom验证";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Prism.Ioc;
|
||||
using Prism.Navigation.Regions;
|
||||
|
||||
namespace YY.Admin.ViewModels.UpperMes;
|
||||
|
||||
public class EquipmentDowntimeListViewModel : BaseViewModel
|
||||
{
|
||||
public EquipmentDowntimeListViewModel(IContainerExtension container, IRegionManager regionManager)
|
||||
: base(container, regionManager)
|
||||
{
|
||||
Title = "设备停机记录";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Prism.Ioc;
|
||||
using Prism.Navigation.Regions;
|
||||
|
||||
namespace YY.Admin.ViewModels.UpperMes;
|
||||
|
||||
public class FormulaMaintainListViewModel : BaseViewModel
|
||||
{
|
||||
public FormulaMaintainListViewModel(IContainerExtension container, IRegionManager regionManager)
|
||||
: base(container, regionManager)
|
||||
{
|
||||
Title = "配方维护";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Prism.Ioc;
|
||||
using Prism.Navigation.Regions;
|
||||
|
||||
namespace YY.Admin.ViewModels.UpperMes;
|
||||
|
||||
public class PlanManageListViewModel : BaseViewModel
|
||||
{
|
||||
public PlanManageListViewModel(IContainerExtension container, IRegionManager regionManager)
|
||||
: base(container, regionManager)
|
||||
{
|
||||
Title = "计划管理";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Prism.Ioc;
|
||||
using Prism.Navigation.Regions;
|
||||
|
||||
namespace YY.Admin.ViewModels.UpperMes;
|
||||
|
||||
public class ProductionLogListViewModel : BaseViewModel
|
||||
{
|
||||
public ProductionLogListViewModel(IContainerExtension container, IRegionManager regionManager)
|
||||
: base(container, regionManager)
|
||||
{
|
||||
Title = "生产日志";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Prism.Ioc;
|
||||
using Prism.Navigation.Regions;
|
||||
|
||||
namespace YY.Admin.ViewModels.UpperMes;
|
||||
|
||||
public class ReportQueryListViewModel : BaseViewModel
|
||||
{
|
||||
public ReportQueryListViewModel(IContainerExtension container, IRegionManager regionManager)
|
||||
: base(container, regionManager)
|
||||
{
|
||||
Title = "报表查询";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Prism.Ioc;
|
||||
using Prism.Navigation.Regions;
|
||||
|
||||
namespace YY.Admin.ViewModels.UpperMes;
|
||||
|
||||
public class ShiftHandoverListViewModel : BaseViewModel
|
||||
{
|
||||
public ShiftHandoverListViewModel(IContainerExtension container, IRegionManager regionManager)
|
||||
: base(container, regionManager)
|
||||
{
|
||||
Title = "交接班";
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,20 @@
|
||||
Style="{StaticResource ToggleButtonSwitch}"/>
|
||||
</DockPanel>
|
||||
|
||||
<TextBlock Text="设备库(厂区 SQL Server)" FontWeight="SemiBold" Margin="0,18,0,8"/>
|
||||
<TextBlock Text="设备 ID" Margin="0,0,0,6"/>
|
||||
<TextBox Text="{Binding EqDeviceId, UpdateSourceTrigger=PropertyChanged}" Height="32"/>
|
||||
<TextBlock Text="服务器" Margin="0,12,0,6"/>
|
||||
<TextBox Text="{Binding EqServerHost, UpdateSourceTrigger=PropertyChanged}" Height="32"/>
|
||||
<TextBlock Text="端口" Margin="0,12,0,6"/>
|
||||
<TextBox Text="{Binding EqServerPort, UpdateSourceTrigger=PropertyChanged}" Height="32"/>
|
||||
<TextBlock Text="数据库" Margin="0,12,0,6"/>
|
||||
<TextBox Text="{Binding EqDbName, UpdateSourceTrigger=PropertyChanged}" Height="32"/>
|
||||
<TextBlock Text="用户名" Margin="0,12,0,6"/>
|
||||
<TextBox Text="{Binding EqUsername, UpdateSourceTrigger=PropertyChanged}" Height="32"/>
|
||||
<TextBlock Text="密码" Margin="0,12,0,6"/>
|
||||
<PasswordBox x:Name="EqPasswordBox" Height="32" PasswordChanged="EqPasswordBox_OnPasswordChanged"/>
|
||||
|
||||
<TextBlock Text="{Binding ErrorMessage}" Foreground="Red" Margin="0,12,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using YY.Admin.ViewModels.Dialogs;
|
||||
|
||||
namespace YY.Admin.Views.Dialogs
|
||||
{
|
||||
@@ -10,6 +12,23 @@ namespace YY.Admin.Views.Dialogs
|
||||
public ServerSettingsDialogView()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContextChanged += OnDataContextChanged;
|
||||
}
|
||||
|
||||
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (DataContext is ServerSettingsDialogViewModel vm && !string.IsNullOrEmpty(vm.EqPassword))
|
||||
{
|
||||
EqPasswordBox.Password = vm.EqPassword;
|
||||
}
|
||||
}
|
||||
|
||||
private void EqPasswordBox_OnPasswordChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ServerSettingsDialogViewModel vm)
|
||||
{
|
||||
vm.EqPassword = EqPasswordBox.Password;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
<UserControl x:Class="YY.Admin.Views.McsActMill.McsActMillListView"
|
||||
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 FilterActName, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="0 0 10 10"
|
||||
hc:InfoElement.Title="动作名称"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
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 FilterActAddrText, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="0 0 10 10"
|
||||
hc:InfoElement.Title="动作地址"
|
||||
hc:InfoElement.TitlePlacement="Left"
|
||||
hc:InfoElement.TitleWidth="80"
|
||||
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>
|
||||
<TextBlock Text="数据直连设备库中间表 MCSToMES_Act_Mill(只读)"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource SecondaryTextBrush}"
|
||||
FontSize="12"/>
|
||||
</hc:UniformSpacingPanel>
|
||||
</Border>
|
||||
|
||||
<DataGrid Grid.Row="2"
|
||||
ItemsSource="{Binding Items}"
|
||||
AutoGenerateColumns="False"
|
||||
IsReadOnly="True"
|
||||
CanUserAddRows="False"
|
||||
SelectionMode="Single"
|
||||
GridLinesVisibility="Horizontal"
|
||||
HorizontalGridLinesBrush="#FFEDEDED"
|
||||
VerticalGridLinesBrush="Transparent"
|
||||
HeadersVisibility="All"
|
||||
ColumnHeaderStyle="{StaticResource CusDataGridColumnHeaderStyle}"
|
||||
Style="{StaticResource CusDataGridStyle}"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Auto">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="GUID" Binding="{Binding Guid}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="280"/>
|
||||
<DataGridTextColumn Header="动作地址" Binding="{Binding ActAddr}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="90"/>
|
||||
<DataGridTextColumn Header="动作名称" Binding="{Binding ActName}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="140"/>
|
||||
<DataGridTextColumn Header="动作名称(英)" Binding="{Binding ActNameEn}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="140"/>
|
||||
<DataGridTextColumn Header="动作备注" Binding="{Binding ActMemo}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="120"/>
|
||||
<DataGridTextColumn Header="关联地址" Binding="{Binding ActRepaddr}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="90"/>
|
||||
<DataGridTextColumn Header="写入时间" Binding="{Binding WriteTime, StringFormat={}{0:yyyy-MM-dd HH:mm:ss}}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="160"/>
|
||||
<DataGridTextColumn Header="读取时间" Binding="{Binding ReadTime, StringFormat={}{0:yyyy-MM-dd HH:mm:ss}}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="160"/>
|
||||
<DataGridTextColumn Header="读写标识" Binding="{Binding RwFlag}" CellStyle="{StaticResource CusDataGridCellStyle}" Width="80"/>
|
||||
</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>
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace YY.Admin.Views.McsActMill;
|
||||
|
||||
public partial class McsActMillListView : UserControl
|
||||
{
|
||||
public McsActMillListView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<UserControl x:Class="YY.Admin.Views.UpperMes.BomValidationListView"
|
||||
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:prism="http://prismlibrary.com/"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
mc:Ignorable="d">
|
||||
<Grid Style="{StaticResource BaseViewStyle}">
|
||||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock Text="{Binding Title}" FontSize="22" FontWeight="SemiBold" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="功能开发中" Margin="0 16 0 0" FontSize="14" Opacity="0.65" HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace YY.Admin.Views.UpperMes;
|
||||
|
||||
public partial class BomValidationListView : UserControl
|
||||
{
|
||||
public BomValidationListView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<UserControl x:Class="YY.Admin.Views.UpperMes.EquipmentDowntimeListView"
|
||||
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:prism="http://prismlibrary.com/"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
mc:Ignorable="d">
|
||||
<Grid Style="{StaticResource BaseViewStyle}">
|
||||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock Text="{Binding Title}" FontSize="22" FontWeight="SemiBold" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="功能开发中" Margin="0 16 0 0" FontSize="14" Opacity="0.65" HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace YY.Admin.Views.UpperMes;
|
||||
|
||||
public partial class EquipmentDowntimeListView : UserControl
|
||||
{
|
||||
public EquipmentDowntimeListView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<UserControl x:Class="YY.Admin.Views.UpperMes.FormulaMaintainListView"
|
||||
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:prism="http://prismlibrary.com/"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
mc:Ignorable="d">
|
||||
<Grid Style="{StaticResource BaseViewStyle}">
|
||||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock Text="{Binding Title}" FontSize="22" FontWeight="SemiBold" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="功能开发中" Margin="0 16 0 0" FontSize="14" Opacity="0.65" HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace YY.Admin.Views.UpperMes;
|
||||
|
||||
public partial class FormulaMaintainListView : UserControl
|
||||
{
|
||||
public FormulaMaintainListView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<UserControl x:Class="YY.Admin.Views.UpperMes.PlanManageListView"
|
||||
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:prism="http://prismlibrary.com/"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
mc:Ignorable="d">
|
||||
<Grid Style="{StaticResource BaseViewStyle}">
|
||||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock Text="{Binding Title}" FontSize="22" FontWeight="SemiBold" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="功能开发中" Margin="0 16 0 0" FontSize="14" Opacity="0.65" HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace YY.Admin.Views.UpperMes;
|
||||
|
||||
public partial class PlanManageListView : UserControl
|
||||
{
|
||||
public PlanManageListView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<UserControl x:Class="YY.Admin.Views.UpperMes.ProductionLogListView"
|
||||
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:prism="http://prismlibrary.com/"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
mc:Ignorable="d">
|
||||
<Grid Style="{StaticResource BaseViewStyle}">
|
||||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock Text="{Binding Title}" FontSize="22" FontWeight="SemiBold" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="功能开发中" Margin="0 16 0 0" FontSize="14" Opacity="0.65" HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace YY.Admin.Views.UpperMes;
|
||||
|
||||
public partial class ProductionLogListView : UserControl
|
||||
{
|
||||
public ProductionLogListView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<UserControl x:Class="YY.Admin.Views.UpperMes.ReportQueryListView"
|
||||
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:prism="http://prismlibrary.com/"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
mc:Ignorable="d">
|
||||
<Grid Style="{StaticResource BaseViewStyle}">
|
||||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock Text="{Binding Title}" FontSize="22" FontWeight="SemiBold" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="功能开发中" Margin="0 16 0 0" FontSize="14" Opacity="0.65" HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace YY.Admin.Views.UpperMes;
|
||||
|
||||
public partial class ReportQueryListView : UserControl
|
||||
{
|
||||
public ReportQueryListView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<UserControl x:Class="YY.Admin.Views.UpperMes.ShiftHandoverListView"
|
||||
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:prism="http://prismlibrary.com/"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
mc:Ignorable="d">
|
||||
<Grid Style="{StaticResource BaseViewStyle}">
|
||||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock Text="{Binding Title}" FontSize="22" FontWeight="SemiBold" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="功能开发中" Margin="0 16 0 0" FontSize="14" Opacity="0.65" HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace YY.Admin.Views.UpperMes;
|
||||
|
||||
public partial class ShiftHandoverListView : UserControl
|
||||
{
|
||||
public ShiftHandoverListView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -135,8 +135,9 @@ var ensureNeverExpireConfig = conn.CreateCommand();
|
||||
ensureNeverExpireConfig.Transaction = tx;
|
||||
ensureNeverExpireConfig.CommandText = @"
|
||||
INSERT INTO sys_config (id, name, code, value, sys_flag, remark, order_no, group_code, create_time)
|
||||
SELECT 1300000000194, '登录永不过期', 'sys_token_never_expire', 'False', 'Y', '桌面端:开启后不触发登录过期提示与自动踢回登录页', 119, 'Default', datetime('now')
|
||||
SELECT 1300000000194, '登录永不过期', 'sys_token_never_expire', 'True', 'Y', '桌面端:开启后不触发登录过期提示与自动踢回登录页', 119, 'Default', datetime('now')
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_config WHERE code = 'sys_token_never_expire');
|
||||
UPDATE sys_config SET value = 'True' WHERE code = 'sys_token_never_expire' AND IFNULL(value,'') <> 'True';
|
||||
";
|
||||
ensureNeverExpireConfig.ExecuteNonQuery();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user