生产模块拆分为前缫/后缫;修复菜单卡顿与重复请求;支持 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:
@@ -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
+13002
File diff suppressed because it is too large
Load Diff
+1542
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user