生产模块拆分为前缫/后缫;修复菜单卡顿与重复请求;支持 IIS 单站点部署
1. 生产模块按工序拆分为两个一级菜单 - 生产-前缫(落丝记录)、生产-后缫(纤度检验、黑板检验) - 旧的「生产管理」(/prod) 目录及子菜单由种子自动清理,页面组件与权限标识保持不变 - 消息中心跳转路径、首页快捷入口路径同步更新 2. 职工管理独立为一级菜单(原挂在「基础数据」目录下),并清理历史子菜单 3. 修复侧边栏菜单展开卡顿(基础数据等子项较多的目录尤其明显) - 去掉 Element Plus 的 height 过渡动画(会逐帧重排全部子项) - 收起时改用 height:0 + visibility:hidden 而非 display:none,子项提前完成布局 - 菜单区独立滚动并预留滚动条宽度(scrollbar-gutter),消除展开时的横向抖动 - 菜单项增加 contain 布局隔离,淡入动画改为容器级 4. 修复页面首次打开重复请求 - 分页组件挂载时会触发一次 current-change,页码未变化时不再重复加载(18 个页面) - 职工管理去掉 onMounted 与默认选中部门的重复加载 - 工种管理、原料庄口的注册表下拉由按 key 多次请求改为一次全量拉取 5. 后端增加前端静态托管与 SPA 路由回退,支持 IIS 单站点部署(前后端同源) 6. 新增部署脚本 deploy-iis.ps1(首次部署)与 update-iis.ps1(更新发布)
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using FreeSql;
|
||||
using F9WebAiTest.Domain.Entities.SYS.Departments;
|
||||
using F9WebAiTest.Domain.Entities.SYS.Employees;
|
||||
using F9WebAiTest.Domain.Enums.SYS.Employees;
|
||||
|
||||
namespace F9WebAiTest.Infrastructure.Data;
|
||||
|
||||
/// <summary>
|
||||
/// 职工数据导入:来源于 F9 系统 biz_base_employee / biz_base_employee_Change_record /
|
||||
/// biz_base_employee_tiaogang_record 三张表的导出数据。
|
||||
/// 幂等:按原始 Id 判重;F9 部门 Id 通过"部门名称"映射到本系统部门(工种 Id 与 F9 一致,直接沿用)。
|
||||
/// </summary>
|
||||
public static class EmployeeSeedData
|
||||
{
|
||||
private const string EmployeeResource = "F9WebAiTest.Infrastructure.Data.SeedData.biz_base_employee.json";
|
||||
private const string ChangeResource = "F9WebAiTest.Infrastructure.Data.SeedData.biz_base_employee_Change_record.json";
|
||||
private const string TiaogangResource = "F9WebAiTest.Infrastructure.Data.SeedData.biz_base_employee_tiaogang_record.json";
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true };
|
||||
|
||||
public static void Seed(IFreeSql fsql)
|
||||
{
|
||||
try
|
||||
{
|
||||
var depMap = BuildDepartmentIdMap(fsql);
|
||||
SeedEmployees(fsql, depMap);
|
||||
SeedChangeRecords(fsql);
|
||||
SeedTiaogangRecords(fsql, depMap);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[EmployeeSeed] 职工数据导入跳过:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>导入职工档案</summary>
|
||||
private static void SeedEmployees(IFreeSql fsql, Dictionary<long, long> depMap)
|
||||
{
|
||||
var items = Load<EmployeeSeedItem>(EmployeeResource);
|
||||
if (items.Count == 0) return;
|
||||
|
||||
var existIds = fsql.Select<Employee>().DisableGlobalFilter("软删除").ToList().Select(x => x.Id).ToHashSet();
|
||||
|
||||
var inserts = new List<Employee>();
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item.Id <= 0 || existIds.Contains(item.Id)) continue;
|
||||
if (string.IsNullOrWhiteSpace(item.BaseName)) continue;
|
||||
|
||||
var depId = depMap.TryGetValue(item.BaseDepId, out var mapped) ? mapped : item.BaseDepId;
|
||||
|
||||
inserts.Add(new Employee(item.Id, depId, item.BaseWorktypeId,
|
||||
(Employee_WorkStatus_Enum)item.BaseWorkStatus, item.BaseCode ?? string.Empty, item.BaseName.Trim(),
|
||||
item.BaseGangwei ?? string.Empty, (item.BasePhone ?? string.Empty).Trim(),
|
||||
(item.BaseIdCard ?? string.Empty).Trim(), ParseLegacyDate(item.BaseBirnDate), item.BaseAddress ?? string.Empty,
|
||||
(item.BaseGender ?? string.Empty).Trim(), (item.BaseMinzu ?? string.Empty).Trim(),
|
||||
item.BaseHunyinStatus ?? string.Empty, item.BaseHuji ?? string.Empty, ParseLegacyDate(item.BaseRuzhiDate),
|
||||
ParseLegacyDate(item.BaseLizhiDate), ParseLegacyDate(item.BaseZhuanzhengDate), ParseBool(item.BaseIsZhuanzheng),
|
||||
item.BaseJinjiLianxiren ?? string.Empty, item.BaseJinjiLianxirenPhone ?? string.Empty,
|
||||
item.BaseHetongType ?? string.Empty, ParseLegacyDate(item.BaseLaodongHetongStartDate),
|
||||
ParseLegacyDate(item.BaseLaodongHetongEndDate), ParseLegacyDate(item.BaseTuixiuDate),
|
||||
ParseLegacyDate(item.BaseShebaoCanbaoDate), item.BaseShebaocanbaoDesc ?? string.Empty,
|
||||
(item.BaseBanckcardCode ?? string.Empty).Trim(), item.KzBiyeYuanxiao ?? string.Empty, item.KbZhuanye ?? string.Empty,
|
||||
item.KbXueli ?? string.Empty, item.ScBanci.HasValue ? (Prod_Banci_Enum)item.ScBanci.Value : null,
|
||||
item.ScWorkGroup ?? string.Empty, item.ScSort, item.NamePinyin ?? string.Empty, item.Memo ?? string.Empty,
|
||||
item.BaseLizhiYuanyin ?? string.Empty)
|
||||
{
|
||||
CreatedAt = ParseLegacyDate(item.CreatedAt) ?? DateTime.Now,
|
||||
CreatedBy = item.CreatedBy ?? string.Empty,
|
||||
UpdatedAt = ParseLegacyDate(item.UpdatedAt),
|
||||
UpdateBy = item.UpdateBy ?? string.Empty
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var batch in inserts.Chunk(200)) fsql.Insert(batch.ToList()).ExecuteAffrows();
|
||||
Console.WriteLine($"[EmployeeSeed] 职工档案:来源 {items.Count} 条,新增 {inserts.Count} 条");
|
||||
}
|
||||
|
||||
/// <summary>导入变更记录</summary>
|
||||
private static void SeedChangeRecords(IFreeSql fsql)
|
||||
{
|
||||
var items = Load<ChangeRecordSeedItem>(ChangeResource);
|
||||
if (items.Count == 0) return;
|
||||
|
||||
var existIds = fsql.Select<EmployeeChangeRecord>().DisableGlobalFilter("软删除").ToList().Select(x => x.Id).ToHashSet();
|
||||
var employeeIds = fsql.Select<Employee>().DisableGlobalFilter("软删除").ToList().Select(x => x.Id).ToHashSet();
|
||||
|
||||
var inserts = new List<EmployeeChangeRecord>();
|
||||
var orphan = 0;
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item.Id <= 0 || existIds.Contains(item.Id)) continue;
|
||||
if (!employeeIds.Contains(item.EmployeeId)) { orphan++; continue; }
|
||||
|
||||
inserts.Add(new EmployeeChangeRecord(item.Id, item.EmployeeId, ParseLegacyDate(item.Date) ?? DateTime.Now,
|
||||
(Employee_ChangeStatus_Enum)item.ChangeStatus, item.ChangeShuoming ?? string.Empty,
|
||||
item.ChangeYuanyin ?? string.Empty)
|
||||
{
|
||||
CreatedAt = ParseLegacyDate(item.CreatedAt) ?? DateTime.Now,
|
||||
CreatedBy = item.CreatedBy ?? string.Empty
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var batch in inserts.Chunk(200)) fsql.Insert(batch.ToList()).ExecuteAffrows();
|
||||
Console.WriteLine($"[EmployeeSeed] 变更记录:来源 {items.Count} 条,新增 {inserts.Count} 条,孤儿 {orphan} 条");
|
||||
}
|
||||
|
||||
/// <summary>导入调岗记录</summary>
|
||||
private static void SeedTiaogangRecords(IFreeSql fsql, Dictionary<long, long> depMap)
|
||||
{
|
||||
var items = Load<TiaogangRecordSeedItem>(TiaogangResource);
|
||||
if (items.Count == 0) return;
|
||||
|
||||
var existIds = fsql.Select<EmployeeTiaogangRecord>().DisableGlobalFilter("软删除").ToList().Select(x => x.Id).ToHashSet();
|
||||
var employeeIds = fsql.Select<Employee>().DisableGlobalFilter("软删除").ToList().Select(x => x.Id).ToHashSet();
|
||||
|
||||
var inserts = new List<EmployeeTiaogangRecord>();
|
||||
var orphan = 0;
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item.Id <= 0 || existIds.Contains(item.Id)) continue;
|
||||
if (!employeeIds.Contains(item.EmployeeId)) { orphan++; continue; }
|
||||
|
||||
inserts.Add(new EmployeeTiaogangRecord(item.Id, item.EmployeeId,
|
||||
depMap.TryGetValue(item.OldDepId, out var od) ? od : item.OldDepId, item.OldWorktypeId,
|
||||
depMap.TryGetValue(item.NewDepId, out var nd) ? nd : item.NewDepId, item.NewWorktypeId,
|
||||
ParseLegacyDate(item.TiaogangDate) ?? DateTime.Now)
|
||||
{
|
||||
CreatedAt = ParseLegacyDate(item.CreatedAt) ?? DateTime.Now,
|
||||
CreatedBy = item.CreatedBy ?? string.Empty
|
||||
});
|
||||
}
|
||||
|
||||
if (inserts.Count > 0) fsql.Insert(inserts).ExecuteAffrows();
|
||||
Console.WriteLine($"[EmployeeSeed] 调岗记录:来源 {items.Count} 条,新增 {inserts.Count} 条,孤儿 {orphan} 条");
|
||||
}
|
||||
|
||||
/// <summary>F9 部门 Id → 本系统部门 Id(以部门名称为桥梁,同工种模块的处理方式)</summary>
|
||||
private static Dictionary<long, long> BuildDepartmentIdMap(IFreeSql fsql)
|
||||
{
|
||||
var map = new Dictionary<long, long>();
|
||||
|
||||
var byName = new Dictionary<string, long>();
|
||||
foreach (var dep in fsql.Select<Department>().ToList())
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(dep.Name)) continue;
|
||||
if (!byName.ContainsKey(dep.Name)) byName[dep.Name] = dep.Id;
|
||||
}
|
||||
|
||||
foreach (var (f9Id, name, _, _, _) in BusinessSeedData.DeptData)
|
||||
{
|
||||
if (byName.TryGetValue(name, out var localId)) map[f9Id] = localId;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private static List<T> Load<T>(string resourceName)
|
||||
{
|
||||
var assembly = typeof(EmployeeSeedData).Assembly;
|
||||
using var stream = assembly.GetManifestResourceStream(resourceName);
|
||||
if (stream == null)
|
||||
{
|
||||
Console.WriteLine($"[EmployeeSeed] 未找到内嵌资源:{resourceName}");
|
||||
return new List<T>();
|
||||
}
|
||||
return JsonSerializer.Deserialize<List<T>>(stream, JsonOptions) ?? new List<T>();
|
||||
}
|
||||
|
||||
/// <summary>兼容 "0"/"1" 字符串、数字与布尔写法</summary>
|
||||
private static bool ParseBool(JsonElement element)
|
||||
{
|
||||
switch (element.ValueKind)
|
||||
{
|
||||
case JsonValueKind.String:
|
||||
var text = element.GetString();
|
||||
return text == "1" || string.Equals(text, "true", StringComparison.OrdinalIgnoreCase);
|
||||
case JsonValueKind.Number:
|
||||
return element.GetInt32() != 0;
|
||||
case JsonValueKind.True:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly string[] LegacyDateFormats =
|
||||
{
|
||||
"d/M/yyyy H:mm:ss.fff", "d/M/yyyy H:mm:ss", "d/M/yyyy H:mm", "d/M/yyyy",
|
||||
"M/d/yyyy H:mm:ss.fff", "M/d/yyyy H:mm:ss", "M/d/yyyy H:mm", "M/d/yyyy",
|
||||
"yyyy-MM-dd HH:mm:ss.fff", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd", "yyyy/M/d"
|
||||
};
|
||||
|
||||
/// <summary>解析 F9 导出的日期,空值与 1970 占位值返回 null</summary>
|
||||
private static DateTime? ParseLegacyDate(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) return null;
|
||||
|
||||
if (DateTime.TryParseExact(value, LegacyDateFormats, CultureInfo.InvariantCulture, DateTimeStyles.None, out var exact))
|
||||
return exact.Year <= 1900 ? null : exact;
|
||||
|
||||
if (DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var any))
|
||||
return any.Year <= 1900 ? null : any;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private sealed class EmployeeSeedItem
|
||||
{
|
||||
[JsonPropertyName("id")] public long Id { get; set; }
|
||||
[JsonPropertyName("created_by")] public string CreatedBy { get; set; }
|
||||
[JsonPropertyName("created_at")] public string CreatedAt { get; set; }
|
||||
[JsonPropertyName("update_by")] public string UpdateBy { get; set; }
|
||||
[JsonPropertyName("updated_at")] public string UpdatedAt { get; set; }
|
||||
[JsonPropertyName("base_depid")] public long BaseDepId { get; set; }
|
||||
[JsonPropertyName("base_worktypeid")] public long BaseWorktypeId { get; set; }
|
||||
[JsonPropertyName("base_workstatus")] public int BaseWorkStatus { get; set; }
|
||||
[JsonPropertyName("base_code")] public string BaseCode { get; set; }
|
||||
[JsonPropertyName("base_name")] public string BaseName { get; set; }
|
||||
[JsonPropertyName("base_gangwei")] public string BaseGangwei { get; set; }
|
||||
[JsonPropertyName("base_phone")] public string BasePhone { get; set; }
|
||||
[JsonPropertyName("base_idcard")] public string BaseIdCard { get; set; }
|
||||
[JsonPropertyName("base_birndate")] public string BaseBirnDate { get; set; }
|
||||
[JsonPropertyName("base_address")] public string BaseAddress { get; set; }
|
||||
[JsonPropertyName("base_gender")] public string BaseGender { get; set; }
|
||||
[JsonPropertyName("base_minzu")] public string BaseMinzu { get; set; }
|
||||
[JsonPropertyName("base_hunyinstatus")] public string BaseHunyinStatus { get; set; }
|
||||
[JsonPropertyName("base_huji")] public string BaseHuji { get; set; }
|
||||
[JsonPropertyName("base_ruzhidate")] public string BaseRuzhiDate { get; set; }
|
||||
[JsonPropertyName("base_lizhidate")] public string BaseLizhiDate { get; set; }
|
||||
[JsonPropertyName("base_zhuanzhengdate")] public string BaseZhuanzhengDate { get; set; }
|
||||
[JsonPropertyName("base_iszhuanzheng")] public JsonElement BaseIsZhuanzheng { get; set; }
|
||||
[JsonPropertyName("base_jinjilianxiren")] public string BaseJinjiLianxiren { get; set; }
|
||||
[JsonPropertyName("base_jinjilianxiren_phone")] public string BaseJinjiLianxirenPhone { get; set; }
|
||||
[JsonPropertyName("base_hetongtype")] public string BaseHetongType { get; set; }
|
||||
[JsonPropertyName("base_laodonghetong_startdate")] public string BaseLaodongHetongStartDate { get; set; }
|
||||
[JsonPropertyName("base_laodonghetong_enddate")] public string BaseLaodongHetongEndDate { get; set; }
|
||||
[JsonPropertyName("base_tuixiu_date")] public string BaseTuixiuDate { get; set; }
|
||||
[JsonPropertyName("base_shebaocanbao_date")] public string BaseShebaoCanbaoDate { get; set; }
|
||||
[JsonPropertyName("base_shebaocanbao_desc")] public string BaseShebaocanbaoDesc { get; set; }
|
||||
[JsonPropertyName("base_banckcardcode")] public string BaseBanckcardCode { get; set; }
|
||||
[JsonPropertyName("kz_biyeyuanxiao")] public string KzBiyeYuanxiao { get; set; }
|
||||
[JsonPropertyName("kb_zhuanye")] public string KbZhuanye { get; set; }
|
||||
[JsonPropertyName("kb_xueli")] public string KbXueli { get; set; }
|
||||
[JsonPropertyName("sc_banci")] public int? ScBanci { get; set; }
|
||||
[JsonPropertyName("sc_workgroup")] public string ScWorkGroup { get; set; }
|
||||
[JsonPropertyName("sc_sort")] public int ScSort { get; set; }
|
||||
[JsonPropertyName("name_pinyin")] public string NamePinyin { get; set; }
|
||||
[JsonPropertyName("memo")] public string Memo { get; set; }
|
||||
[JsonPropertyName("base_lizhiyuanyin")] public string BaseLizhiYuanyin { get; set; }
|
||||
}
|
||||
|
||||
private sealed class ChangeRecordSeedItem
|
||||
{
|
||||
[JsonPropertyName("id")] public long Id { get; set; }
|
||||
[JsonPropertyName("created_by")] public string CreatedBy { get; set; }
|
||||
[JsonPropertyName("created_at")] public string CreatedAt { get; set; }
|
||||
[JsonPropertyName("employee_id")] public long EmployeeId { get; set; }
|
||||
[JsonPropertyName("date")] public string Date { get; set; }
|
||||
[JsonPropertyName("change_status")] public int ChangeStatus { get; set; }
|
||||
[JsonPropertyName("change_shuoming")] public string ChangeShuoming { get; set; }
|
||||
[JsonPropertyName("change_yuanyin")] public string ChangeYuanyin { get; set; }
|
||||
}
|
||||
|
||||
private sealed class TiaogangRecordSeedItem
|
||||
{
|
||||
[JsonPropertyName("id")] public long Id { get; set; }
|
||||
[JsonPropertyName("created_by")] public string CreatedBy { get; set; }
|
||||
[JsonPropertyName("created_at")] public string CreatedAt { get; set; }
|
||||
[JsonPropertyName("employee_id")] public long EmployeeId { get; set; }
|
||||
[JsonPropertyName("old_depid")] public long OldDepId { get; set; }
|
||||
[JsonPropertyName("old_worktypeid")] public long OldWorktypeId { get; set; }
|
||||
[JsonPropertyName("new_depid")] public long NewDepId { get; set; }
|
||||
[JsonPropertyName("new_worktypeid")] public long NewWorktypeId { get; set; }
|
||||
[JsonPropertyName("tiaogang_date")] public string TiaogangDate { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user