feat: 完成多模块功能迭代与优化

本次提交包含多模块功能更新:
1. 权限系统重构:移除硬编码admin放行逻辑,新增精细化权限控制,完善权限树形结构与用户/角色保护
2. 新增物料导入导出接口,补充台账查询条件
3. 优化列表查询排序逻辑,新增区域编码大小写不敏感搜索
4. 修复帮助抽屉重复弹出问题,补充页面权限控制
5. 新增弱密码提示逻辑,优化MES/WMS用户/角色管理权限
6. 调整路由结构,新增权限校验脚本与前端权限树重构
7. 补充数据库字段与依赖包更新
This commit is contained in:
SunYF
2026-09-03 18:13:59 +08:00
parent 572fa975bc
commit c1fc6d32b2
47 changed files with 1761 additions and 926 deletions
+199 -2
View File
@@ -2,6 +2,7 @@ package handler
import (
"net/http"
"strings"
"time"
"bj_power_wms/ent"
@@ -9,6 +10,7 @@ import (
"bj_power_wms/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
"github.com/xuri/excelize/v2"
)
// createMaterialHandler 创建物料档案
@@ -164,7 +166,7 @@ func queryMaterialsHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return
}
list, err := q.Order(ent.Desc("created_at")).
list, err := q.Order(ent.Desc("created_at"), ent.Desc("id")).
Offset((page - 1) * pageSize).
Limit(pageSize).
All(ctx0())
@@ -185,7 +187,7 @@ func queryMaterialsHandler(ctx *svc.ServiceContext) http.HandlerFunc {
// listMaterialsHandler 下拉框全量列表(不翻页)
func listMaterialsHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
list, err := ctx.EntClient.Material.Query().Order(ent.Desc("created_at")).All(ctx0())
list, err := ctx.EntClient.Material.Query().Order(ent.Desc("created_at"), ent.Desc("id")).All(ctx0())
if err != nil {
fail(w, http.StatusInternalServerError, err.Error())
return
@@ -210,3 +212,198 @@ func getMaterialHandler(ctx *svc.ServiceContext) http.HandlerFunc {
ok(w, m)
}
}
// exportMaterialsHandler 物料档案全量导出(xlsx),按当前筛选条件导出
func exportMaterialsHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
materialCode := r.URL.Query().Get("materialCode")
name := r.URL.Query().Get("name")
spec := r.URL.Query().Get("spec")
manageMode := atoi(r.URL.Query().Get("manageMode"), 0)
q := ctx.EntClient.Material.Query()
if materialCode != "" {
q = q.Where(material.CodeContains(materialCode))
}
if name != "" {
q = q.Where(material.NameContains(name))
}
if spec != "" {
q = q.Where(material.SpecContains(spec))
}
if manageMode != 0 {
q = q.Where(material.ManageModeEQ(manageMode))
}
list, err := q.Order(ent.Desc("created_at"), ent.Desc("id")).All(ctx0())
if err != nil {
fail(w, http.StatusInternalServerError, err.Error())
return
}
type exportRow struct {
Code string
Name string
ShortName string
Spec string
Unit string
ManageMode int
Description string
CreatedAt int64
}
rows := make([]exportRow, 0, len(list))
for _, m := range list {
rows = append(rows, exportRow{
Code: m.Code, Name: m.Name, ShortName: m.ShortName, Spec: m.Spec,
Unit: m.Unit, ManageMode: m.ManageMode, Description: m.Description, CreatedAt: m.CreatedAt,
})
}
if r.URL.Query().Get("format") == "xlsx" {
headers := []string{"物料编码", "名称", "简称", "规格", "单位", "类型", "说明", "创建时间"}
matrix := make([][]any, 0, len(rows))
for _, rr := range rows {
matrix = append(matrix, []any{
rr.Code, rr.Name, rr.ShortName, rr.Spec, rr.Unit,
manageModeLabel(rr.ManageMode), rr.Description, unixFmt(rr.CreatedAt),
})
}
sendExcel(w, xlsxFilename("物料档案"), headers, matrix)
return
}
ok(w, map[string]any{"total": len(rows), "list": rows})
}
}
// excelMaterialImportHandler 物料档案 Excel 批量导入
// 模板(首行忽略表头):物料编码 | 名称 | 简称 | 规格 | 单位 | 类型(1结构件/2精密件) | 说明
// 强约束:全成功或全失败(一次性全导入 or 全失败,禁止部分成功)。
// 阶段1 逐行解析+校验(编码必填且唯一 / 名称必填 / 类型合法) → 任一错则整体拒绝并列出错误行;
// 阶段2 全部通过 → 开启单事务整体提交,任一步失败整体回滚。
func excelMaterialImportHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if err := r.ParseMultipartForm(20 << 20); err != nil {
fail(w, http.StatusBadRequest, "文件上传失败: "+err.Error())
return
}
f, _, err := r.FormFile("file")
if err != nil {
fail(w, http.StatusBadRequest, "缺少 file 字段")
return
}
defer f.Close()
xl, err := excelize.OpenReader(f)
if err != nil {
fail(w, http.StatusBadRequest, "无法解析 Excel: "+err.Error())
return
}
defer xl.Close()
sheet := xl.GetSheetList()[0]
rows, err := xl.GetRows(sheet)
if err != nil {
fail(w, http.StatusBadRequest, "读取工作表失败: "+err.Error())
return
}
if len(rows) <= 1 {
fail(w, http.StatusBadRequest, "文件无数据行(首行为表头,需至少一行数据)")
return
}
type planRow struct {
row int
code string
name string
shortName string
spec string
unit string
manageMode int
description string
}
plans := make([]planRow, 0, len(rows)-1)
errs := make([]importErr, 0)
// 阶段1:解析 + 校验(不落库)
for i, row := range rows {
if i == 0 {
continue // 表头
}
cell := func(idx int) string {
if idx < len(row) {
return strings.TrimSpace(row[idx])
}
return ""
}
pr := planRow{row: i + 1}
pr.code = cell(0)
pr.name = cell(1)
pr.shortName = cell(2)
pr.spec = cell(3)
pr.unit = cell(4)
pr.manageMode = atoi(cell(5), 1)
pr.description = cell(6)
if pr.code == "" {
errs = append(errs, importErr{pr.row, pr.code, "物料编码为空"})
continue
}
if pr.name == "" {
errs = append(errs, importErr{pr.row, pr.code, "名称为空"})
continue
}
if pr.manageMode != 1 && pr.manageMode != 2 {
errs = append(errs, importErr{pr.row, pr.code, "类型必须为 1(结构件) 或 2(精密件)"})
continue
}
exists, _ := ctx.EntClient.Material.Query().
Where(material.CodeEQ(pr.code)).Exist(ctx0())
if exists {
errs = append(errs, importErr{pr.row, pr.code, "物料编码已存在: " + pr.code})
continue
}
plans = append(plans, pr)
}
// 阶段2:有错则整体拒绝,一行都不写
if len(errs) > 0 {
ok(w, map[string]any{"success": 0, "failed": len(errs), "errors": errs})
return
}
// 阶段3:全部通过 → 单事务整体提交(原子,全成功或全失败)
tx, e := ctx.EntClient.Tx(ctx0())
if e != nil {
fail(w, http.StatusInternalServerError, "开启事务失败: "+e.Error())
return
}
committed := false
defer func() {
if !committed {
_ = tx.Rollback()
}
}()
for _, pr := range plans {
if _, e = tx.Material.Create().
SetCode(pr.code).
SetName(pr.name).
SetNillableShortName(strPtr(pr.shortName)).
SetNillableSpec(strPtr(pr.spec)).
SetNillableUnit(strPtr(pr.unit)).
SetNillableDescription(strPtr(pr.description)).
SetManageMode(pr.manageMode).
SetIsBatchManaged(pr.manageMode == 1).
SetIsSerialManaged(pr.manageMode == 2).
Save(ctx0()); e != nil {
fail(w, http.StatusInternalServerError, "创建物料失败: "+e.Error())
return
}
}
if e = tx.Commit(); e != nil {
fail(w, http.StatusInternalServerError, "提交事务失败: "+e.Error())
return
}
committed = true
ok(w, map[string]any{"success": len(plans), "failed": 0, "errors": []importErr{}})
}
}