生产模块拆分为前缫/后缫;修复菜单卡顿与重复请求;支持 IIS 单站点部署

1. 生产模块按工序拆分为两个一级菜单
   - 生产-前缫(落丝记录)、生产-后缫(纤度检验、黑板检验)
   - 旧的「生产管理」(/prod) 目录及子菜单由种子自动清理,页面组件与权限标识保持不变
   - 消息中心跳转路径、首页快捷入口路径同步更新

2. 职工管理独立为一级菜单(原挂在「基础数据」目录下),并清理历史子菜单

3. 修复侧边栏菜单展开卡顿(基础数据等子项较多的目录尤其明显)
   - 去掉 Element Plus 的 height 过渡动画(会逐帧重排全部子项)
   - 收起时改用 height:0 + visibility:hidden 而非 display:none,子项提前完成布局
   - 菜单区独立滚动并预留滚动条宽度(scrollbar-gutter),消除展开时的横向抖动
   - 菜单项增加 contain 布局隔离,淡入动画改为容器级

4. 修复页面首次打开重复请求
   - 分页组件挂载时会触发一次 current-change,页码未变化时不再重复加载(18 个页面)
   - 职工管理去掉 onMounted 与默认选中部门的重复加载
   - 工种管理、原料庄口的注册表下拉由按 key 多次请求改为一次全量拉取

5. 后端增加前端静态托管与 SPA 路由回退,支持 IIS 单站点部署(前后端同源)
6. 新增部署脚本 deploy-iis.ps1(首次部署)与 update-iis.ps1(更新发布)
This commit is contained in:
yanpeng200212
2026-10-12 14:09:59 +08:00
parent 2ffb83830c
commit c3e2665257
47 changed files with 59270 additions and 109 deletions
@@ -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)); }
}
}
+8
View File
@@ -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.