Files
yanpeng200212 c3e2665257 生产模块拆分为前缫/后缫;修复菜单卡顿与重复请求;支持 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(更新发布)
2026-10-12 14:09:59 +08:00

281 lines
15 KiB
C#

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; }
}
}