生产模块拆分为前缫/后缫;修复菜单卡顿与重复请求;支持 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,134 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Common.Exceptions;
|
||||
using F9WebAiTest.Application.CQRS.SYS.Employees.Commands;
|
||||
using F9WebAiTest.Application.CQRS.SYS.Employees.Queries;
|
||||
using F9WebAiTest.Domain.Enums.SYS.Employees;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace F9WebAiTest.Api.Controllers;
|
||||
|
||||
/// <summary>职工档案管理(含转正/离职/调岗/重新入职/生产信息维护)</summary>
|
||||
[ApiController]
|
||||
[Route("api/sys/employee")]
|
||||
[Authorize]
|
||||
public class SysEmployeeController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
public SysEmployeeController(IMediator mediator) => _mediator = mediator;
|
||||
|
||||
/// <summary>分页查询(部门含子部门;workStatus/banci 不传表示全部)</summary>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetPaged([FromQuery] long depId = 0, [FromQuery] long worktypeId = 0,
|
||||
[FromQuery] Employee_WorkStatus_Enum? workStatus = null, [FromQuery] Prod_Banci_Enum? banci = null,
|
||||
[FromQuery] string like = "", [FromQuery] int pageIndex = 1, [FromQuery] int pageSize = 20)
|
||||
=> Ok(ApiResult<object>.Ok(await _mediator.Send(new GetEmployeePagedQuery(depId, worktypeId, workStatus,
|
||||
banci, like ?? string.Empty, pageIndex, pageSize))));
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetById(long id)
|
||||
{
|
||||
var result = await _mediator.Send(new GetEmployeeByIdQuery(id));
|
||||
return result == null
|
||||
? NotFound(ApiResult<object>.Fail("职工不存在", 404))
|
||||
: Ok(ApiResult<object>.Ok(result));
|
||||
}
|
||||
|
||||
/// <summary>变更记录</summary>
|
||||
[HttpGet("{id}/change-records")]
|
||||
public async Task<IActionResult> GetChangeRecords(long id)
|
||||
=> Ok(ApiResult<object>.Ok(await _mediator.Send(new GetEmployeeChangeRecordsQuery(id))));
|
||||
|
||||
/// <summary>调岗记录</summary>
|
||||
[HttpGet("{id}/tiaogang-records")]
|
||||
public async Task<IActionResult> GetTiaogangRecords(long id)
|
||||
=> Ok(ApiResult<object>.Ok(await _mediator.Send(new GetEmployeeTiaogangRecordsQuery(id))));
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] Employee_CreateCommand command)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(new { id = await _mediator.Send(command) }, "新增成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> Update(long id, [FromBody] Employee_UpdateCommand command)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(command with { Id = id }), "修改成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(long id)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(new Employee_DelCommand(id)), "删除成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
/// <summary>转正</summary>
|
||||
[HttpPut("{id}/zhuanzheng")]
|
||||
public async Task<IActionResult> Zhuanzheng(long id, [FromBody] Employee_ZhuanzhengCommand command)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(command with { Id = id }), "转正成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
/// <summary>取消转正</summary>
|
||||
[HttpPut("{id}/zhuanzheng-cancel")]
|
||||
public async Task<IActionResult> ZhuanzhengCancel(long id)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(new Employee_ZhuanzhengCancelCommand(id)), "已取消转正")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
/// <summary>离职</summary>
|
||||
[HttpPut("{id}/lizhi")]
|
||||
public async Task<IActionResult> Lizhi(long id, [FromBody] Employee_LizhiCommand command)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(command with { Id = id }), "已办理离职")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
/// <summary>取消离职</summary>
|
||||
[HttpPut("{id}/lizhi-cancel")]
|
||||
public async Task<IActionResult> LizhiCancel(long id)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(new Employee_LizhiCancelCommand(id)), "已取消离职")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
/// <summary>调岗</summary>
|
||||
[HttpPut("{id}/tiaogang")]
|
||||
public async Task<IActionResult> Tiaogang(long id, [FromBody] Employee_TiaogangCommand command)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(command with { Id = id }), "调岗成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
/// <summary>重新入职</summary>
|
||||
[HttpPut("{id}/renew-ruzhis")]
|
||||
public async Task<IActionResult> RenewRuzhi(long id, [FromBody] Employee_RenewRuzhiCommand command)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(command with { Id = id }), "重新入职成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
|
||||
/// <summary>修改生产信息(工作组 / 顺序号)</summary>
|
||||
[HttpPut("{id}/sc")]
|
||||
public async Task<IActionResult> UpdateSc(long id, [FromBody] Employee_UpdateScCommand command)
|
||||
{
|
||||
try { return Ok(ApiResult<object>.Ok(await _mediator.Send(command with { Id = id }), "修改成功")); }
|
||||
catch (F9NotFoundException ex) { return NotFound(ApiResult<object>.Fail(ex.Message, 404)); }
|
||||
catch (F9OperationException ex) { return BadRequest(ApiResult<object>.Fail(ex.Message, 400)); }
|
||||
}
|
||||
}
|
||||
@@ -98,6 +98,11 @@ using (var scope = app.Services.CreateScope())
|
||||
DbSeeder.Seed(fsql, snowflake);
|
||||
}
|
||||
|
||||
// 前端静态资源:Vue 打包产物放在 wwwroot。
|
||||
// IIS 单站点部署时前端与 API 同源(浏览器访问 /api/...),无需跨域与反向代理。
|
||||
app.UseDefaultFiles();
|
||||
app.UseStaticFiles();
|
||||
|
||||
// NSwag 中间件(swagger 文档 + UI)
|
||||
app.UseOpenApi();
|
||||
app.UseSwaggerUi();
|
||||
@@ -109,4 +114,7 @@ app.UseAuthorization();
|
||||
app.MapControllers();
|
||||
app.MapHub<NotificationHub>("/hubs/notification");
|
||||
|
||||
// 前端使用 history 路由,未匹配到 API/静态文件的路径统一交给 index.html
|
||||
app.MapFallbackToFile("index.html");
|
||||
|
||||
app.Run();
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,324 @@
|
||||
using F9WebAiTest.Application.Common.Exceptions;
|
||||
using F9WebAiTest.Application.Dtos.SYS.Employees;
|
||||
using F9WebAiTest.Application.Interfaces.Repositories.SYS.Employees;
|
||||
using F9WebAiTest.Application.Interfaces.SnowflakeID;
|
||||
using F9WebAiTest.Domain.Entities.SYS.Employees;
|
||||
using F9WebAiTest.Domain.Enums.SYS.Employees;
|
||||
using F9WebAiTest.Domain.Interfaces.SYS.Employees;
|
||||
using MediatR;
|
||||
|
||||
namespace F9WebAiTest.Application.CQRS.SYS.Employees.Commands;
|
||||
|
||||
/// <summary>新增职工(同时写一条"新入职"变更记录)</summary>
|
||||
public record Employee_CreateCommand(
|
||||
long DepId, long WorktypeId, string Code, string Name, string Gangwei, string Phone, string IdCard,
|
||||
DateTime? BirthDate, string Address, string Gender, string Minzu, string HunyinStatus, string Huji,
|
||||
DateTime? RuzhiDate, string JinjiLianxiren, string JinjiLianxirenPhone, string HetongType,
|
||||
DateTime? HetongStartDate, DateTime? HetongEndDate, DateTime? TuixiuDate, DateTime? ShebaoDate, string ShebaoDesc,
|
||||
string BankcardCode, string BiyeYuanxiao, string Zhuanye, string Xueli, Prod_Banci_Enum? Banci, string WorkGroup,
|
||||
int Sort, string NamePinyin, string Memo) : IRequest<long>;
|
||||
|
||||
public class CreateEmployeeCommandHandler(
|
||||
IEmployeeWriteRepository repository,
|
||||
IEmployeeReadRepository readRepository,
|
||||
ISnowflakeIdGenerator snowflakeIdGenerator,
|
||||
IPublisher eventBus) : IRequestHandler<Employee_CreateCommand, long>
|
||||
{
|
||||
public async Task<long> Handle(Employee_CreateCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
if (command.DepId <= 0) throw new F9OperationException("请选择部门");
|
||||
if (string.IsNullOrWhiteSpace(command.Name)) throw new F9OperationException("请输入姓名");
|
||||
|
||||
var entity = new Employee(snowflakeIdGenerator.CreateId(), command.DepId, command.WorktypeId,
|
||||
Employee_WorkStatus_Enum.在职, command.Code ?? string.Empty, command.Name.Trim(),
|
||||
command.Gangwei ?? string.Empty, command.Phone ?? string.Empty, command.IdCard ?? string.Empty,
|
||||
command.BirthDate, command.Address ?? string.Empty, command.Gender ?? string.Empty, command.Minzu ?? string.Empty,
|
||||
command.HunyinStatus ?? string.Empty, command.Huji ?? string.Empty, command.RuzhiDate, null, null, false,
|
||||
command.JinjiLianxiren ?? string.Empty, command.JinjiLianxirenPhone ?? string.Empty,
|
||||
command.HetongType ?? string.Empty, command.HetongStartDate, command.HetongEndDate, command.TuixiuDate,
|
||||
command.ShebaoDate, command.ShebaoDesc ?? string.Empty, command.BankcardCode ?? string.Empty,
|
||||
command.BiyeYuanxiao ?? string.Empty, command.Zhuanye ?? string.Empty, command.Xueli ?? string.Empty,
|
||||
command.Banci, command.WorkGroup ?? string.Empty, command.Sort, command.NamePinyin ?? string.Empty,
|
||||
command.Memo ?? string.Empty, string.Empty);
|
||||
|
||||
var result = await repository.AddAsync(entity);
|
||||
if (result == false) throw new F9OperationException("职工", "新增");
|
||||
|
||||
// 变更记录:新入职
|
||||
var depName = await readRepository.GetDepNameAsync(entity.DepId);
|
||||
var worktypeName = await readRepository.GetWorktypeNameAsync(entity.WorktypeId);
|
||||
await readRepository.AddChangeRecordAsync(new EmployeeChangeRecordDto(0, entity.Id,
|
||||
entity.RuzhiDate ?? DateTime.Now.Date, Employee_ChangeStatus_Enum.新入职, "",
|
||||
$"{depName},{worktypeName}", "新入职"));
|
||||
|
||||
foreach (var de in entity.DomainEvents) await eventBus.Publish(de, cancellationToken);
|
||||
entity.ClearDomainEvents();
|
||||
|
||||
return entity.Id;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>修改职工基本信息</summary>
|
||||
public record Employee_UpdateCommand(
|
||||
long Id, string Code, string Name, string Gangwei, string Phone, string IdCard, DateTime? BirthDate,
|
||||
string Address, string Gender, string Minzu, string HunyinStatus, string Huji, DateTime? RuzhiDate,
|
||||
string JinjiLianxiren, string JinjiLianxirenPhone, string HetongType, DateTime? HetongStartDate,
|
||||
DateTime? HetongEndDate, DateTime? TuixiuDate, DateTime? ShebaoDate, string ShebaoDesc, string BankcardCode,
|
||||
string BiyeYuanxiao, string Zhuanye, string Xueli, string NamePinyin, string Memo) : IRequest<bool>;
|
||||
|
||||
public class UpdateEmployeeCommandHandler(
|
||||
IEmployeeWriteRepository repository,
|
||||
IPublisher eventBus) : IRequestHandler<Employee_UpdateCommand, bool>
|
||||
{
|
||||
public async Task<bool> Handle(Employee_UpdateCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await repository.GetByIdAsync(command.Id, cancellationToken)
|
||||
?? throw new F9NotFoundException("职工不存在");
|
||||
if (string.IsNullOrWhiteSpace(command.Name)) throw new F9OperationException("请输入姓名");
|
||||
|
||||
entity.Update(command.Code ?? string.Empty, command.Name.Trim(), command.Gangwei ?? string.Empty,
|
||||
command.Phone ?? string.Empty, command.IdCard ?? string.Empty, command.BirthDate, command.Address ?? string.Empty,
|
||||
command.Gender ?? string.Empty, command.Minzu ?? string.Empty, command.HunyinStatus ?? string.Empty,
|
||||
command.Huji ?? string.Empty, command.RuzhiDate, command.JinjiLianxiren ?? string.Empty,
|
||||
command.JinjiLianxirenPhone ?? string.Empty, command.HetongType ?? string.Empty, command.HetongStartDate,
|
||||
command.HetongEndDate, command.TuixiuDate, command.ShebaoDate, command.ShebaoDesc ?? string.Empty,
|
||||
command.BankcardCode ?? string.Empty, command.BiyeYuanxiao ?? string.Empty, command.Zhuanye ?? string.Empty,
|
||||
command.Xueli ?? string.Empty, command.NamePinyin ?? string.Empty, command.Memo ?? string.Empty);
|
||||
|
||||
var result = await repository.UpdateAsync(entity);
|
||||
if (result == false) throw new F9OperationException("职工", "修改");
|
||||
|
||||
foreach (var de in entity.DomainEvents) await eventBus.Publish(de, cancellationToken);
|
||||
entity.ClearDomainEvents();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>删除职工</summary>
|
||||
public record Employee_DelCommand(long Id) : IRequest<bool>;
|
||||
|
||||
public class DelEmployeeCommandHandler(
|
||||
IEmployeeWriteRepository repository,
|
||||
IPublisher eventBus) : IRequestHandler<Employee_DelCommand, bool>
|
||||
{
|
||||
public async Task<bool> Handle(Employee_DelCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await repository.GetByIdAsync(command.Id, cancellationToken)
|
||||
?? throw new F9NotFoundException("职工不存在");
|
||||
|
||||
var result = await repository.SoftDeleteAsync(command.Id);
|
||||
if (result == false) throw new F9OperationException("职工", "删除");
|
||||
|
||||
entity.Deleted();
|
||||
foreach (var de in entity.DomainEvents) await eventBus.Publish(de, cancellationToken);
|
||||
entity.ClearDomainEvents();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>转正(写"转正"变更记录)</summary>
|
||||
public record Employee_ZhuanzhengCommand(long Id, DateTime ZhuanzhengDate) : IRequest<bool>;
|
||||
|
||||
public class ZhuanzhengCommandHandler(
|
||||
IEmployeeWriteRepository repository,
|
||||
IEmployeeReadRepository readRepository,
|
||||
IPublisher eventBus) : IRequestHandler<Employee_ZhuanzhengCommand, bool>
|
||||
{
|
||||
public async Task<bool> Handle(Employee_ZhuanzhengCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await repository.GetByIdAsync(command.Id, cancellationToken)
|
||||
?? throw new F9NotFoundException("职工不存在");
|
||||
if (entity.IsZhuanzheng) throw new F9OperationException("该职工已转正");
|
||||
|
||||
entity.Zhuanzheng(command.ZhuanzhengDate);
|
||||
var result = await repository.UpdateAsync(entity);
|
||||
if (result == false) throw new F9OperationException("转正", "操作");
|
||||
|
||||
var depName = await readRepository.GetDepNameAsync(entity.DepId);
|
||||
var worktypeName = await readRepository.GetWorktypeNameAsync(entity.WorktypeId);
|
||||
await readRepository.AddChangeRecordAsync(new EmployeeChangeRecordDto(0, entity.Id, command.ZhuanzhengDate,
|
||||
Employee_ChangeStatus_Enum.转正, "", $"{depName},{worktypeName}", "转正"));
|
||||
|
||||
foreach (var de in entity.DomainEvents) await eventBus.Publish(de, cancellationToken);
|
||||
entity.ClearDomainEvents();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>取消转正</summary>
|
||||
public record Employee_ZhuanzhengCancelCommand(long Id) : IRequest<bool>;
|
||||
|
||||
public class ZhuanzhengCancelCommandHandler(
|
||||
IEmployeeWriteRepository repository,
|
||||
IPublisher eventBus) : IRequestHandler<Employee_ZhuanzhengCancelCommand, bool>
|
||||
{
|
||||
public async Task<bool> Handle(Employee_ZhuanzhengCancelCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await repository.GetByIdAsync(command.Id, cancellationToken)
|
||||
?? throw new F9NotFoundException("职工不存在");
|
||||
|
||||
entity.ZhuanzhengCancel();
|
||||
var result = await repository.UpdateAsync(entity);
|
||||
if (result == false) throw new F9OperationException("取消转正", "操作");
|
||||
|
||||
foreach (var de in entity.DomainEvents) await eventBus.Publish(de, cancellationToken);
|
||||
entity.ClearDomainEvents();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>离职(写"离职"变更记录)</summary>
|
||||
public record Employee_LizhiCommand(long Id, DateTime LizhiDate, string LizhiYuanyin) : IRequest<bool>;
|
||||
|
||||
public class LizhiCommandHandler(
|
||||
IEmployeeWriteRepository repository,
|
||||
IEmployeeReadRepository readRepository,
|
||||
IPublisher eventBus) : IRequestHandler<Employee_LizhiCommand, bool>
|
||||
{
|
||||
public async Task<bool> Handle(Employee_LizhiCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await repository.GetByIdAsync(command.Id, cancellationToken)
|
||||
?? throw new F9NotFoundException("职工不存在");
|
||||
if (entity.WorkStatus == Employee_WorkStatus_Enum.离职) throw new F9OperationException("该职工已离职");
|
||||
|
||||
entity.Lizhi(command.LizhiDate, command.LizhiYuanyin);
|
||||
var result = await repository.UpdateAsync(entity);
|
||||
if (result == false) throw new F9OperationException("离职", "操作");
|
||||
|
||||
var depName = await readRepository.GetDepNameAsync(entity.DepId);
|
||||
var worktypeName = await readRepository.GetWorktypeNameAsync(entity.WorktypeId);
|
||||
await readRepository.AddChangeRecordAsync(new EmployeeChangeRecordDto(0, entity.Id, command.LizhiDate,
|
||||
Employee_ChangeStatus_Enum.离职, "", $"{depName},{worktypeName}", command.LizhiYuanyin ?? string.Empty));
|
||||
|
||||
foreach (var de in entity.DomainEvents) await eventBus.Publish(de, cancellationToken);
|
||||
entity.ClearDomainEvents();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>取消离职</summary>
|
||||
public record Employee_LizhiCancelCommand(long Id) : IRequest<bool>;
|
||||
|
||||
public class LizhiCancelCommandHandler(
|
||||
IEmployeeWriteRepository repository,
|
||||
IPublisher eventBus) : IRequestHandler<Employee_LizhiCancelCommand, bool>
|
||||
{
|
||||
public async Task<bool> Handle(Employee_LizhiCancelCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await repository.GetByIdAsync(command.Id, cancellationToken)
|
||||
?? throw new F9NotFoundException("职工不存在");
|
||||
|
||||
entity.LizhiCancel();
|
||||
var result = await repository.UpdateAsync(entity);
|
||||
if (result == false) throw new F9OperationException("取消离职", "操作");
|
||||
|
||||
foreach (var de in entity.DomainEvents) await eventBus.Publish(de, cancellationToken);
|
||||
entity.ClearDomainEvents();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>调岗(写调岗记录 + 变更记录)</summary>
|
||||
public record Employee_TiaogangCommand(long Id, long DepId, long WorkTypeId, DateTime Date, string GangweiName,
|
||||
Prod_Banci_Enum? Sc_Banci) : IRequest<bool>;
|
||||
|
||||
public class TiaogangCommandHandler(
|
||||
IEmployeeWriteRepository repository,
|
||||
IEmployeeReadRepository readRepository,
|
||||
IPublisher eventBus) : IRequestHandler<Employee_TiaogangCommand, bool>
|
||||
{
|
||||
public async Task<bool> Handle(Employee_TiaogangCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await repository.GetByIdAsync(command.Id, cancellationToken)
|
||||
?? throw new F9NotFoundException("职工不存在");
|
||||
if (command.DepId <= 0) throw new F9OperationException("请选择部门");
|
||||
if (entity.WorkStatus == Employee_WorkStatus_Enum.离职) throw new F9OperationException("离职人员请使用「重新入职」");
|
||||
|
||||
var oldDepId = entity.DepId;
|
||||
var oldWorktypeId = entity.WorktypeId;
|
||||
|
||||
entity.Tiaogang(command.DepId, command.WorkTypeId, command.GangweiName, command.Sc_Banci);
|
||||
var result = await repository.UpdateAsync(entity);
|
||||
if (result == false) throw new F9OperationException("调岗", "操作");
|
||||
|
||||
await readRepository.AddTiaogangRecordAsync(entity.Id, oldDepId, oldWorktypeId, command.DepId,
|
||||
command.WorkTypeId, command.Date);
|
||||
|
||||
var depName = await readRepository.GetDepNameAsync(command.DepId);
|
||||
var worktypeName = await readRepository.GetWorktypeNameAsync(command.WorkTypeId);
|
||||
await readRepository.AddChangeRecordAsync(new EmployeeChangeRecordDto(0, entity.Id, command.Date,
|
||||
Employee_ChangeStatus_Enum.调岗, "", $"{depName},{worktypeName}", "调动"));
|
||||
|
||||
foreach (var de in entity.DomainEvents) await eventBus.Publish(de, cancellationToken);
|
||||
entity.ClearDomainEvents();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>重新入职(离职人员)</summary>
|
||||
public record Employee_RenewRuzhiCommand(long Id, long DepId, long WorkTypeId, DateTime Date, string GangweiName,
|
||||
Prod_Banci_Enum? Sc_Banci) : IRequest<bool>;
|
||||
|
||||
public class RenewRuzhiCommandHandler(
|
||||
IEmployeeWriteRepository repository,
|
||||
IEmployeeReadRepository readRepository,
|
||||
IPublisher eventBus) : IRequestHandler<Employee_RenewRuzhiCommand, bool>
|
||||
{
|
||||
public async Task<bool> Handle(Employee_RenewRuzhiCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await repository.GetByIdAsync(command.Id, cancellationToken)
|
||||
?? throw new F9NotFoundException("职工不存在");
|
||||
if (entity.WorkStatus != Employee_WorkStatus_Enum.离职) throw new F9OperationException("请选择离职人员");
|
||||
if (command.DepId <= 0) throw new F9OperationException("请选择部门");
|
||||
|
||||
var oldDepId = entity.DepId;
|
||||
var oldWorktypeId = entity.WorktypeId;
|
||||
|
||||
entity.RenewRuzhi(command.Date, command.DepId, command.WorkTypeId, command.GangweiName, command.Sc_Banci);
|
||||
var result = await repository.UpdateAsync(entity);
|
||||
if (result == false) throw new F9OperationException("重新入职", "操作");
|
||||
|
||||
await readRepository.AddTiaogangRecordAsync(entity.Id, oldDepId, oldWorktypeId, command.DepId,
|
||||
command.WorkTypeId, command.Date);
|
||||
|
||||
var depName = await readRepository.GetDepNameAsync(command.DepId);
|
||||
var worktypeName = await readRepository.GetWorktypeNameAsync(command.WorkTypeId);
|
||||
await readRepository.AddChangeRecordAsync(new EmployeeChangeRecordDto(0, entity.Id, command.Date,
|
||||
Employee_ChangeStatus_Enum.再入职, "", $"{depName},{worktypeName}", "再入职"));
|
||||
|
||||
foreach (var de in entity.DomainEvents) await eventBus.Publish(de, cancellationToken);
|
||||
entity.ClearDomainEvents();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>修改生产信息(工作组 / 顺序号)</summary>
|
||||
public record Employee_UpdateScCommand(long Id, string Sc_WorkGroup, int Sc_Sort) : IRequest<bool>;
|
||||
|
||||
public class UpdateScCommandHandler(
|
||||
IEmployeeWriteRepository repository,
|
||||
IPublisher eventBus) : IRequestHandler<Employee_UpdateScCommand, bool>
|
||||
{
|
||||
public async Task<bool> Handle(Employee_UpdateScCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await repository.GetByIdAsync(command.Id, cancellationToken)
|
||||
?? throw new F9NotFoundException("职工不存在");
|
||||
|
||||
entity.UpdateSc(command.Sc_WorkGroup, command.Sc_Sort);
|
||||
var result = await repository.UpdateAsync(entity);
|
||||
if (result == false) throw new F9OperationException("生产信息", "修改");
|
||||
|
||||
foreach (var de in entity.DomainEvents) await eventBus.Publish(de, cancellationToken);
|
||||
entity.ClearDomainEvents();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Dtos.SYS.Employees;
|
||||
using F9WebAiTest.Application.Interfaces.Repositories.SYS.Employees;
|
||||
using F9WebAiTest.Domain.Enums.SYS.Employees;
|
||||
using MediatR;
|
||||
|
||||
namespace F9WebAiTest.Application.CQRS.SYS.Employees.Queries;
|
||||
|
||||
/// <summary>职工分页查询(部门含子部门 + 工种 + 在职状态 + 班次 + 关键字)</summary>
|
||||
public record GetEmployeePagedQuery(long DepId = 0, long WorktypeId = 0, Employee_WorkStatus_Enum? WorkStatus = null,
|
||||
Prod_Banci_Enum? Banci = null, string Like = "", int PageIndex = 1, int PageSize = 20)
|
||||
: IRequest<PageResult<EmployeeReadDto_View>>;
|
||||
|
||||
public class GetEmployeePagedQueryHandler(IEmployeeReadRepository readRepository)
|
||||
: IRequestHandler<GetEmployeePagedQuery, PageResult<EmployeeReadDto_View>>
|
||||
{
|
||||
public async Task<PageResult<EmployeeReadDto_View>> Handle(GetEmployeePagedQuery query, CancellationToken ct)
|
||||
=> await readRepository.GetPagedAsync(query.DepId, query.WorktypeId, query.WorkStatus, query.Banci,
|
||||
query.Like, query.PageIndex, query.PageSize);
|
||||
}
|
||||
|
||||
/// <summary>按 Id 查询职工(编辑回显)</summary>
|
||||
public record GetEmployeeByIdQuery(long Id) : IRequest<EmployeeReadDto_Edit>;
|
||||
|
||||
public class GetEmployeeByIdQueryHandler(IEmployeeReadRepository readRepository)
|
||||
: IRequestHandler<GetEmployeeByIdQuery, EmployeeReadDto_Edit>
|
||||
{
|
||||
public async Task<EmployeeReadDto_Edit> Handle(GetEmployeeByIdQuery query, CancellationToken ct)
|
||||
=> await readRepository.GetByIdForEditAsync(query.Id);
|
||||
}
|
||||
|
||||
/// <summary>职工变更记录</summary>
|
||||
public record GetEmployeeChangeRecordsQuery(long EmployeeId) : IRequest<List<EmployeeChangeRecordDto>>;
|
||||
|
||||
public class GetEmployeeChangeRecordsQueryHandler(IEmployeeReadRepository readRepository)
|
||||
: IRequestHandler<GetEmployeeChangeRecordsQuery, List<EmployeeChangeRecordDto>>
|
||||
{
|
||||
public async Task<List<EmployeeChangeRecordDto>> Handle(GetEmployeeChangeRecordsQuery query, CancellationToken ct)
|
||||
=> await readRepository.GetChangeRecordsAsync(query.EmployeeId);
|
||||
}
|
||||
|
||||
/// <summary>职工调岗记录</summary>
|
||||
public record GetEmployeeTiaogangRecordsQuery(long EmployeeId) : IRequest<List<EmployeeTiaogangRecordDto>>;
|
||||
|
||||
public class GetEmployeeTiaogangRecordsQueryHandler(IEmployeeReadRepository readRepository)
|
||||
: IRequestHandler<GetEmployeeTiaogangRecordsQuery, List<EmployeeTiaogangRecordDto>>
|
||||
{
|
||||
public async Task<List<EmployeeTiaogangRecordDto>> Handle(GetEmployeeTiaogangRecordsQuery query, CancellationToken ct)
|
||||
=> await readRepository.GetTiaogangRecordsAsync(query.EmployeeId);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using F9WebAiTest.Domain.Enums.SYS.Employees;
|
||||
|
||||
namespace F9WebAiTest.Application.Dtos.SYS.Employees;
|
||||
|
||||
/// <summary>职工列表读模型</summary>
|
||||
public record EmployeeReadDto_View(
|
||||
long Id,
|
||||
long DepId,
|
||||
string DepName,
|
||||
long WorktypeId,
|
||||
string WorktypeName,
|
||||
Prod_Banci_Enum? Banci,
|
||||
string BanciName,
|
||||
string Gangwei,
|
||||
Employee_WorkStatus_Enum WorkStatus,
|
||||
bool IsZhuanzheng,
|
||||
string Code,
|
||||
string Name,
|
||||
string Gender,
|
||||
int Nianling,
|
||||
string IdCard,
|
||||
string Phone,
|
||||
DateTime? BirthDate,
|
||||
DateTime? RuzhiDate,
|
||||
DateTime? ZhuanzhengDate,
|
||||
DateTime? LizhiDate,
|
||||
string LizhiYuanyin,
|
||||
string WorkGroup,
|
||||
int Sort,
|
||||
string BankcardCode,
|
||||
string HetongType,
|
||||
DateTime? HetongStartDate,
|
||||
DateTime? HetongEndDate,
|
||||
DateTime? TuixiuDate,
|
||||
DateTime? ShebaoDate,
|
||||
string Minzu,
|
||||
string HunyinStatus,
|
||||
string Xueli,
|
||||
string Huji,
|
||||
string Address,
|
||||
string JinjiLianxiren,
|
||||
string JinjiLianxirenPhone,
|
||||
string BiyeYuanxiao,
|
||||
string Zhuanye,
|
||||
string NamePinyin,
|
||||
string Memo);
|
||||
|
||||
/// <summary>职工编辑读模型</summary>
|
||||
public record EmployeeReadDto_Edit(
|
||||
long Id,
|
||||
long DepId,
|
||||
long WorktypeId,
|
||||
Employee_WorkStatus_Enum WorkStatus,
|
||||
Prod_Banci_Enum? Banci,
|
||||
string Code,
|
||||
string Name,
|
||||
string Gangwei,
|
||||
string Phone,
|
||||
string IdCard,
|
||||
DateTime? BirthDate,
|
||||
string Address,
|
||||
string Gender,
|
||||
string Minzu,
|
||||
string HunyinStatus,
|
||||
string Huji,
|
||||
DateTime? RuzhiDate,
|
||||
DateTime? LizhiDate,
|
||||
DateTime? ZhuanzhengDate,
|
||||
bool IsZhuanzheng,
|
||||
string JinjiLianxiren,
|
||||
string JinjiLianxirenPhone,
|
||||
string HetongType,
|
||||
DateTime? HetongStartDate,
|
||||
DateTime? HetongEndDate,
|
||||
DateTime? TuixiuDate,
|
||||
DateTime? ShebaoDate,
|
||||
string ShebaoDesc,
|
||||
string BankcardCode,
|
||||
string BiyeYuanxiao,
|
||||
string Zhuanye,
|
||||
string Xueli,
|
||||
string WorkGroup,
|
||||
int Sort,
|
||||
string NamePinyin,
|
||||
string Memo,
|
||||
string LizhiYuanyin);
|
||||
|
||||
/// <summary>职工变更记录读模型</summary>
|
||||
public record EmployeeChangeRecordDto(
|
||||
long Id,
|
||||
long EmployeeId,
|
||||
DateTime Date,
|
||||
Employee_ChangeStatus_Enum ChangeStatus,
|
||||
string ChangeStatusName,
|
||||
string ChangeShuoming,
|
||||
string ChangeYuanyin);
|
||||
|
||||
/// <summary>职工调岗记录读模型</summary>
|
||||
public record EmployeeTiaogangRecordDto(
|
||||
long Id,
|
||||
long EmployeeId,
|
||||
long OldDepId,
|
||||
string OldDepName,
|
||||
long OldWorktypeId,
|
||||
string OldWorktypeName,
|
||||
long NewDepId,
|
||||
string NewDepName,
|
||||
long NewWorktypeId,
|
||||
string NewWorktypeName,
|
||||
DateTime TiaogangDate);
|
||||
@@ -8,7 +8,8 @@ public record WorkTypeReadDto_View(
|
||||
string BizType,
|
||||
string SccbFenlei,
|
||||
bool IsActive,
|
||||
string DepartmentNames);
|
||||
string DepartmentNames,
|
||||
List<long> DepartmentIds);
|
||||
|
||||
/// <summary>工种编辑读模型</summary>
|
||||
public record WorkTypeReadDto_Edit(
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Dtos.SYS.Employees;
|
||||
using F9WebAiTest.Domain.Enums.SYS.Employees;
|
||||
using F9WebAiTest.Domain.Interfaces;
|
||||
|
||||
namespace F9WebAiTest.Application.Interfaces.Repositories.SYS.Employees;
|
||||
|
||||
/// <summary>职工读仓储(返回读模型 DTO)</summary>
|
||||
public interface IEmployeeReadRepository : IDInterface
|
||||
{
|
||||
/// <summary>分页查询(部门含子部门;workStatus/banci 为 null 表示全部)</summary>
|
||||
Task<PageResult<EmployeeReadDto_View>> GetPagedAsync(long depId, long worktypeId,
|
||||
Employee_WorkStatus_Enum? workStatus, Prod_Banci_Enum? banci, string keyword, int pageIndex, int pageSize);
|
||||
|
||||
Task<EmployeeReadDto_Edit> GetByIdForEditAsync(long id);
|
||||
|
||||
/// <summary>变更记录(按日期倒序)</summary>
|
||||
Task<List<EmployeeChangeRecordDto>> GetChangeRecordsAsync(long employeeId);
|
||||
|
||||
/// <summary>调岗记录(按日期倒序)</summary>
|
||||
Task<List<EmployeeTiaogangRecordDto>> GetTiaogangRecordsAsync(long employeeId);
|
||||
|
||||
/// <summary>写变更记录</summary>
|
||||
Task<bool> AddChangeRecordAsync(EmployeeChangeRecordDto record);
|
||||
|
||||
/// <summary>写调岗记录</summary>
|
||||
Task<bool> AddTiaogangRecordAsync(long employeeId, long oldDepId, long oldWorktypeId, long newDepId,
|
||||
long newWorktypeId, DateTime date);
|
||||
|
||||
/// <summary>部门名称(用于变更记录说明)</summary>
|
||||
Task<string> GetDepNameAsync(long depId);
|
||||
|
||||
/// <summary>工种名称(用于变更记录说明)</summary>
|
||||
Task<string> GetWorktypeNameAsync(long worktypeId);
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using F9WebAiTest.Domain.Enums.SYS.Employees;
|
||||
using F9WebAiTest.Domain.Events.SYS.Employees;
|
||||
|
||||
namespace F9WebAiTest.Domain.Entities.SYS.Employees;
|
||||
|
||||
/// <summary>
|
||||
/// 职工档案:部门/工种/岗位 + 基本信息 + 合同社保 + 教育经历 + 生产信息(班次/工作组/顺序号)。
|
||||
/// 转正、离职、调岗、重新入职等通过行为方法变更,并单独记录变更/调岗历史。
|
||||
/// </summary>
|
||||
[Table(Name = "sys_employee")]
|
||||
public class Employee : AggregateRoot
|
||||
{
|
||||
/// <summary>所属部门 Id</summary>
|
||||
[Column(Name = "dep_id")] public long DepId { get; private set; }
|
||||
|
||||
/// <summary>工种 Id</summary>
|
||||
[Column(Name = "worktype_id")] public long WorktypeId { get; private set; }
|
||||
|
||||
/// <summary>在职状态</summary>
|
||||
[Column(Name = "work_status")] public Employee_WorkStatus_Enum WorkStatus { get; private set; }
|
||||
|
||||
/// <summary>工号</summary>
|
||||
[Column(Name = "code")] public string Code { get; private set; }
|
||||
|
||||
/// <summary>姓名</summary>
|
||||
[Column(Name = "name")] public string Name { get; private set; }
|
||||
|
||||
/// <summary>岗位</summary>
|
||||
[Column(Name = "gangwei")] public string Gangwei { get; private set; }
|
||||
|
||||
/// <summary>电话</summary>
|
||||
[Column(Name = "phone")] public string Phone { get; private set; }
|
||||
|
||||
/// <summary>身份证号</summary>
|
||||
[Column(Name = "idcard")] public string IdCard { get; private set; }
|
||||
|
||||
/// <summary>出生日期</summary>
|
||||
[Column(Name = "birth_date")] public DateTime? BirthDate { get; private set; }
|
||||
|
||||
/// <summary>住址</summary>
|
||||
[Column(Name = "address")] public string Address { get; private set; }
|
||||
|
||||
/// <summary>性别</summary>
|
||||
[Column(Name = "gender")] public string Gender { get; private set; }
|
||||
|
||||
/// <summary>民族</summary>
|
||||
[Column(Name = "minzu")] public string Minzu { get; private set; }
|
||||
|
||||
/// <summary>婚姻状态</summary>
|
||||
[Column(Name = "hunyin_status")] public string HunyinStatus { get; private set; }
|
||||
|
||||
/// <summary>户籍</summary>
|
||||
[Column(Name = "huji")] public string Huji { get; private set; }
|
||||
|
||||
/// <summary>入职日期</summary>
|
||||
[Column(Name = "ruzhi_date")] public DateTime? RuzhiDate { get; private set; }
|
||||
|
||||
/// <summary>离职日期</summary>
|
||||
[Column(Name = "lizhi_date")] public DateTime? LizhiDate { get; private set; }
|
||||
|
||||
/// <summary>转正日期</summary>
|
||||
[Column(Name = "zhuanzheng_date")] public DateTime? ZhuanzhengDate { get; private set; }
|
||||
|
||||
/// <summary>是否已转正</summary>
|
||||
[Column(Name = "is_zhuanzheng")] public bool IsZhuanzheng { get; private set; }
|
||||
|
||||
/// <summary>紧急联系人</summary>
|
||||
[Column(Name = "jinji_lianxiren")] public string JinjiLianxiren { get; private set; }
|
||||
|
||||
/// <summary>紧急联系人电话</summary>
|
||||
[Column(Name = "jinji_lianxiren_phone")] public string JinjiLianxirenPhone { get; private set; }
|
||||
|
||||
/// <summary>合同类型</summary>
|
||||
[Column(Name = "hetong_type")] public string HetongType { get; private set; }
|
||||
|
||||
/// <summary>劳动合同开始日期</summary>
|
||||
[Column(Name = "hetong_start_date")] public DateTime? HetongStartDate { get; private set; }
|
||||
|
||||
/// <summary>劳动合同结束日期</summary>
|
||||
[Column(Name = "hetong_end_date")] public DateTime? HetongEndDate { get; private set; }
|
||||
|
||||
/// <summary>退休日期</summary>
|
||||
[Column(Name = "tuixiu_date")] public DateTime? TuixiuDate { get; private set; }
|
||||
|
||||
/// <summary>社保参保日期</summary>
|
||||
[Column(Name = "shebao_date")] public DateTime? ShebaoDate { get; private set; }
|
||||
|
||||
/// <summary>社保参保情况说明</summary>
|
||||
[Column(Name = "shebao_desc")] public string ShebaoDesc { get; private set; }
|
||||
|
||||
/// <summary>银行卡号</summary>
|
||||
[Column(Name = "bankcard_code")] public string BankcardCode { get; private set; }
|
||||
|
||||
/// <summary>毕业院校</summary>
|
||||
[Column(Name = "biye_yuanxiao")] public string BiyeYuanxiao { get; private set; }
|
||||
|
||||
/// <summary>专业</summary>
|
||||
[Column(Name = "zhuanye")] public string Zhuanye { get; private set; }
|
||||
|
||||
/// <summary>学历</summary>
|
||||
[Column(Name = "xueli")] public string Xueli { get; private set; }
|
||||
|
||||
/// <summary>班次(甲/乙)</summary>
|
||||
[Column(Name = "banci")] public Prod_Banci_Enum? Banci { get; private set; }
|
||||
|
||||
/// <summary>工作组</summary>
|
||||
[Column(Name = "work_group")] public string WorkGroup { get; private set; }
|
||||
|
||||
/// <summary>顺序号</summary>
|
||||
[Column(Name = "sort")] public int Sort { get; private set; }
|
||||
|
||||
/// <summary>姓名拼音(简拼)</summary>
|
||||
[Column(Name = "name_pinyin")] public string NamePinyin { get; private set; }
|
||||
|
||||
/// <summary>备注</summary>
|
||||
[Column(Name = "memo")] public string Memo { get; private set; }
|
||||
|
||||
/// <summary>离职原因</summary>
|
||||
[Column(Name = "lizhi_yuanyin")] public string LizhiYuanyin { get; private set; }
|
||||
|
||||
public Employee() { }
|
||||
|
||||
public Employee(long id, long depId, long worktypeId, Employee_WorkStatus_Enum workStatus, string code, string name,
|
||||
string gangwei, string phone, string idCard, DateTime? birthDate, string address, string gender, string minzu,
|
||||
string hunyinStatus, string huji, DateTime? ruzhiDate, DateTime? lizhiDate, DateTime? zhuanzhengDate,
|
||||
bool isZhuanzheng, string jinjiLianxiren, string jinjiLianxirenPhone, string hetongType,
|
||||
DateTime? hetongStartDate, DateTime? hetongEndDate, DateTime? tuixiuDate, DateTime? shebaoDate, string shebaoDesc,
|
||||
string bankcardCode, string biyeYuanxiao, string zhuanye, string xueli, Prod_Banci_Enum? banci, string workGroup,
|
||||
int sort, string namePinyin, string memo, string lizhiYuanyin)
|
||||
{
|
||||
Id = id;
|
||||
DepId = depId;
|
||||
WorktypeId = worktypeId;
|
||||
WorkStatus = workStatus;
|
||||
Code = code;
|
||||
Name = name;
|
||||
Gangwei = gangwei;
|
||||
Phone = phone;
|
||||
IdCard = idCard;
|
||||
BirthDate = birthDate;
|
||||
Address = address;
|
||||
Gender = gender;
|
||||
Minzu = minzu;
|
||||
HunyinStatus = hunyinStatus;
|
||||
Huji = huji;
|
||||
RuzhiDate = ruzhiDate;
|
||||
LizhiDate = lizhiDate;
|
||||
ZhuanzhengDate = zhuanzhengDate;
|
||||
IsZhuanzheng = isZhuanzheng;
|
||||
JinjiLianxiren = jinjiLianxiren;
|
||||
JinjiLianxirenPhone = jinjiLianxirenPhone;
|
||||
HetongType = hetongType;
|
||||
HetongStartDate = hetongStartDate;
|
||||
HetongEndDate = hetongEndDate;
|
||||
TuixiuDate = tuixiuDate;
|
||||
ShebaoDate = shebaoDate;
|
||||
ShebaoDesc = shebaoDesc;
|
||||
BankcardCode = bankcardCode;
|
||||
BiyeYuanxiao = biyeYuanxiao;
|
||||
Zhuanye = zhuanye;
|
||||
Xueli = xueli;
|
||||
Banci = banci;
|
||||
WorkGroup = workGroup;
|
||||
Sort = sort;
|
||||
NamePinyin = namePinyin;
|
||||
Memo = memo;
|
||||
LizhiYuanyin = lizhiYuanyin;
|
||||
AddDomainEvent(new Employee_ChangedDomainEvent(Id));
|
||||
}
|
||||
|
||||
/// <summary>修改基本信息(部门/工种/班次通过调岗变更)</summary>
|
||||
public void Update(string code, string name, string gangwei, string phone, string idCard, DateTime? birthDate,
|
||||
string address, string gender, string minzu, string hunyinStatus, string huji, DateTime? ruzhiDate,
|
||||
string jinjiLianxiren, string jinjiLianxirenPhone, string hetongType, DateTime? hetongStartDate,
|
||||
DateTime? hetongEndDate, DateTime? tuixiuDate, DateTime? shebaoDate, string shebaoDesc, string bankcardCode,
|
||||
string biyeYuanxiao, string zhuanye, string xueli, string namePinyin, string memo)
|
||||
{
|
||||
Code = code;
|
||||
Name = name;
|
||||
Gangwei = gangwei;
|
||||
Phone = phone;
|
||||
IdCard = idCard;
|
||||
BirthDate = birthDate;
|
||||
Address = address;
|
||||
Gender = gender;
|
||||
Minzu = minzu;
|
||||
HunyinStatus = hunyinStatus;
|
||||
Huji = huji;
|
||||
RuzhiDate = ruzhiDate;
|
||||
JinjiLianxiren = jinjiLianxiren;
|
||||
JinjiLianxirenPhone = jinjiLianxirenPhone;
|
||||
HetongType = hetongType;
|
||||
HetongStartDate = hetongStartDate;
|
||||
HetongEndDate = hetongEndDate;
|
||||
TuixiuDate = tuixiuDate;
|
||||
ShebaoDate = shebaoDate;
|
||||
ShebaoDesc = shebaoDesc;
|
||||
BankcardCode = bankcardCode;
|
||||
BiyeYuanxiao = biyeYuanxiao;
|
||||
Zhuanye = zhuanye;
|
||||
Xueli = xueli;
|
||||
NamePinyin = namePinyin;
|
||||
Memo = memo;
|
||||
AddDomainEvent(new Employee_ChangedDomainEvent(Id));
|
||||
}
|
||||
|
||||
/// <summary>转正</summary>
|
||||
public void Zhuanzheng(DateTime date)
|
||||
{
|
||||
IsZhuanzheng = true;
|
||||
ZhuanzhengDate = date.Date;
|
||||
AddDomainEvent(new Employee_ChangedDomainEvent(Id));
|
||||
}
|
||||
|
||||
/// <summary>取消转正</summary>
|
||||
public void ZhuanzhengCancel()
|
||||
{
|
||||
IsZhuanzheng = false;
|
||||
ZhuanzhengDate = null;
|
||||
AddDomainEvent(new Employee_ChangedDomainEvent(Id));
|
||||
}
|
||||
|
||||
/// <summary>离职</summary>
|
||||
public void Lizhi(DateTime date, string yuanyin)
|
||||
{
|
||||
WorkStatus = Employee_WorkStatus_Enum.离职;
|
||||
LizhiDate = date.Date;
|
||||
LizhiYuanyin = yuanyin ?? string.Empty;
|
||||
AddDomainEvent(new Employee_ChangedDomainEvent(Id));
|
||||
}
|
||||
|
||||
/// <summary>取消离职</summary>
|
||||
public void LizhiCancel()
|
||||
{
|
||||
WorkStatus = Employee_WorkStatus_Enum.在职;
|
||||
LizhiDate = null;
|
||||
LizhiYuanyin = string.Empty;
|
||||
AddDomainEvent(new Employee_ChangedDomainEvent(Id));
|
||||
}
|
||||
|
||||
/// <summary>调岗</summary>
|
||||
public void Tiaogang(long depId, long worktypeId, string gangwei, Prod_Banci_Enum? banci)
|
||||
{
|
||||
DepId = depId;
|
||||
WorktypeId = worktypeId;
|
||||
if (!string.IsNullOrWhiteSpace(gangwei)) Gangwei = gangwei;
|
||||
if (banci.HasValue) Banci = banci;
|
||||
AddDomainEvent(new Employee_ChangedDomainEvent(Id));
|
||||
}
|
||||
|
||||
/// <summary>重新入职(离职人员按新部门/工种入职)</summary>
|
||||
public void RenewRuzhi(DateTime date, long depId, long worktypeId, string gangwei, Prod_Banci_Enum? banci)
|
||||
{
|
||||
WorkStatus = Employee_WorkStatus_Enum.在职;
|
||||
DepId = depId;
|
||||
WorktypeId = worktypeId;
|
||||
RuzhiDate = date.Date;
|
||||
LizhiDate = null;
|
||||
LizhiYuanyin = string.Empty;
|
||||
if (!string.IsNullOrWhiteSpace(gangwei)) Gangwei = gangwei;
|
||||
if (banci.HasValue) Banci = banci;
|
||||
AddDomainEvent(new Employee_ChangedDomainEvent(Id));
|
||||
}
|
||||
|
||||
/// <summary>修改生产信息(工作组 / 顺序号)</summary>
|
||||
public void UpdateSc(string workGroup, int sort)
|
||||
{
|
||||
WorkGroup = workGroup ?? string.Empty;
|
||||
Sort = sort;
|
||||
AddDomainEvent(new Employee_ChangedDomainEvent(Id));
|
||||
}
|
||||
|
||||
public void Deleted() => AddDomainEvent(new Employee_ChangedDomainEvent(Id));
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
using F9WebAiTest.Domain.Enums.SYS.Employees;
|
||||
|
||||
namespace F9WebAiTest.Domain.Entities.SYS.Employees;
|
||||
|
||||
/// <summary>职工变更记录(入职/调岗/转正/离职/再入职)</summary>
|
||||
[Table(Name = "sys_employee_change_record")]
|
||||
public class EmployeeChangeRecord : BusinessEntity
|
||||
{
|
||||
[Column(Name = "employee_id")] public long EmployeeId { get; private set; }
|
||||
|
||||
/// <summary>变更日期</summary>
|
||||
[Column(Name = "date")] public DateTime Date { get; private set; }
|
||||
|
||||
/// <summary>变更类型</summary>
|
||||
[Column(Name = "change_status")] public Employee_ChangeStatus_Enum ChangeStatus { get; private set; }
|
||||
|
||||
/// <summary>变更说明(部门、岗位等)</summary>
|
||||
[Column(Name = "change_shuoming")] public string ChangeShuoming { get; private set; }
|
||||
|
||||
/// <summary>变更原因</summary>
|
||||
[Column(Name = "change_yuanyin")] public string ChangeYuanyin { get; private set; }
|
||||
|
||||
public EmployeeChangeRecord() { }
|
||||
|
||||
public EmployeeChangeRecord(long id, long employeeId, DateTime date, Employee_ChangeStatus_Enum changeStatus,
|
||||
string changeShuoming, string changeYuanyin)
|
||||
{
|
||||
Id = id;
|
||||
EmployeeId = employeeId;
|
||||
Date = date;
|
||||
ChangeStatus = changeStatus;
|
||||
ChangeShuoming = changeShuoming;
|
||||
ChangeYuanyin = changeYuanyin;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using FreeSql.DataAnnotations;
|
||||
|
||||
namespace F9WebAiTest.Domain.Entities.SYS.Employees;
|
||||
|
||||
/// <summary>职工调岗记录(记录调岗前后的部门与工种)</summary>
|
||||
[Table(Name = "sys_employee_tiaogang_record")]
|
||||
public class EmployeeTiaogangRecord : BusinessEntity
|
||||
{
|
||||
[Column(Name = "employee_id")] public long EmployeeId { get; private set; }
|
||||
|
||||
[Column(Name = "old_depid")] public long OldDepId { get; private set; }
|
||||
|
||||
[Column(Name = "old_worktypeid")] public long OldWorktypeId { get; private set; }
|
||||
|
||||
[Column(Name = "new_depid")] public long NewDepId { get; private set; }
|
||||
|
||||
[Column(Name = "new_worktypeid")] public long NewWorktypeId { get; private set; }
|
||||
|
||||
/// <summary>调岗日期</summary>
|
||||
[Column(Name = "tiaogang_date")] public DateTime TiaogangDate { get; private set; }
|
||||
|
||||
public EmployeeTiaogangRecord() { }
|
||||
|
||||
public EmployeeTiaogangRecord(long id, long employeeId, long oldDepId, long oldWorktypeId, long newDepId,
|
||||
long newWorktypeId, DateTime tiaogangDate)
|
||||
{
|
||||
Id = id;
|
||||
EmployeeId = employeeId;
|
||||
OldDepId = oldDepId;
|
||||
OldWorktypeId = oldWorktypeId;
|
||||
NewDepId = newDepId;
|
||||
NewWorktypeId = newWorktypeId;
|
||||
TiaogangDate = tiaogangDate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace F9WebAiTest.Domain.Enums.SYS.Employees;
|
||||
|
||||
/// <summary>职工在职状态(与 F9 的 Employee_WorkStatus_Enum 一致)</summary>
|
||||
public enum Employee_WorkStatus_Enum
|
||||
{
|
||||
在职 = 0,
|
||||
离职 = 1
|
||||
}
|
||||
|
||||
/// <summary>生产班次(与 F9 的 Prod_Banci_Enum 一致)</summary>
|
||||
public enum Prod_Banci_Enum
|
||||
{
|
||||
甲 = 1,
|
||||
乙 = 2
|
||||
}
|
||||
|
||||
/// <summary>职工变更记录类型(与 F9 的 Employee_ChangeStatus_Enum 一致)</summary>
|
||||
public enum Employee_ChangeStatus_Enum
|
||||
{
|
||||
新入职 = 1,
|
||||
调岗 = 2,
|
||||
离职 = 3,
|
||||
再入职 = 4,
|
||||
转正 = 5
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
using F9WebAiTest.Domain.Entities;
|
||||
|
||||
namespace F9WebAiTest.Domain.Events.SYS.Employees;
|
||||
|
||||
public record Employee_ChangedDomainEvent(long employeeId) : DomainEvent;
|
||||
@@ -0,0 +1,12 @@
|
||||
using F9WebAiTest.Domain.Entities.SYS.Employees;
|
||||
|
||||
namespace F9WebAiTest.Domain.Interfaces.SYS.Employees;
|
||||
|
||||
/// <summary>职工写仓储(聚合根仓储)</summary>
|
||||
public interface IEmployeeWriteRepository : IDInterface
|
||||
{
|
||||
Task<Employee> GetByIdAsync(long id, CancellationToken cancellationToken);
|
||||
Task<bool> AddAsync(Employee entity);
|
||||
Task<bool> UpdateAsync(Employee entity);
|
||||
Task<bool> SoftDeleteAsync(long id);
|
||||
}
|
||||
@@ -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
@@ -7,6 +7,7 @@ using F9WebAiTest.Application.Interfaces.Repositories.SYS.RBAC.Users;
|
||||
using F9WebAiTest.Application.Interfaces.Repositories.SYS.WorkTypes;
|
||||
using F9WebAiTest.Application.Interfaces.Repositories.SYS.Registries;
|
||||
using F9WebAiTest.Application.Interfaces.Repositories.SYS.YuanliaoZhuangkous;
|
||||
using F9WebAiTest.Application.Interfaces.Repositories.SYS.Employees;
|
||||
using F9WebAiTest.Application.Interfaces.Repositories.SYS.Gongyingshangs;
|
||||
using F9WebAiTest.Application.Interfaces.Repositories.SYS.WuziMulus;
|
||||
using F9WebAiTest.Application.Interfaces.Repositories.SYS.Zq;
|
||||
@@ -23,6 +24,7 @@ using F9WebAiTest.Domain.Interfaces.SYS.RBAC.Users;
|
||||
using F9WebAiTest.Domain.Interfaces.SYS.WorkTypes;
|
||||
using F9WebAiTest.Domain.Interfaces.SYS.Registries;
|
||||
using F9WebAiTest.Domain.Interfaces.SYS.YuanliaoZhuangkous;
|
||||
using F9WebAiTest.Domain.Interfaces.SYS.Employees;
|
||||
using F9WebAiTest.Domain.Interfaces.SYS.Gongyingshangs;
|
||||
using F9WebAiTest.Domain.Interfaces.SYS.WuziMulus;
|
||||
using F9WebAiTest.Domain.Interfaces.SYS.Zq;
|
||||
@@ -35,6 +37,7 @@ using F9WebAiTest.Infrastructure.FreeSqls.Repositories.SYS.RBAC.Users;
|
||||
using F9WebAiTest.Infrastructure.FreeSqls.Repositories.SYS.WorkTypes;
|
||||
using F9WebAiTest.Infrastructure.FreeSqls.Repositories.SYS.Registries;
|
||||
using F9WebAiTest.Infrastructure.FreeSqls.Repositories.SYS.YuanliaoZhuangkous;
|
||||
using F9WebAiTest.Infrastructure.FreeSqls.Repositories.SYS.Employees;
|
||||
using F9WebAiTest.Infrastructure.FreeSqls.Repositories.SYS.Gongyingshangs;
|
||||
using F9WebAiTest.Infrastructure.FreeSqls.Repositories.SYS.WuziMulus;
|
||||
using F9WebAiTest.Infrastructure.FreeSqls.Repositories.SYS.Zq;
|
||||
@@ -97,6 +100,7 @@ public static class DependencyInjection
|
||||
services.AddScoped<IYuanliaoZhuangkouWriteRepository, YuanliaoZhuangkouWriteRepository>();
|
||||
services.AddScoped<IWuziGroupWriteRepository, WuziGroupWriteRepository>();
|
||||
services.AddScoped<IGongyingshangWriteRepository, GongyingshangWriteRepository>();
|
||||
services.AddScoped<IEmployeeWriteRepository, EmployeeWriteRepository>();
|
||||
services.AddScoped<IWuziMuluWriteRepository, WuziMuluWriteRepository>();
|
||||
|
||||
// 读仓储(Application 接口)
|
||||
@@ -110,6 +114,7 @@ public static class DependencyInjection
|
||||
services.AddScoped<IYuanliaoZhuangkouReadRepository, YuanliaoZhuangkouReadRepository>();
|
||||
services.AddScoped<IWuziGroupReadRepository, WuziGroupReadRepository>();
|
||||
services.AddScoped<IGongyingshangReadRepository, GongyingshangReadRepository>();
|
||||
services.AddScoped<IEmployeeReadRepository, EmployeeReadRepository>();
|
||||
services.AddScoped<IWuziMuluReadRepository, WuziMuluReadRepository>();
|
||||
|
||||
return services;
|
||||
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
using FreeSql;
|
||||
using F9WebAiTest.Application.Common;
|
||||
using F9WebAiTest.Application.Interfaces.SnowflakeID;
|
||||
using F9WebAiTest.Application.Dtos.SYS.Employees;
|
||||
using F9WebAiTest.Application.Interfaces.Repositories.SYS.Employees;
|
||||
using F9WebAiTest.Domain.Entities.SYS.Departments;
|
||||
using F9WebAiTest.Domain.Entities.SYS.Employees;
|
||||
using F9WebAiTest.Domain.Entities.SYS.WorkTypes;
|
||||
using F9WebAiTest.Domain.Enums.SYS.Employees;
|
||||
using F9WebAiTest.Domain.Interfaces.SYS.Employees;
|
||||
|
||||
namespace F9WebAiTest.Infrastructure.FreeSqls.Repositories.SYS.Employees;
|
||||
|
||||
internal class EmployeeWriteRepository : F9RepositoryBase, IEmployeeWriteRepository
|
||||
{
|
||||
public EmployeeWriteRepository(IFreeSql fsql) : base(fsql) { }
|
||||
|
||||
public async Task<Employee> GetByIdAsync(long id, CancellationToken cancellationToken)
|
||||
=> await Orm.Select<Employee>().Where(x => x.Id == id && x.DeletedAt == null).FirstAsync(cancellationToken);
|
||||
|
||||
public async Task<bool> AddAsync(Employee entity)
|
||||
{
|
||||
entity.CreatedAt = DateTime.Now;
|
||||
return await Orm.Insert(entity).ExecuteAffrowsAsync() > 0;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateAsync(Employee entity)
|
||||
{
|
||||
entity.UpdatedAt = DateTime.Now;
|
||||
return await Orm.Update<Employee>().SetSource(entity).ExecuteAffrowsAsync() > 0;
|
||||
}
|
||||
|
||||
public async Task<bool> SoftDeleteAsync(long id)
|
||||
=> await Orm.Update<Employee>()
|
||||
.Set(x => x.IsDeleted, true)
|
||||
.Set(x => x.DeletedAt, DateTime.Now)
|
||||
.Where(x => x.Id == id)
|
||||
.ExecuteAffrowsAsync() > 0;
|
||||
}
|
||||
|
||||
internal class EmployeeReadRepository : F9RepositoryBase, IEmployeeReadRepository
|
||||
{
|
||||
private readonly ISnowflakeIdGenerator _snowflakeIdGenerator;
|
||||
|
||||
public EmployeeReadRepository(IFreeSql fsql, ISnowflakeIdGenerator snowflakeIdGenerator) : base(fsql)
|
||||
=> _snowflakeIdGenerator = snowflakeIdGenerator;
|
||||
|
||||
public async Task<PageResult<EmployeeReadDto_View>> GetPagedAsync(long depId, long worktypeId,
|
||||
Employee_WorkStatus_Enum? workStatus, Prod_Banci_Enum? banci, string keyword, int pageIndex, int pageSize)
|
||||
{
|
||||
if (pageIndex <= 0) pageIndex = 1;
|
||||
if (pageSize <= 0) pageSize = 20;
|
||||
|
||||
var query = Orm.Select<Employee>().Where(x => x.DeletedAt == null);
|
||||
|
||||
if (depId > 0)
|
||||
{
|
||||
var depIds = await GetDepIdsWithChildrenAsync(depId);
|
||||
query = query.Where(x => depIds.Contains(x.DepId));
|
||||
}
|
||||
if (worktypeId > 0) query = query.Where(x => x.WorktypeId == worktypeId);
|
||||
if (workStatus.HasValue) query = query.Where(x => x.WorkStatus == workStatus.Value);
|
||||
if (banci.HasValue) query = query.Where(x => x.Banci == banci.Value);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(keyword))
|
||||
{
|
||||
var kw = keyword.Trim();
|
||||
query = query.Where(x => x.Name.Contains(kw) || x.Code.Contains(kw) || x.Phone.Contains(kw)
|
||||
|| x.IdCard.Contains(kw) || x.Gangwei.Contains(kw) || x.NamePinyin.Contains(kw));
|
||||
}
|
||||
|
||||
var total = await query.CountAsync();
|
||||
var items = await query
|
||||
.OrderBy(x => x.DepId)
|
||||
.OrderBy(x => x.Sort)
|
||||
.OrderBy(x => x.Name)
|
||||
.Page(pageIndex, pageSize)
|
||||
.ToListAsync(x => new EmployeeReadDto_View(x.Id, x.DepId, "", x.WorktypeId, "", x.Banci, "", x.Gangwei,
|
||||
x.WorkStatus, x.IsZhuanzheng, x.Code, x.Name, x.Gender, 0, x.IdCard, x.Phone, x.BirthDate, x.RuzhiDate,
|
||||
x.ZhuanzhengDate, x.LizhiDate, x.LizhiYuanyin, x.WorkGroup, x.Sort, x.BankcardCode, x.HetongType,
|
||||
x.HetongStartDate, x.HetongEndDate, x.TuixiuDate, x.ShebaoDate, x.Minzu, x.HunyinStatus, x.Xueli, x.Huji,
|
||||
x.Address, x.JinjiLianxiren, x.JinjiLianxirenPhone, x.BiyeYuanxiao, x.Zhuanye, x.NamePinyin, x.Memo));
|
||||
|
||||
if (items.Count == 0)
|
||||
{
|
||||
return new PageResult<EmployeeReadDto_View> { Items = items, Total = total, PageIndex = pageIndex, PageSize = pageSize };
|
||||
}
|
||||
|
||||
var depNameMap = await GetDepNameMapAsync();
|
||||
var worktypeNameMap = await GetWorktypeNameMapAsync();
|
||||
|
||||
var list = items
|
||||
.Select(x => x with
|
||||
{
|
||||
DepName = depNameMap.TryGetValue(x.DepId, out var dn) ? dn : string.Empty,
|
||||
WorktypeName = worktypeNameMap.TryGetValue(x.WorktypeId, out var wn) ? wn : string.Empty,
|
||||
BanciName = x.Banci.HasValue ? x.Banci.Value.ToString() : string.Empty,
|
||||
Nianling = CalcAge(x.BirthDate, x.IdCard)
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return new PageResult<EmployeeReadDto_View> { Items = list, Total = total, PageIndex = pageIndex, PageSize = pageSize };
|
||||
}
|
||||
|
||||
public async Task<EmployeeReadDto_Edit> GetByIdForEditAsync(long id)
|
||||
=> await Orm.Select<Employee>()
|
||||
.Where(x => x.Id == id && x.DeletedAt == null)
|
||||
.FirstAsync(x => new EmployeeReadDto_Edit(x.Id, x.DepId, x.WorktypeId, x.WorkStatus, x.Banci, x.Code,
|
||||
x.Name, x.Gangwei, x.Phone, x.IdCard, x.BirthDate, x.Address, x.Gender, x.Minzu, x.HunyinStatus, x.Huji,
|
||||
x.RuzhiDate, x.LizhiDate, x.ZhuanzhengDate, x.IsZhuanzheng, x.JinjiLianxiren, x.JinjiLianxirenPhone,
|
||||
x.HetongType, x.HetongStartDate, x.HetongEndDate, x.TuixiuDate, x.ShebaoDate, x.ShebaoDesc,
|
||||
x.BankcardCode, x.BiyeYuanxiao, x.Zhuanye, x.Xueli, x.WorkGroup, x.Sort, x.NamePinyin, x.Memo,
|
||||
x.LizhiYuanyin));
|
||||
|
||||
public async Task<List<EmployeeChangeRecordDto>> GetChangeRecordsAsync(long employeeId)
|
||||
{
|
||||
var rows = await Orm.Select<EmployeeChangeRecord>()
|
||||
.Where(x => x.EmployeeId == employeeId)
|
||||
.OrderByDescending(x => x.Date)
|
||||
.ToListAsync(x => new EmployeeChangeRecordDto(x.Id, x.EmployeeId, x.Date, x.ChangeStatus, "",
|
||||
x.ChangeShuoming, x.ChangeYuanyin));
|
||||
|
||||
return rows.Select(x => x with { ChangeStatusName = x.ChangeStatus.ToString() }).ToList();
|
||||
}
|
||||
|
||||
public async Task<List<EmployeeTiaogangRecordDto>> GetTiaogangRecordsAsync(long employeeId)
|
||||
{
|
||||
var rows = await Orm.Select<EmployeeTiaogangRecord>()
|
||||
.Where(x => x.EmployeeId == employeeId)
|
||||
.OrderByDescending(x => x.TiaogangDate)
|
||||
.ToListAsync(x => new EmployeeTiaogangRecordDto(x.Id, x.EmployeeId, x.OldDepId, "", x.OldWorktypeId, "",
|
||||
x.NewDepId, "", x.NewWorktypeId, "", x.TiaogangDate));
|
||||
|
||||
if (rows.Count == 0) return rows;
|
||||
|
||||
var depNameMap = await GetDepNameMapAsync();
|
||||
var worktypeNameMap = await GetWorktypeNameMapAsync();
|
||||
|
||||
return rows
|
||||
.Select(x => x with
|
||||
{
|
||||
OldDepName = depNameMap.TryGetValue(x.OldDepId, out var odn) ? odn : string.Empty,
|
||||
NewDepName = depNameMap.TryGetValue(x.NewDepId, out var ndn) ? ndn : string.Empty,
|
||||
OldWorktypeName = worktypeNameMap.TryGetValue(x.OldWorktypeId, out var own) ? own : string.Empty,
|
||||
NewWorktypeName = worktypeNameMap.TryGetValue(x.NewWorktypeId, out var nwn) ? nwn : string.Empty
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task<bool> AddChangeRecordAsync(EmployeeChangeRecordDto record)
|
||||
{
|
||||
var entity = new EmployeeChangeRecord(_snowflakeIdGenerator.CreateId(), record.EmployeeId,
|
||||
record.Date, record.ChangeStatus, record.ChangeShuoming ?? string.Empty, record.ChangeYuanyin ?? string.Empty)
|
||||
{
|
||||
CreatedAt = DateTime.Now
|
||||
};
|
||||
return await Orm.Insert(entity).ExecuteAffrowsAsync() > 0;
|
||||
}
|
||||
|
||||
public async Task<bool> AddTiaogangRecordAsync(long employeeId, long oldDepId, long oldWorktypeId, long newDepId,
|
||||
long newWorktypeId, DateTime date)
|
||||
{
|
||||
var entity = new EmployeeTiaogangRecord(_snowflakeIdGenerator.CreateId(), employeeId, oldDepId,
|
||||
oldWorktypeId, newDepId, newWorktypeId, date)
|
||||
{
|
||||
CreatedAt = DateTime.Now
|
||||
};
|
||||
return await Orm.Insert(entity).ExecuteAffrowsAsync() > 0;
|
||||
}
|
||||
|
||||
public async Task<string> GetDepNameAsync(long depId)
|
||||
{
|
||||
if (depId <= 0) return string.Empty;
|
||||
var dep = await Orm.Select<Department>().Where(x => x.Id == depId).FirstAsync();
|
||||
return dep?.Name ?? string.Empty;
|
||||
}
|
||||
|
||||
public async Task<string> GetWorktypeNameAsync(long worktypeId)
|
||||
{
|
||||
if (worktypeId <= 0) return string.Empty;
|
||||
var worktype = await Orm.Select<WorkType>().Where(x => x.Id == worktypeId).FirstAsync();
|
||||
return worktype?.WorkName ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>取部门及其全部下级部门 Id</summary>
|
||||
private async Task<List<long>> GetDepIdsWithChildrenAsync(long rootDepId)
|
||||
{
|
||||
var all = await Orm.Select<Department>().Where(x => x.DeletedAt == null).ToListAsync();
|
||||
var result = new HashSet<long> { rootDepId };
|
||||
|
||||
var changed = true;
|
||||
while (changed)
|
||||
{
|
||||
changed = false;
|
||||
foreach (var dep in all)
|
||||
{
|
||||
if (dep.ParentId != 0 && result.Contains(dep.ParentId) && result.Add(dep.Id)) changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToList();
|
||||
}
|
||||
|
||||
private async Task<Dictionary<long, string>> GetDepNameMapAsync()
|
||||
=> (await Orm.Select<Department>().Where(x => x.DeletedAt == null).ToListAsync())
|
||||
.ToDictionary(x => x.Id, x => x.Name);
|
||||
|
||||
private async Task<Dictionary<long, string>> GetWorktypeNameMapAsync()
|
||||
=> (await Orm.Select<WorkType>().Where(x => x.DeletedAt == null).ToListAsync())
|
||||
.ToDictionary(x => x.Id, x => x.WorkName);
|
||||
|
||||
/// <summary>年龄:优先出生日期,其次从身份证号推算</summary>
|
||||
private static int CalcAge(DateTime? birthDate, string idCard)
|
||||
{
|
||||
var birth = birthDate;
|
||||
|
||||
if (birth == null && !string.IsNullOrWhiteSpace(idCard))
|
||||
{
|
||||
var card = idCard.Trim();
|
||||
string? datePart = card.Length switch
|
||||
{
|
||||
18 => card.Substring(6, 8),
|
||||
15 => "19" + card.Substring(6, 6),
|
||||
_ => null
|
||||
};
|
||||
if (datePart != null
|
||||
&& DateTime.TryParseExact(datePart, "yyyyMMdd", null, System.Globalization.DateTimeStyles.None, out var parsed))
|
||||
{
|
||||
birth = parsed;
|
||||
}
|
||||
}
|
||||
|
||||
if (birth == null) return 0;
|
||||
|
||||
var today = DateTime.Today;
|
||||
var age = today.Year - birth.Value.Year;
|
||||
if (birth.Value.Date > today.AddYears(-age)) age--;
|
||||
return age < 0 ? 0 : age;
|
||||
}
|
||||
}
|
||||
+13
-9
@@ -28,11 +28,14 @@ internal class WorkTypeReadRepository : F9RepositoryBase, IWorkTypeReadRepositor
|
||||
.OrderBy(x => x.SortOrder)
|
||||
.OrderBy(x => x.Id)
|
||||
.Page(pageIndex, pageSize)
|
||||
.ToListAsync(x => new WorkTypeReadDto_View(x.Id, x.WorkName, x.SortOrder, x.BizType, x.SccbFenlei, x.IsActive, ""));
|
||||
.ToListAsync(x => new WorkTypeReadDto_View(x.Id, x.WorkName, x.SortOrder, x.BizType, x.SccbFenlei, x.IsActive, "", null));
|
||||
|
||||
var nameMap = await GetDepartmentNameMapAsync(items.Select(x => x.Id).ToList());
|
||||
// 部门名称与 Id 一次性带出,前端无需再逐个工种查询关联部门
|
||||
var infoMap = await GetDepartmentInfoMapAsync(items.Select(x => x.Id).ToList());
|
||||
var list = items
|
||||
.Select(x => x with { DepartmentNames = nameMap.TryGetValue(x.Id, out var names) ? names : string.Empty })
|
||||
.Select(x => infoMap.TryGetValue(x.Id, out var info)
|
||||
? x with { DepartmentNames = info.Names, DepartmentIds = info.Ids }
|
||||
: x with { DepartmentNames = string.Empty, DepartmentIds = new List<long>() })
|
||||
.ToList();
|
||||
|
||||
return new PageResult<WorkTypeReadDto_View>
|
||||
@@ -61,10 +64,10 @@ internal class WorkTypeReadRepository : F9RepositoryBase, IWorkTypeReadRepositor
|
||||
.OrderBy(x => x.AssignedAt)
|
||||
.ToListAsync(x => new WorkTypeRoleReadDto_Cache(x.Id, x.WorkTypeId, x.RoleId));
|
||||
|
||||
/// <summary>工种 Id → 所属部门名称串(顿号分隔)</summary>
|
||||
private async Task<Dictionary<long, string>> GetDepartmentNameMapAsync(List<long> workTypeIds)
|
||||
/// <summary>工种 Id → 所属部门名称串(顿号分隔)与部门 Id 列表</summary>
|
||||
private async Task<Dictionary<long, (string Names, List<long> Ids)>> GetDepartmentInfoMapAsync(List<long> workTypeIds)
|
||||
{
|
||||
var map = new Dictionary<long, string>();
|
||||
var map = new Dictionary<long, (string, List<long>)>();
|
||||
if (workTypeIds == null || workTypeIds.Count == 0) return map;
|
||||
|
||||
var relations = await Orm.Select<WorkTypeDepartment>()
|
||||
@@ -78,11 +81,12 @@ internal class WorkTypeReadRepository : F9RepositoryBase, IWorkTypeReadRepositor
|
||||
|
||||
foreach (var group in relations.GroupBy(x => x.WorkTypeId))
|
||||
{
|
||||
var names = group
|
||||
.Select(r => depNameMap.TryGetValue(r.DepartmentId, out var name) ? name : null)
|
||||
var ids = group.Select(r => r.DepartmentId).Distinct().ToList();
|
||||
var names = ids
|
||||
.Select(id => depNameMap.TryGetValue(id, out var name) ? name : null)
|
||||
.Where(name => !string.IsNullOrEmpty(name))
|
||||
.ToList();
|
||||
map[group.Key] = string.Join("、", names);
|
||||
map[group.Key] = (string.Join("、", names), ids);
|
||||
}
|
||||
|
||||
return map;
|
||||
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
# ============================================================
|
||||
# 将「智慧缫丝系统」(f9-web-ai-test) 发布到本机 IIS
|
||||
# - 自动安装 ASP.NET Core Hosting Bundle(IIS 托管 .NET 8 必需)
|
||||
# - 创建应用池 / 站点 / 目录权限,并做一次自检
|
||||
# 必须以【管理员身份】运行
|
||||
# ============================================================
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# 全过程写入日志文件,便于提权窗口关闭后回看
|
||||
$logDir = Join-Path $PSScriptRoot '_runlog'
|
||||
New-Item -ItemType Directory -Force -Path $logDir | Out-Null
|
||||
try { Start-Transcript -Path (Join-Path $logDir 'deploy-iis.log') -Force -ErrorAction Stop | Out-Null } catch { }
|
||||
|
||||
$publishDir = 'C:\Users\32453\CodeBuddy\20260825142915\publish'
|
||||
$siteName = 'F9WebAiTest'
|
||||
$appPoolName = 'F9WebAiTest'
|
||||
$preferredPort = 80
|
||||
$ancmPath = 'C:\Program Files\IIS\Asp.Net Core Module\V2\aspnetcorev2.dll'
|
||||
$hostingBundle = 'https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/8.0.21/dotnet-hosting-8.0.21-win.exe'
|
||||
$installer = Join-Path $env:TEMP 'dotnet-hosting-8.0.21-win.exe'
|
||||
|
||||
function Info($m) { Write-Host "[部署] $m" -ForegroundColor Cyan }
|
||||
function Warn($m) { Write-Host "[警告] $m" -ForegroundColor Yellow }
|
||||
|
||||
# ---------- 0. 前置校验 ----------
|
||||
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
throw '请以管理员身份运行本脚本'
|
||||
}
|
||||
if (-not (Test-Path $publishDir)) { throw "发布目录不存在:$publishDir" }
|
||||
if (-not (Test-Path (Join-Path $publishDir 'wwwroot\index.html'))) { throw "前端产物缺失:$publishDir\wwwroot\index.html" }
|
||||
if (-not (Test-Path (Join-Path $publishDir 'F9WebAiTest.Api.dll'))) { throw "后端产物缺失:$publishDir\F9WebAiTest.Api.dll" }
|
||||
if (-not (Test-Path 'C:\Windows\System32\inetsrv\appcmd.exe')) { throw '本机未安装 IIS,请先启用 IIS 功能' }
|
||||
|
||||
Import-Module WebAdministration
|
||||
|
||||
# ---------- 1. ASP.NET Core Hosting Bundle ----------
|
||||
if (-not (Test-Path $ancmPath)) {
|
||||
Info '未检测到 AspNetCoreModuleV2,开始下载 Hosting Bundle 8.0.21 ...'
|
||||
Invoke-WebRequest -Uri $hostingBundle -OutFile $installer -UseBasicParsing
|
||||
Info '下载完成,静默安装中(约 1-2 分钟)...'
|
||||
Start-Process -FilePath $installer -ArgumentList '/install', '/quiet', '/norestart' -Wait
|
||||
if (-not (Test-Path $ancmPath)) { throw 'Hosting Bundle 安装失败,请手动安装后重试' }
|
||||
Info '安装完成,重启 IIS 使模块生效 ...'
|
||||
& iisreset /noforce | Out-Null
|
||||
} else {
|
||||
Info 'AspNetCoreModuleV2 已安装,跳过'
|
||||
}
|
||||
|
||||
# ---------- 2. 选择端口 ----------
|
||||
$port = $preferredPort
|
||||
$occupied = netstat -ano | Select-String 'LISTENING' | Select-String ":$port\s"
|
||||
if ($occupied) {
|
||||
$port = 8080
|
||||
Warn "端口 $preferredPort 已被占用,改用 $port"
|
||||
}
|
||||
|
||||
# ---------- 3. 应用池(无托管代码,与 .NET Core 应用匹配)----------
|
||||
if (Test-Path "IIS:\AppPools\$appPoolName") {
|
||||
Info "应用池 $appPoolName 已存在,更新配置"
|
||||
} else {
|
||||
New-WebAppPool -Name $appPoolName | Out-Null
|
||||
Info "已创建应用池 $appPoolName"
|
||||
}
|
||||
Set-ItemProperty "IIS:\AppPools\$appPoolName" -Name managedRuntimeVersion -Value ''
|
||||
Set-ItemProperty "IIS:\AppPools\$appPoolName" -Name startMode -Value 'AlwaysRunning'
|
||||
|
||||
# ---------- 4. 站点 ----------
|
||||
if (Test-Path "IIS:\Sites\$siteName") {
|
||||
Info "站点 $siteName 已存在,更新物理路径与应用池"
|
||||
Set-ItemProperty "IIS:\Sites\$siteName" -Name physicalPath -Value $publishDir
|
||||
Set-ItemProperty "IIS:\Sites\$siteName" -Name applicationPool -Value $appPoolName
|
||||
if (-not (Get-WebBinding -Name $siteName -Protocol http -Port $port -ErrorAction SilentlyContinue)) {
|
||||
New-WebBinding -Name $siteName -Protocol http -Port $port -IPAddress '*'
|
||||
Info "已补充绑定 http://*:$port"
|
||||
}
|
||||
} else {
|
||||
New-Website -Name $siteName -PhysicalPath $publishDir -Port $port -ApplicationPool $appPoolName -Force | Out-Null
|
||||
Info "已创建站点 $siteName"
|
||||
}
|
||||
|
||||
# ---------- 5. 目录权限(应用池身份需读写:SQLite 库文件、日志)----------
|
||||
$aclUser = "IIS AppPool\$appPoolName"
|
||||
& icacls $publishDir /grant "${aclUser}:(OI)(CI)M" /T /Q | Out-Null
|
||||
Info "已授予 $aclUser 读写权限"
|
||||
|
||||
# ---------- 6. 启动 ----------
|
||||
if ((Get-WebAppPoolState -Name $appPoolName).Value -ne 'Started') { Start-WebAppPool -Name $appPoolName }
|
||||
if ((Get-Website -Name $siteName).State -ne 'Started') { Start-Website -Name $siteName }
|
||||
Start-Sleep -Seconds 4
|
||||
|
||||
# ---------- 7. 自检 ----------
|
||||
$url = "http://localhost:$port/"
|
||||
Info '开始自检 ...'
|
||||
try {
|
||||
$r = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 40
|
||||
Info "首页:HTTP $($r.StatusCode)(前端已托管)"
|
||||
} catch {
|
||||
Warn "首页访问失败:$($_.Exception.Message)"
|
||||
Warn "如为 500,请查看 事件查看器 → Windows 日志 → 应用程序,或打开 $publishDir\web.config 把 stdoutLogEnabled 改为 true 后重试"
|
||||
}
|
||||
try {
|
||||
$login = Invoke-RestMethod -Uri "${url}api/auth/login" -Method Post -ContentType 'application/json' `
|
||||
-Body '{"userName":"admin","password":"123456"}' -TimeoutSec 40
|
||||
Info "登录接口:code=$($login.code) $($login.message)"
|
||||
} catch {
|
||||
Warn "登录接口调用失败:$($_.Exception.Message)"
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Info "部署完成,访问地址:$url"
|
||||
Info "API 文档:${url}swagger 默认账号:admin / 123456"
|
||||
@@ -0,0 +1,78 @@
|
||||
# ============================================================
|
||||
# 一键更新 IIS 上的「智慧缫丝系统」(f9-web-ai-test)
|
||||
# 改动代码后运行:停站点 → 重新发布后端 → 同步前端产物 → 启站点 → 自检
|
||||
# 前置:先执行过 deploy-iis.ps1;前端已用 npm run build 构建
|
||||
# 必须以【管理员身份】运行(停/启 IIS 站点、替换被占用的程序文件)
|
||||
# ============================================================
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$root = 'C:\Users\32453\CodeBuddy\20260825142915'
|
||||
$webDir = Join-Path $root 'web'
|
||||
$distDir = Join-Path $webDir 'dist'
|
||||
$apiProject = Join-Path $root 'backend\F9WebAiTest.Api'
|
||||
$publishDir = Join-Path $root 'publish'
|
||||
$appPoolName = 'F9WebAiTest'
|
||||
$siteName = 'F9WebAiTest'
|
||||
|
||||
$logDir = Join-Path $root '_runlog'
|
||||
New-Item -ItemType Directory -Force -Path $logDir | Out-Null
|
||||
try { Start-Transcript -Path (Join-Path $logDir 'update-iis.log') -Force -ErrorAction Stop | Out-Null } catch { }
|
||||
|
||||
function Info($m) { Write-Host "[更新] $m" -ForegroundColor Cyan }
|
||||
function Warn($m) { Write-Host "[警告] $m" -ForegroundColor Yellow }
|
||||
|
||||
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
throw '请以管理员身份运行本脚本'
|
||||
}
|
||||
if (-not (Test-Path $distDir)) { throw "前端产物不存在:$distDir(请先在 web 目录执行 npm run build)" }
|
||||
|
||||
# 必须先加载 WebAdministration,IIS: 驱动器才存在(否则站点判断会误报不存在)
|
||||
Import-Module WebAdministration
|
||||
if (-not (Get-Website -Name $siteName -ErrorAction SilentlyContinue)) { throw "IIS 站点 $siteName 不存在,请先运行 deploy-iis.ps1" }
|
||||
|
||||
# ---------- 1. 停站点与应用池(释放 dll 与数据库文件占用)----------
|
||||
if ((Get-Website -Name $siteName).State -ne 'Stopped') { Stop-Website -Name $siteName }
|
||||
if ((Get-WebAppPoolState -Name $appPoolName).Value -ne 'Stopped') { Stop-WebAppPool -Name $appPoolName }
|
||||
Start-Sleep -Seconds 3
|
||||
Info '站点已停止,文件已释放'
|
||||
|
||||
# ---------- 2. 发布后端(Release)----------
|
||||
Info '发布后端(Release)...'
|
||||
& 'C:\Program Files\dotnet\dotnet.exe' publish $apiProject -c Release -o $publishDir 2>&1 |
|
||||
Where-Object { $_ -match 'error|已成功|-> ' } | Select-Object -Last 5
|
||||
if ($LASTEXITCODE -ne 0) { throw "dotnet publish 失败(退出码 $LASTEXITCODE)" }
|
||||
|
||||
# ---------- 3. 同步前端产物到 wwwroot ----------
|
||||
$wwwroot = Join-Path $publishDir 'wwwroot'
|
||||
if (Test-Path $wwwroot) { Remove-Item $wwwroot -Recurse -Force }
|
||||
New-Item -ItemType Directory -Force -Path $wwwroot | Out-Null
|
||||
Copy-Item (Join-Path $distDir '*') $wwwroot -Recurse -Force
|
||||
Info "前端产物已同步:$wwwroot"
|
||||
|
||||
# ---------- 4. 目录权限兜底 ----------
|
||||
& icacls $publishDir /grant "IIS AppPool\${appPoolName}:(OI)(CI)M" /T /Q | Out-Null
|
||||
|
||||
# ---------- 5. 启动站点 ----------
|
||||
Start-WebAppPool -Name $appPoolName
|
||||
Start-Website -Name $siteName
|
||||
Start-Sleep -Seconds 5
|
||||
Info '站点已启动'
|
||||
|
||||
# ---------- 6. 自检 ----------
|
||||
$url = 'http://localhost/'
|
||||
try {
|
||||
$r = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 40
|
||||
Info "首页:HTTP $($r.StatusCode)"
|
||||
} catch {
|
||||
Warn "首页访问失败:$($_.Exception.Message)"
|
||||
}
|
||||
try {
|
||||
$login = Invoke-RestMethod -Uri "${url}api/auth/login" -Method Post -ContentType 'application/json' `
|
||||
-Body '{"userName":"admin","password":"123456"}' -TimeoutSec 40
|
||||
Info "登录接口:code=$($login.code) $($login.message)"
|
||||
} catch {
|
||||
Warn "登录接口调用失败:$($_.Exception.Message)"
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Info "更新完成,访问地址:$url"
|
||||
+119
-37
@@ -87,43 +87,45 @@ async function handleLogout() {
|
||||
<el-icon class="logo-icon"><MagicStick /></el-icon>
|
||||
<span v-if="!isCollapse" class="logo-text">智慧缫丝系统</span>
|
||||
</div>
|
||||
<el-menu
|
||||
:default-active="route.path"
|
||||
:collapse="isCollapse"
|
||||
background-color="#0c3b2e"
|
||||
text-color="#b5cdc2"
|
||||
active-text-color="#e8c877"
|
||||
router
|
||||
class="menu"
|
||||
>
|
||||
<el-menu-item index="/dashboard">
|
||||
<el-icon><HomeFilled /></el-icon>
|
||||
<template #title>首页</template>
|
||||
</el-menu-item>
|
||||
|
||||
<template v-for="menu in sidebarMenus" :key="menu.id">
|
||||
<!-- 有子菜单 -->
|
||||
<el-sub-menu v-if="menu.children && menu.children.length > 0" :index="menu.path">
|
||||
<template #title>
|
||||
<el-icon><component :is="menu.icon || 'Menu'" /></el-icon>
|
||||
<span>{{ menu.name }}</span>
|
||||
</template>
|
||||
<el-menu-item
|
||||
v-for="child in menu.children"
|
||||
:key="child.id"
|
||||
:index="child.path"
|
||||
>
|
||||
<el-icon><component :is="child.icon || 'Document'" /></el-icon>
|
||||
<template #title>{{ child.name }}</template>
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<!-- 无子菜单 -->
|
||||
<el-menu-item v-else :index="menu.path">
|
||||
<el-icon><component :is="menu.icon || 'Menu'" /></el-icon>
|
||||
<template #title>{{ menu.name }}</template>
|
||||
<div class="menu-wrapper">
|
||||
<el-menu
|
||||
:default-active="route.path"
|
||||
:collapse="isCollapse"
|
||||
background-color="#0c3b2e"
|
||||
text-color="#b5cdc2"
|
||||
active-text-color="#e8c877"
|
||||
router
|
||||
class="menu"
|
||||
>
|
||||
<el-menu-item index="/dashboard">
|
||||
<el-icon><HomeFilled /></el-icon>
|
||||
<template #title>首页</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-menu>
|
||||
|
||||
<template v-for="menu in sidebarMenus" :key="menu.id">
|
||||
<!-- 有子菜单 -->
|
||||
<el-sub-menu v-if="menu.children && menu.children.length > 0" :index="menu.path">
|
||||
<template #title>
|
||||
<el-icon><component :is="menu.icon || 'Menu'" /></el-icon>
|
||||
<span>{{ menu.name }}</span>
|
||||
</template>
|
||||
<el-menu-item
|
||||
v-for="child in menu.children"
|
||||
:key="child.id"
|
||||
:index="child.path"
|
||||
>
|
||||
<el-icon><component :is="child.icon || 'Document'" /></el-icon>
|
||||
<template #title>{{ child.name }}</template>
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<!-- 无子菜单 -->
|
||||
<el-menu-item v-else :index="menu.path">
|
||||
<el-icon><component :is="menu.icon || 'Menu'" /></el-icon>
|
||||
<template #title>{{ menu.name }}</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-menu>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- 主区域 -->
|
||||
@@ -187,17 +189,95 @@ async function handleLogout() {
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 220px;
|
||||
background: linear-gradient(180deg, #0f4a38 0%, #0c3b2e 100%);
|
||||
transition: width 0.3s;
|
||||
overflow-x: hidden;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidebar.collapse {
|
||||
width: 64px;
|
||||
}
|
||||
|
||||
/* 折叠态下图标居中优先,不预留滚动槽(此时子菜单以浮层弹出,高度不变化,不会抖动) */
|
||||
.sidebar.collapse .menu-wrapper {
|
||||
scrollbar-gutter: auto;
|
||||
}
|
||||
|
||||
/* 菜单区独立滚动,并始终预留滚动条宽度(scrollbar-gutter: stable):
|
||||
否则展开"基础数据"这类子项较多的目录时,总高度跨过视口,滚动条突然出现/消失,
|
||||
会把菜单横向挤压位移,视觉上就是一卡一卡 */
|
||||
.menu-wrapper {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
scrollbar-gutter: stable;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.18) transparent;
|
||||
}
|
||||
|
||||
.menu-wrapper::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.menu-wrapper::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.menu-wrapper::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.18);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.menu-wrapper::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.32);
|
||||
}
|
||||
|
||||
/* Element Plus 默认用 transition: height 做展开动画,而 height 过渡不是合成层动画:
|
||||
浏览器每一帧都要重新布局容器内的**全部**子菜单项,子项多时(如"基础数据"10 项)
|
||||
在普通机器上会明显掉帧(实测单帧可达 120ms+)。
|
||||
这里去掉 height 过渡,改用只涉及合成层的 opacity + transform 动画,视觉一样但不触发重排。 */
|
||||
.menu-wrapper :deep(.el-collapse-transition-enter-active),
|
||||
.menu-wrapper :deep(.el-collapse-transition-leave-active) {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
/* 淡入只加在整个子菜单容器上(只产生一个合成层),
|
||||
若加在每个子项上,会为 10 个元素分别创建合成层,展开瞬间反而更慢 */
|
||||
.menu-wrapper :deep(.el-menu--inline) {
|
||||
animation: menu-submenu-in 0.16s ease-out;
|
||||
}
|
||||
|
||||
/* 布局/绘制隔离:展开时把每个菜单项的重排限制在自身内部,避免整块重排 */
|
||||
.menu-wrapper :deep(.el-menu-item),
|
||||
.menu-wrapper :deep(.el-sub-menu__title) {
|
||||
contain: layout paint;
|
||||
}
|
||||
|
||||
/* 收起时不要用 display:none(Element Plus 默认行为),而是用 height:0 + 隐藏裁剪:
|
||||
这样子项在页面加载时就已完成布局,展开瞬间无需对全部子项做首次布局,
|
||||
这正是"子项越多越卡"(基础数据 10 项)的根源所在 */
|
||||
.menu-wrapper :deep(.el-sub-menu:not(.is-opened) > .el-menu--inline) {
|
||||
display: block !important;
|
||||
height: 0 !important;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
@keyframes menu-submenu-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
flex: none;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -294,6 +374,8 @@ async function handleLogout() {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
/* 内容区同样预留滚动条宽度,避免切换页面时宽度跳动 */
|
||||
scrollbar-gutter: stable;
|
||||
background: #f0f2f5;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -58,6 +58,7 @@ export const componentMap = {
|
||||
'base/yuanliaozhuangkou/index': () => import('../views/base/yuanliaozhuangkou/index.vue'),
|
||||
'base/wuzimulu/index': () => import('../views/base/wuzimulu/index.vue'),
|
||||
'base/gongyingshang/index': () => import('../views/base/gongyingshang/index.vue'),
|
||||
'base/employee/index': () => import('../views/base/employee/index.vue'),
|
||||
'prod/lsjl/index': () => import('../views/prod/lsjl/index.vue'),
|
||||
'prod/xdjy/index': () => import('../views/prod/xdjy/index.vue'),
|
||||
'prod/hbjy/index': () => import('../views/prod/hbjy/index.vue'),
|
||||
|
||||
@@ -36,6 +36,8 @@ function statusType(status) {
|
||||
}
|
||||
|
||||
function handlePageChange(page) {
|
||||
// 分页组件挂载时会触发一次 current-change,页码未变化时忽略,避免首次打开重复请求
|
||||
if (page === pageIndex.value) return;
|
||||
pageIndex.value = page;
|
||||
loadRecords();
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ function resetSearch() {
|
||||
}
|
||||
|
||||
function handlePageChange(page) {
|
||||
// 分页组件挂载时会触发一次 current-change,页码未变化时忽略,避免首次打开重复请求
|
||||
if (page === query.pageIndex) return;
|
||||
query.pageIndex = page;
|
||||
load();
|
||||
}
|
||||
|
||||
@@ -79,7 +79,12 @@ async function del(row) {
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function handlePageChange(p) { query.pageIndex = p; load(); }
|
||||
function handlePageChange(p) {
|
||||
// 分页组件挂载时会触发一次 current-change,页码未变化时忽略,避免首次打开重复请求
|
||||
if (p === query.pageIndex) return;
|
||||
query.pageIndex = p;
|
||||
load();
|
||||
}
|
||||
onMounted(() => { load(); loadGuige(); });
|
||||
</script>
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -43,6 +43,13 @@ const form = reactive({
|
||||
|
||||
const vendorGroupTypes = [3, 4]; // 副产品 / 生丝 → 默认分组为"客户"
|
||||
|
||||
// 分页组件挂载时会触发一次 current-change:页码未变化时直接忽略,避免页面首次打开重复请求
|
||||
function handlePageChange(p) {
|
||||
if (p === query.pageIndex) return;
|
||||
query.pageIndex = p;
|
||||
loadList();
|
||||
}
|
||||
|
||||
async function loadList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
@@ -190,12 +197,12 @@ onMounted(loadList);
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
v-model:current-page="query.pageIndex"
|
||||
v-model:page-size="query.pageSize"
|
||||
:current-page="query.pageIndex"
|
||||
:page-size="query.pageSize"
|
||||
:total="total"
|
||||
layout="total, prev, pager, next"
|
||||
class="pager"
|
||||
@current-change="loadList"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
|
||||
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="520px">
|
||||
|
||||
@@ -73,7 +73,12 @@ async function del(row) {
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function handlePageChange(p) { query.pageIndex = p; load(); }
|
||||
function handlePageChange(p) {
|
||||
// 分页组件挂载时会触发一次 current-change,页码未变化时忽略,避免首次打开重复请求
|
||||
if (p === query.pageIndex) return;
|
||||
query.pageIndex = p;
|
||||
load();
|
||||
}
|
||||
onMounted(load);
|
||||
</script>
|
||||
|
||||
|
||||
@@ -71,7 +71,12 @@ async function del(row) {
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function handlePageChange(p) { query.pageIndex = p; load(); }
|
||||
function handlePageChange(p) {
|
||||
// 分页组件挂载时会触发一次 current-change,页码未变化时忽略,避免首次打开重复请求
|
||||
if (p === query.pageIndex) return;
|
||||
query.pageIndex = p;
|
||||
load();
|
||||
}
|
||||
onMounted(() => { load(); loadDepts(); });
|
||||
</script>
|
||||
|
||||
|
||||
@@ -13,18 +13,20 @@ const query = reactive({ pageIndex: 1, pageSize: 20, keyword: '' });
|
||||
const bizTypeOptions = ref(['生产', '辅助']);
|
||||
const sccbFenleiOptions = ref(['无', '管理']);
|
||||
|
||||
// 一次拉取注册表全量后本地取用,避免每个键各发一次请求
|
||||
async function loadRegistryOptions() {
|
||||
try {
|
||||
const load = async (keyName) => {
|
||||
const res = await request.get('/sys/registry/by-key', {
|
||||
params: { category: '基础数据\\工种', keyName }
|
||||
});
|
||||
return (res.data?.value || '')
|
||||
const res = await request.get('/sys/registry');
|
||||
const all = res.data || [];
|
||||
const pick = (keyName) => {
|
||||
const hit = all.find((x) => x.category === '基础数据\\工种' && x.keyName === keyName);
|
||||
return (hit?.value || '')
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
};
|
||||
const [biz, fenlei] = await Promise.all([load('业务类型'), load('生产成本分类')]);
|
||||
const biz = pick('业务类型');
|
||||
const fenlei = pick('生产成本分类');
|
||||
if (biz.length) bizTypeOptions.value = biz;
|
||||
if (fenlei.length) sccbFenleiOptions.value = fenlei;
|
||||
} catch (e) {
|
||||
@@ -52,6 +54,13 @@ const boundRoleIds = ref([]);
|
||||
|
||||
const currentRow = ref(null);
|
||||
|
||||
// 分页组件挂载时会触发一次 current-change:页码未变化时直接忽略,避免页面首次打开重复请求
|
||||
function handlePageChange(p) {
|
||||
if (p === query.pageIndex) return;
|
||||
query.pageIndex = p;
|
||||
loadList();
|
||||
}
|
||||
|
||||
async function loadList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
@@ -255,12 +264,12 @@ onMounted(() => {
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
v-model:current-page="query.pageIndex"
|
||||
v-model:page-size="query.pageSize"
|
||||
:current-page="query.pageIndex"
|
||||
:page-size="query.pageSize"
|
||||
:total="total"
|
||||
layout="total, prev, pager, next"
|
||||
class="pager"
|
||||
@current-change="loadList"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
|
||||
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="460px">
|
||||
|
||||
@@ -120,6 +120,13 @@ function handleNodeClick(node) {
|
||||
loadMulu();
|
||||
}
|
||||
|
||||
// 分页组件挂载时会触发一次 current-change:页码未变化时直接忽略,避免页面首次打开重复请求
|
||||
function handlePageChange(p) {
|
||||
if (p === query.pageIndex) return;
|
||||
query.pageIndex = p;
|
||||
loadMulu();
|
||||
}
|
||||
|
||||
async function loadMulu() {
|
||||
loading.value = true;
|
||||
try {
|
||||
@@ -397,12 +404,12 @@ onMounted(loadGroups);
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
v-model:current-page="query.pageIndex"
|
||||
v-model:page-size="query.pageSize"
|
||||
:current-page="query.pageIndex"
|
||||
:page-size="query.pageSize"
|
||||
:total="total"
|
||||
layout="total, prev, pager, next"
|
||||
class="pager"
|
||||
@current-change="loadMulu"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -49,27 +49,22 @@ function bendiwaidiLabel(v) {
|
||||
return hit ? hit.label : v;
|
||||
}
|
||||
|
||||
// 一次拉取注册表全量后本地取用,避免每个键各发一次请求
|
||||
async function loadRegistryOptions() {
|
||||
const load = async (keyName) => {
|
||||
const res = await request.get('/sys/registry/by-key', {
|
||||
params: { category: '基础数据\\原料庄口', keyName }
|
||||
});
|
||||
return (res.data?.value || '')
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
};
|
||||
try {
|
||||
const [chandi, zhandian, jibie, canpinzhong] = await Promise.all([
|
||||
load('产地'),
|
||||
load('站点'),
|
||||
load('季别'),
|
||||
load('蚕品种')
|
||||
]);
|
||||
registryOptions.chandi = chandi;
|
||||
registryOptions.zhandian = zhandian;
|
||||
registryOptions.jibie = jibie;
|
||||
registryOptions.canpinzhong = canpinzhong;
|
||||
const res = await request.get('/sys/registry');
|
||||
const all = res.data || [];
|
||||
const pick = (keyName) => {
|
||||
const hit = all.find((x) => x.category === '基础数据\\原料庄口' && x.keyName === keyName);
|
||||
return (hit?.value || '')
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
};
|
||||
registryOptions.chandi = pick('产地');
|
||||
registryOptions.zhandian = pick('站点');
|
||||
registryOptions.jibie = pick('季别');
|
||||
registryOptions.canpinzhong = pick('蚕品种');
|
||||
} catch (e) {
|
||||
// 注册表未配置时下拉为空,仍可手工录入
|
||||
}
|
||||
@@ -82,6 +77,13 @@ async function loadWuziOptions() {
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// 分页组件挂载时会触发一次 current-change:页码未变化时直接忽略,避免页面首次打开重复请求
|
||||
function handlePageChange(p) {
|
||||
if (p === query.pageIndex) return;
|
||||
query.pageIndex = p;
|
||||
loadList();
|
||||
}
|
||||
|
||||
async function loadList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
@@ -274,12 +276,12 @@ onMounted(async () => {
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
v-model:current-page="query.pageIndex"
|
||||
v-model:page-size="query.pageSize"
|
||||
:current-page="query.pageIndex"
|
||||
:page-size="query.pageSize"
|
||||
:total="total"
|
||||
layout="total, prev, pager, next"
|
||||
class="pager"
|
||||
@current-change="loadList"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
|
||||
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="560px">
|
||||
|
||||
@@ -48,6 +48,13 @@ function defaultRange() {
|
||||
return { start: str(start), end: str(end) };
|
||||
}
|
||||
|
||||
// 分页组件挂载时会触发一次 current-change:页码未变化时直接忽略,避免页面首次打开重复请求
|
||||
function handlePageChange(p) {
|
||||
if (p === query.pageIndex) return;
|
||||
query.pageIndex = p;
|
||||
loadList();
|
||||
}
|
||||
|
||||
async function loadList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
@@ -205,12 +212,12 @@ onMounted(loadList);
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
v-model:current-page="query.pageIndex"
|
||||
v-model:page-size="query.pageSize"
|
||||
:current-page="query.pageIndex"
|
||||
:page-size="query.pageSize"
|
||||
:total="total"
|
||||
layout="total, prev, pager, next"
|
||||
class="pager"
|
||||
@current-change="loadList"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
|
||||
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="480px">
|
||||
|
||||
@@ -80,7 +80,12 @@ async function del(row) {
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function handlePageChange(p) { query.pageIndex = p; load(); }
|
||||
function handlePageChange(p) {
|
||||
// 分页组件挂载时会触发一次 current-change,页码未变化时忽略,避免首次打开重复请求
|
||||
if (p === query.pageIndex) return;
|
||||
query.pageIndex = p;
|
||||
load();
|
||||
}
|
||||
onMounted(load);
|
||||
</script>
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ const userStore = useUserStore();
|
||||
const user = userStore.userInfo || {};
|
||||
|
||||
const quickLinks = [
|
||||
{ title: '落丝记录', desc: '缫丝生产 · 落丝管理', icon: 'DataLine', path: '/prod/lsjl', color: '#1e6b52' },
|
||||
{ title: '纤度检验', desc: '纤度 / 野纤等级判定', icon: 'ScaleToOriginal', path: '/prod/xdjy', color: '#c9a54a' },
|
||||
{ title: '黑板检验', desc: '清洁 / 洁净等级', icon: 'Grid', path: '/prod/hbjy', color: '#3f8f6e' },
|
||||
{ title: '落丝记录', desc: '前缫 · 落丝管理', icon: 'DataLine', path: '/prod/qianxian/lsjl', color: '#1e6b52' },
|
||||
{ title: '纤度检验', desc: '后缫 · 纤度 / 野纤等级判定', icon: 'ScaleToOriginal', path: '/prod/houxian/xdjy', color: '#c9a54a' },
|
||||
{ title: '黑板检验', desc: '后缫 · 清洁 / 洁净等级', icon: 'Grid', path: '/prod/houxian/hbjy', color: '#3f8f6e' },
|
||||
{ title: '考勤打卡', desc: '上下班签到签退', icon: 'AlarmClock', path: '/attendance/punch', color: '#b8873a' }
|
||||
];
|
||||
|
||||
|
||||
@@ -64,7 +64,12 @@ async function markAllRead() {
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function handlePageChange(p) { query.pageIndex = p; load(); }
|
||||
function handlePageChange(p) {
|
||||
// 分页组件挂载时会触发一次 current-change,页码未变化时忽略,避免首次打开重复请求
|
||||
if (p === query.pageIndex) return;
|
||||
query.pageIndex = p;
|
||||
load();
|
||||
}
|
||||
function resetPage() { query.pageIndex = 1; load(); }
|
||||
|
||||
function rowClass({ row }) {
|
||||
|
||||
@@ -31,7 +31,12 @@ async function load() {
|
||||
}
|
||||
}
|
||||
|
||||
function handlePageChange(p) { query.pageIndex = p; load(); }
|
||||
function handlePageChange(p) {
|
||||
// 分页组件挂载时会触发一次 current-change,页码未变化时忽略,避免首次打开重复请求
|
||||
if (p === query.pageIndex) return;
|
||||
query.pageIndex = p;
|
||||
load();
|
||||
}
|
||||
|
||||
// ==================== 编辑对话框 ====================
|
||||
const dialogVisible = ref(false);
|
||||
|
||||
@@ -124,7 +124,12 @@ async function del(row) {
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function handlePageChange(p) { query.pageIndex = p; load(); }
|
||||
function handlePageChange(p) {
|
||||
// 分页组件挂载时会触发一次 current-change,页码未变化时忽略,避免首次打开重复请求
|
||||
if (p === query.pageIndex) return;
|
||||
query.pageIndex = p;
|
||||
load();
|
||||
}
|
||||
|
||||
onMounted(() => { load(); loadDepts(); loadZhuangkou(); loadGuige(); });
|
||||
</script>
|
||||
|
||||
@@ -32,7 +32,12 @@ async function load() {
|
||||
}
|
||||
}
|
||||
|
||||
function handlePageChange(p) { query.pageIndex = p; load(); }
|
||||
function handlePageChange(p) {
|
||||
// 分页组件挂载时会触发一次 current-change,页码未变化时忽略,避免首次打开重复请求
|
||||
if (p === query.pageIndex) return;
|
||||
query.pageIndex = p;
|
||||
load();
|
||||
}
|
||||
|
||||
// ==================== 编辑对话框 ====================
|
||||
const dialogVisible = ref(false);
|
||||
|
||||
@@ -138,7 +138,12 @@ async function handleSaveRoles() {
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function handlePageChange(page) { query.page = page; loadList(); }
|
||||
function handlePageChange(page) {
|
||||
// 分页组件挂载时会触发一次 current-change,页码未变化时忽略,避免首次打开重复请求
|
||||
if (page === query.page) return;
|
||||
query.page = page;
|
||||
loadList();
|
||||
}
|
||||
|
||||
onMounted(() => { loadList(); loadDepts(); loadRoles(); });
|
||||
</script>
|
||||
|
||||
@@ -128,6 +128,8 @@ async function handleResetPwd(row) {
|
||||
}
|
||||
|
||||
function handlePageChange(page) {
|
||||
// 分页组件挂载时会触发一次 current-change,页码未变化时忽略,避免首次打开重复请求
|
||||
if (page === query.pageIndex) return;
|
||||
query.pageIndex = page;
|
||||
loadList();
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ export default defineConfig({
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 5173,
|
||||
// 注意:这里刻意不做 server.warmup 全量预热。
|
||||
// 页面组件一律按需加载:只有点进具体菜单项时,才编译并加载对应页面组件。
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:3000',
|
||||
|
||||
Reference in New Issue
Block a user