生产模块拆分为前缫/后缫;修复菜单卡顿与重复请求;支持 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:
yanpeng200212
2026-10-12 14:09:59 +08:00
parent 2ffb83830c
commit c3e2665257
47 changed files with 59270 additions and 109 deletions
@@ -227,15 +227,21 @@ public static class BusinessSeedData
AddMenu(fsql, baseDirId, "生丝定级指标", "/base/dingjizhibiao", "base/dingjizhibiao/index", "TrendCharts", "base:dingjizhibiao:list", 3);
AddMenu(fsql, baseDirId, "机组车台", "/base/jizuchetai", "base/jizuchetai/index", "SetUp", "base:jizuchetai:list", 4);
// 生产管理目录
var prodDirId = fsql.Select<Menu>().Where(m => m.Path == "/prod").First()?.Id ?? 0;
if (prodDirId == 0)
// 生产模块按工序拆分为两个一级目录:生产-前缫(落丝)/ 生产-后缫(纤度、黑板检验)
var qianxianDirId = fsql.Select<Menu>().Where(m => m.Path == "/prod/qianxian").First()?.Id ?? 0;
if (qianxianDirId == 0)
{
prodDirId = DbSeeder.InsertAndGetId(fsql, new Menu { Name = "生产管理", Type = "directory", Path = "/prod", Icon = "Cpu", Sort = 4 });
qianxianDirId = DbSeeder.InsertAndGetId(fsql, new Menu { Name = "生产-前缫", Type = "directory", Path = "/prod/qianxian", Icon = "Cpu", Sort = 5 });
}
AddMenu(fsql, prodDirId, "落丝记录", "/prod/lsjl", "prod/lsjl/index", "Document", "prod:lsjl:list", 1);
AddMenu(fsql, prodDirId, "纤度检验", "/prod/xdjy", "prod/xdjy/index", "EditPen", "prod:xdjy:list", 2);
AddMenu(fsql, prodDirId, "黑板检验", "/prod/hbjy", "prod/hbjy/index", "Notebook", "prod:hbjy:list", 3);
AddMenu(fsql, qianxianDirId, "落丝记录", "/prod/qianxian/lsjl", "prod/lsjl/index", "Document", "prod:lsjl:list", 1);
var houxianDirId = fsql.Select<Menu>().Where(m => m.Path == "/prod/houxian").First()?.Id ?? 0;
if (houxianDirId == 0)
{
houxianDirId = DbSeeder.InsertAndGetId(fsql, new Menu { Name = "生产-后缫", Type = "directory", Path = "/prod/houxian", Icon = "DataLine", Sort = 6 });
}
AddMenu(fsql, houxianDirId, "纤度检验", "/prod/houxian/xdjy", "prod/xdjy/index", "EditPen", "prod:xdjy:list", 1);
AddMenu(fsql, houxianDirId, "黑板检验", "/prod/houxian/hbjy", "prod/hbjy/index", "Notebook", "prod:hbjy:list", 2);
var adminRole = fsql.Select<Role>().Where(r => r.Code == "admin").First();
if (adminRole != null)
@@ -60,12 +60,13 @@ public static class DbSeeder
// 6.6 F9 风格 RBAC 菜单(用户/角色/菜单/部门管理,写入新 sys_rbac_menus 表)
SeedSysRbacMenus(fsql, snowflake);
// 6.65 基础数据(工种 + 注册表配置 + 原料庄口 + 物资 + 往来单位,F9 导出数据幂等导入)
// 6.65 基础数据(工种 + 注册表配置 + 原料庄口 + 物资 + 往来单位 + 职工,F9 导出数据幂等导入)
WorkTypeSeedData.Seed(fsql, snowflake);
RegistrySeedData.Seed(fsql);
YuanliaoZhuangkouSeedData.Seed(fsql);
WuziSeedData.Seed(fsql);
GongyingshangSeedData.Seed(fsql);
EmployeeSeedData.Seed(fsql);
// 6.66 基础数据菜单(工种管理、账期管理、注册表、原料庄口、物资目录、往来单位)
SeedWorkTypeMenus(fsql, snowflake);
@@ -75,6 +76,12 @@ public static class DbSeeder
SeedWuziMenus(fsql, snowflake);
SeedGongyingshangMenus(fsql, snowflake);
// 6.67 职工管理(一级菜单,与"基础数据"平级)
SeedEmployeeTopMenu(fsql, snowflake);
// 6.68 生产模块(按工序拆分:生产-前缫 / 生产-后缫)
SeedProdMenus(fsql, snowflake);
// 6.7 为菜单创建权限单元(Permission),并修正旧菜单权限关联(统一权限模型)
FixMenuPermissions(fsql, snowflake);
@@ -175,7 +182,7 @@ public static class DbSeeder
{
Code = "TeyeFound", Name = "发现特野", Description = "纤度检验发现特野时自动推送提醒",
RequiredPermission = "", IsActive = true, CreatedBy = 0, Sort = 1,
Category = NotificationCategory.Notification, Icon = "Warning", RouteTemplate = "/prod/xdjy"
Category = NotificationCategory.Notification, Icon = "Warning", RouteTemplate = "/prod/houxian/xdjy"
}).ExecuteAffrows();
}
@@ -186,7 +193,7 @@ public static class DbSeeder
{
Code = "TdcFound", Name = "发现特大糙", Description = "黑板检验发现特大糙时自动推送提醒",
RequiredPermission = "", IsActive = true, CreatedBy = 0, Sort = 2,
Category = NotificationCategory.Notification, Icon = "Warning", RouteTemplate = "/prod/hbjy"
Category = NotificationCategory.Notification, Icon = "Warning", RouteTemplate = "/prod/houxian/hbjy"
}).ExecuteAffrows();
}
@@ -572,6 +579,203 @@ public static class DbSeeder
}
}
/// <summary>
/// 职工管理菜单:独立的一级菜单(原作为"基础数据"的子菜单)。
/// 会清理历史遗留的 /base/employee 子菜单,并规范一级菜单排序,避免同一页面出现两个入口。
/// </summary>
private static void SeedEmployeeTopMenu(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
{
try
{
const string menuPath = "/employee";
const string menuIcon = "Avatar";
// 1) 清理历史:曾是"基础数据"下的子菜单
var legacy = fsql.Select<SysMenu>().Where(m => m.Path == "/base/employee").First();
if (legacy != null)
{
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_role_permissions WHERE permission_id = {legacy.Id}");
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_menus WHERE id = {legacy.Id}");
Console.WriteLine("[DbSeeder] 已清理历史子菜单 /base/employee");
}
// 2) 创建/校正一级菜单
var menu = fsql.Select<SysMenu>().Where(m => m.Path == menuPath).First();
if (menu == null)
{
menu = new SysMenu(snowflake.CreateId(), "职工管理", menuIcon, MenuType_Enum.页面, null, null, 0, 4, 0);
menu.SetWebInfo(menuPath, "base/employee/index", "base:employee:list");
fsql.Insert(menu).ExecuteAffrows();
}
else if (!menu.IsActive || menu.IconSvg != menuIcon || menu.ParentId != 0)
{
fsql.Update<SysMenu>()
.Set(x => x.IconSvg, menuIcon)
.Set(x => x.IsActive, true)
.Set(x => x.ParentId, 0)
.Set(x => x.Level, 1)
.Where(x => x.Id == menu.Id)
.ExecuteAffrows();
}
// 3) 规范一级菜单顺序:考勤管理 / 基础数据 / 职工管理 / 生产管理 / 消息中心 / 系统管理
foreach (var (path, sort) in new (string Path, int Sort)[]
{
("/attendance", 2), ("/base", 3), ("/employee", 4),
("/prod", 5), ("/notification", 6), ("/sys", 7)
})
{
fsql.Update<SysMenu>()
.Set(x => x.SortOrder, sort)
.Where(x => x.ParentId == 0 && x.Path == path && x.SortOrder != sort)
.ExecuteAffrows();
}
// 4) admin 角色授权(后续 FixMenuPermissions 会把 permission_id 修正为权限单元 Id)
var adminRole = fsql.Select<SysRole>().Where(r => r.Code == "admin").First();
if (adminRole != null)
{
var any = fsql.Select<SysRolePermission>()
.Where(x => x.RoleId == adminRole.Id && x.PermissionId == menu.Id)
.First();
if (any == null)
{
fsql.Insert(new SysRolePermission(snowflake.CreateId(), adminRole.Id, menu.Id)).ExecuteAffrows();
}
}
Console.WriteLine("[DbSeeder] 职工管理一级菜单已就绪");
}
catch (Exception ex)
{
Console.WriteLine($"[DbSeeder] 职工管理菜单种子跳过:{ex}");
}
}
/// <summary>
/// 生产模块菜单:按缫丝工序拆分为「生产-前缫」(落丝记录)与「生产-后缫」(纤度检验、黑板检验)两个一级菜单。
/// 会清理旧的「生产管理」(/prod) 目录及其子菜单;页面组件与权限标识保持不变。
/// </summary>
private static void SeedProdMenus(IFreeSql fsql, ISnowflakeIdGenerator snowflake)
{
try
{
// 1) 清理旧「生产管理」结构(精确匹配历史路径)
foreach (var legacyPath in new[] { "/prod", "/prod/lsjl", "/prod/xdjy", "/prod/hbjy" })
{
var legacy = fsql.Select<SysMenu>().Where(m => m.Path == legacyPath).First();
if (legacy == null) continue;
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_role_permissions WHERE permission_id = {legacy.Id}");
fsql.Ado.ExecuteNonQuery($"DELETE FROM sys_rbac_menus WHERE id = {legacy.Id}");
Console.WriteLine($"[DbSeeder] 已清理旧生产菜单 {legacyPath}");
}
// 2) 两个一级目录 + 各自页面菜单
EnsureTopMenuWithChildren(fsql, snowflake, "/prod/qianxian", "生产-前缫", "Cpu", 5,
new (string Name, string Path, string Component, string Permission, string Icon, int Sort)[]
{
("落丝记录", "/prod/qianxian/lsjl", "prod/lsjl/index", "prod:lsjl:list", "Document", 1)
});
EnsureTopMenuWithChildren(fsql, snowflake, "/prod/houxian", "生产-后缫", "DataLine", 6,
new (string Name, string Path, string Component, string Permission, string Icon, int Sort)[]
{
("纤度检验", "/prod/houxian/xdjy", "prod/xdjy/index", "prod:xdjy:list", "EditPen", 1),
("黑板检验", "/prod/houxian/hbjy", "prod/hbjy/index", "prod:hbjy:list", "Notebook", 2)
});
// 3) 规范一级菜单顺序
foreach (var (path, sort) in new (string Path, int Sort)[]
{
("/attendance", 2), ("/base", 3), ("/employee", 4),
("/prod/qianxian", 5), ("/prod/houxian", 6), ("/notification", 7), ("/sys", 8)
})
{
fsql.Update<SysMenu>()
.Set(x => x.SortOrder, sort)
.Where(x => x.ParentId == 0 && x.Path == path && x.SortOrder != sort)
.ExecuteAffrows();
}
// 4) 消息跳转路径同步到新路由
fsql.Ado.ExecuteNonQuery("UPDATE NotificationTypes SET RouteTemplate = '/prod/houxian/xdjy' WHERE Code = 'TeyeFound'");
fsql.Ado.ExecuteNonQuery("UPDATE NotificationTypes SET RouteTemplate = '/prod/houxian/hbjy' WHERE Code = 'TdcFound'");
Console.WriteLine("[DbSeeder] 生产模块菜单已就绪(生产-前缫 / 生产-后缫)");
}
catch (Exception ex)
{
Console.WriteLine($"[DbSeeder] 生产菜单种子跳过:{ex}");
}
}
/// <summary>
/// 确保一个一级目录菜单及其下属页面菜单存在(含父级、图标、启用状态校正),并给 admin 角色授权。
/// 注意:图标名必须是 Element Plus 图标组件名。
/// </summary>
private static void EnsureTopMenuWithChildren(IFreeSql fsql, ISnowflakeIdGenerator snowflake,
string dirPath, string dirName, string dirIcon, int dirSort,
(string Name, string Path, string Component, string Permission, string Icon, int Sort)[] children)
{
var dir = fsql.Select<SysMenu>().Where(m => m.Path == dirPath).First();
if (dir == null)
{
dir = new SysMenu(snowflake.CreateId(), dirName, dirIcon, MenuType_Enum.分组, null, null, 0, dirSort, 0);
dir.SetWebInfo(dirPath, "", "");
fsql.Insert(dir).ExecuteAffrows();
}
else if (!dir.IsActive || dir.IconSvg != dirIcon || dir.Name != dirName || dir.ParentId != 0)
{
fsql.Update<SysMenu>()
.Set(x => x.Name, dirName)
.Set(x => x.IconSvg, dirIcon)
.Set(x => x.IsActive, true)
.Set(x => x.ParentId, 0)
.Set(x => x.Level, 1)
.Where(x => x.Id == dir.Id)
.ExecuteAffrows();
}
var adminRoleId = fsql.Select<SysRole>().Where(r => r.Code == "admin").First()?.Id ?? 0;
EnsureMenuPermission(fsql, snowflake, adminRoleId, dir.Id);
foreach (var (name, path, component, permission, icon, sort) in children)
{
var child = fsql.Select<SysMenu>().Where(m => m.Path == path).First();
if (child == null)
{
child = new SysMenu(snowflake.CreateId(), name, icon, MenuType_Enum.页面, null, null, dir.Id, sort, 1);
child.SetWebInfo(path, component, permission);
fsql.Insert(child).ExecuteAffrows();
}
else if (!child.IsActive || child.IconSvg != icon || child.ParentId != dir.Id)
{
fsql.Update<SysMenu>()
.Set(x => x.IconSvg, icon)
.Set(x => x.IsActive, true)
.Set(x => x.ParentId, dir.Id)
.Set(x => x.Level, 2)
.Where(x => x.Id == child.Id)
.ExecuteAffrows();
}
EnsureMenuPermission(fsql, snowflake, adminRoleId, child.Id);
}
}
/// <summary>确保指定菜单已授权给角色(后续 FixMenuPermissions 会把 permission_id 修正为权限单元 Id)</summary>
private static void EnsureMenuPermission(IFreeSql fsql, ISnowflakeIdGenerator snowflake, long roleId, long menuId)
{
if (roleId <= 0 || menuId <= 0) return;
var any = fsql.Select<SysRolePermission>()
.Where(x => x.RoleId == roleId && x.PermissionId == menuId)
.First();
if (any == null)
{
fsql.Insert(new SysRolePermission(snowflake.CreateId(), roleId, menuId)).ExecuteAffrows();
}
}
/// <summary>
/// 确保"基础数据"目录下的菜单存在(含启用状态与图标校正),并给 admin 角色授权。
/// 注意:图标名必须是 Element Plus 图标组件名,前端按图标名渲染菜单图标。
@@ -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; }
}
}
File diff suppressed because it is too large Load Diff