diff --git a/bj_power_wms/internal/handler/rbac.go b/bj_power_wms/internal/handler/rbac.go index ddad0d1..8e28a03 100644 --- a/bj_power_wms/internal/handler/rbac.go +++ b/bj_power_wms/internal/handler/rbac.go @@ -147,10 +147,25 @@ func userDTO(u *ent.User) map[string]any { } } -// listUsersHandler GET /api/user/list 管理员查看全部用户 +// listUsersHandler GET /api/user/list 管理员查看全部用户(后端真分页,created_at desc 最新置顶) func listUsersHandler(ctx *svc.ServiceContext) http.HandlerFunc { return requirePerm(ctx, "user:manage", func(w http.ResponseWriter, r *http.Request) { - list, err := ctx.EntClient.User.Query().Order(ent.Asc("id")).All(ctx0()) + page := atoi(r.URL.Query().Get("page"), 1) + pageSize := atoi(r.URL.Query().Get("pageSize"), 20) + if page < 1 { + page = 1 + } + if pageSize < 1 || pageSize > 200 { + pageSize = 20 + } + q := ctx.EntClient.User.Query() + total, err := q.Count(ctx0()) + if err != nil { + fail(w, http.StatusInternalServerError, err.Error()) + return + } + list, err := q.Order(ent.Desc("created_at"), ent.Desc("id")). + Offset((page - 1) * pageSize).Limit(pageSize).All(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) return @@ -159,7 +174,7 @@ func listUsersHandler(ctx *svc.ServiceContext) http.HandlerFunc { for _, u := range list { out = append(out, userDTO(u)) } - ok(w, map[string]any{"list": out}) + ok(w, map[string]any{"list": out, "total": total, "page": page, "pageSize": pageSize}) }) } diff --git a/bj_power_wms/internal/handler/stock.go b/bj_power_wms/internal/handler/stock.go index bc3575c..cd1951d 100644 --- a/bj_power_wms/internal/handler/stock.go +++ b/bj_power_wms/internal/handler/stock.go @@ -3,6 +3,7 @@ package handler import ( "fmt" "net/http" + "sort" "strings" "time" @@ -24,8 +25,9 @@ type stockKey struct { } // stockAggRow 聚合主列表的行结构(按 物料+区域+质量+管理粒度 聚合,只返回数量,不铺开明细) +// ID = 组内最近入库的库存行真实主键(inventory.id)——聚合视图无自身实体,取真实主键,禁止伪 id type stockAggRow struct { - ID int64 `json:"id"` // 组合键稳定序号的伪 id,供前端行 key 使用 + ID int64 `json:"id"` // inventory.id(组内最近入库行) MaterialCode string `json:"materialCode"` // 物料编码 MaterialName string `json:"materialName"` // 物料名称 Spec string `json:"spec"` // 规格型号 @@ -49,6 +51,7 @@ func queryStockHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { materialCode := r.URL.Query().Get("materialCode") materialName := r.URL.Query().Get("materialName") // 物料名称模糊 + keyword := r.URL.Query().Get("keyword") // 模糊搜索:物料编码/批次号/SN 任一匹配(下拉拾取器用) zoneCode := r.URL.Query().Get("zoneCode") qualityStatus := r.URL.Query().Get("qualityStatus") manageMode := r.URL.Query().Get("manageMode") // 1/2/空=全部 @@ -61,6 +64,7 @@ func queryStockHandler(ctx *svc.ServiceContext) http.HandlerFunc { rows, total, err := aggregateStock(ctx, aggregateStockArgs{ MaterialCode: materialCode, MaterialName: materialName, + Keyword: keyword, ZoneCode: zoneCode, QualityStatus: qualityStatus, ManageMode: manageMode, @@ -87,6 +91,7 @@ func queryStockHandler(ctx *svc.ServiceContext) http.HandlerFunc { type aggregateStockArgs struct { MaterialCode string MaterialName string + Keyword string // 模糊搜索:物料编码/批次号/SN 任一匹配(下拉拾取器用) ZoneCode string QualityStatus string ManageMode string @@ -114,6 +119,14 @@ func aggregateStock(ctx *svc.ServiceContext, a aggregateStockArgs) ([]*stockAggR if a.MaterialName != "" { q = q.Where(inventory.MaterialNameContains(a.MaterialName)) } + if a.Keyword != "" { + // 模糊搜索:物料编码 / 批次号 / SN 任一命中(库存拾取下拉用);ContainsFold 大小写不敏感 + q = q.Where(inventory.Or( + inventory.MaterialCodeContainsFold(a.Keyword), + inventory.BatchNoContainsFold(a.Keyword), + inventory.SnCodeContainsFold(a.Keyword), + )) + } if a.ZoneCode != "" { q = q.Where(inventory.ZoneCodeEQ(a.ZoneCode)) } @@ -184,6 +197,7 @@ func aggregateStock(ctx *svc.ServiceContext, a aggregateStockArgs) ([]*stockAggR snCnt int lastInbNo string lastTime int64 + lastInvID int64 // 组内最近入库的库存行真实主键(inventory.id),作行标识 } group := map[stockKey]*agg{} order := []stockKey{} @@ -210,9 +224,11 @@ func aggregateStock(ctx *svc.ServiceContext, a aggregateStockArgs) ([]*stockAggR } g.snCnt++ } + // all 已按 created_at desc, id desc 排序,首见即最近一条 if inv.CreatedAt > g.lastTime { g.lastTime = inv.CreatedAt g.lastInbNo = inv.InboundNo + g.lastInvID = int64(inv.ID) } } @@ -235,7 +251,7 @@ func aggregateStock(ctx *svc.ServiceContext, a aggregateStockArgs) ([]*stockAggR name = key.MaterialCode } rows = append(rows, &stockAggRow{ - ID: int64(i + 1), + ID: g.lastInvID, // 组内最近入库的库存行真实主键(inventory.id),禁止伪 id MaterialCode: key.MaterialCode, MaterialName: name, Spec: matSpec[key.MaterialCode], @@ -256,23 +272,26 @@ func aggregateStock(ctx *svc.ServiceContext, a aggregateStockArgs) ([]*stockAggR // materialAggRow 物料汇总行(按 物料编码 聚合,忽略区域与质量状态) // 回答"这个物料总共有多少":总数/锁定/可用 + 质量分布(合格/未检/不合格) + 分布区域列表。 +// ID 取物料主表(material)主键——聚合视图无自身实体,主表 ID 即行标识,禁止伪 id。 type materialAggRow struct { - MaterialCode string `json:"materialCode"` - MaterialName string `json:"materialName"` - Spec string `json:"spec"` - ManageMode int `json:"manageMode"` // 1结构件/2精密件 - TotalQty int `json:"totalQty"` // 总数量(结构件=数量求和;精密件=SN 行数) - LockedQty int `json:"lockedQty"` // 锁定量 - AvailQty int `json:"availQty"` // 可用量 = total - locked - QtyQualified int `json:"qtyQualified"` // 合格数量 - QtyPending int `json:"qtyPending"` // 未检数量 - QtyRejected int `json:"qtyRejected"` // 不合格数量 - Zones []string `json:"zones"` // 分布区域列表 - ZoneCount int `json:"zoneCount"` // 分布区域数 - BatchCount int `json:"batchCount"` // 批次数(结构件维度的行数) - SnCount int `json:"snCount"` // SN 数(精密件维度的行数) - LastInboundNo string `json:"lastInboundNo"` // 最近入库单号 - CreatedAt int64 `json:"createdAt"` // 最近一次入库时间 + ID int64 `json:"id"` // 物料主表 ID(material.id,档案缺失时为 0) + MaterialCode string `json:"materialCode"` + MaterialName string `json:"materialName"` + Spec string `json:"spec"` + ManageMode int `json:"manageMode"` // 1结构件/2精密件 + TotalQty int `json:"totalQty"` // 总数量(结构件=数量求和;精密件=SN 行数) + LockedQty int `json:"lockedQty"` // 锁定量 + AvailQty int `json:"availQty"` // 可用量 = total - locked + QtyQualified int `json:"qtyQualified"` // 合格数量 + QtyPending int `json:"qtyPending"` // 未检数量 + QtyRejected int `json:"qtyRejected"` // 不合格数量 + Zones []string `json:"zones"` // 分布区域列表 + ZoneCount int `json:"zoneCount"` // 分布区域数 + BatchCount int `json:"batchCount"` // 批次数(结构件维度的行数) + SnCount int `json:"snCount"` // SN 数(精密件维度的行数) + LastInboundNo string `json:"lastInboundNo"` // 最近入库单号 + MaterialCreatedAt int64 `json:"materialCreatedAt"` // 物料主表创建时间(material.created_at) + CreatedAt int64 `json:"createdAt"` // 最近一次入库时间 } // materialSummaryArgs 物料汇总的筛选条件 @@ -328,9 +347,11 @@ func aggregateMaterialSummary(ctx *svc.ServiceContext, a materialSummaryArgs) ([ return nil, 0, err } - // 物料名称/规格补全 + // 物料名称/规格/主表ID/主表创建时间补全 matName := map[string]string{} matSpec := map[string]string{} + matID := map[string]int64{} + matCreatedAt := map[string]int64{} matCodes := []string{} seen := map[string]bool{} for _, inv := range all { @@ -345,6 +366,8 @@ func aggregateMaterialSummary(ctx *svc.ServiceContext, a materialSummaryArgs) ([ for _, m := range mats { matName[m.Code] = m.Name matSpec[m.Code] = m.Spec + matID[m.Code] = int64(m.ID) + matCreatedAt[m.Code] = m.CreatedAt } } @@ -426,22 +449,24 @@ func aggregateMaterialSummary(ctx *svc.ServiceContext, a materialSummaryArgs) ([ name = code } rows = append(rows, &materialAggRow{ - MaterialCode: code, - MaterialName: name, - Spec: matSpec[code], - ManageMode: g.mMode, - TotalQty: g.totalQty, - LockedQty: g.lockedQty, - AvailQty: g.totalQty - g.lockedQty, - QtyQualified: g.qQualified, - QtyPending: g.qPending, - QtyRejected: g.qRejected, - Zones: g.zones, - ZoneCount: len(g.zones), - BatchCount: g.batchCnt, - SnCount: g.snCnt, - LastInboundNo: g.lastInbNo, - CreatedAt: g.lastTime, + ID: matID[code], + MaterialCode: code, + MaterialName: name, + Spec: matSpec[code], + ManageMode: g.mMode, + TotalQty: g.totalQty, + LockedQty: g.lockedQty, + AvailQty: g.totalQty - g.lockedQty, + QtyQualified: g.qQualified, + QtyPending: g.qPending, + QtyRejected: g.qRejected, + Zones: g.zones, + ZoneCount: len(g.zones), + BatchCount: g.batchCnt, + SnCount: g.snCnt, + LastInboundNo: g.lastInbNo, + MaterialCreatedAt: matCreatedAt[code], + CreatedAt: g.lastTime, }) } return rows, total, nil @@ -512,6 +537,27 @@ func stockDetailsHandler(ctx *svc.ServiceContext) http.HandlerFunc { // 返回 .xlsx 二进制,文件名 = 库存汇总_时间.xlsx。 func exportStockHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + if r.URL.Query().Get("view") == "zone" { + out, err := buildZoneSummary(ctx) + if err != nil { + fail(w, http.StatusInternalServerError, err.Error()) + return + } + headers := []string{"ID", "区域编码", "区域名称", "质量状态", "类型", "数量", "区域创建时间", "最近入库时间"} + matrix := make([][]any, 0, len(out)) + for _, rw := range out { + id := any("") + if rw.ID > 0 { + id = rw.ID + } + matrix = append(matrix, []any{ + id, rw.ZoneCode, rw.ZoneName, rw.Quality, rw.Type, rw.Qty, + unixFmt(rw.ZoneCreatedAt), unixFmt(rw.LastCreatedAt), + }) + } + sendExcel(w, xlsxFilename("区域汇总"), headers, matrix) + return + } if r.URL.Query().Get("view") == "material" { rows, _, err := aggregateMaterialSummary(ctx, materialSummaryArgs{ MaterialCode: r.URL.Query().Get("materialCode"), @@ -524,15 +570,19 @@ func exportStockHandler(ctx *svc.ServiceContext) http.HandlerFunc { fail(w, http.StatusInternalServerError, err.Error()) return } - headers := []string{"物料编码", "物料名称", "规格", "类型", "总数量", "锁定量", "可用量", "合格", "未检", "不合格", "分布区域", "批次数", "SN数", "最近入库单", "最近入库时间"} + headers := []string{"ID", "物料编码", "物料名称", "规格", "类型", "总数量", "锁定量", "可用量", "合格", "未检", "不合格", "分布区域", "批次数", "SN数", "最近入库单", "物料创建时间", "最近入库时间"} matrix := make([][]any, 0, len(rows)) for _, rw := range rows { + id := any("") + if rw.ID > 0 { + id = rw.ID + } matrix = append(matrix, []any{ - rw.MaterialCode, rw.MaterialName, rw.Spec, + id, rw.MaterialCode, rw.MaterialName, rw.Spec, manageModeLabel(rw.ManageMode), rw.TotalQty, rw.LockedQty, rw.AvailQty, rw.QtyQualified, rw.QtyPending, rw.QtyRejected, strings.Join(rw.Zones, "、"), rw.BatchCount, rw.SnCount, - rw.LastInboundNo, unixFmt(rw.CreatedAt), + rw.LastInboundNo, unixFmt(rw.MaterialCreatedAt), unixFmt(rw.CreatedAt), }) } sendExcel(w, xlsxFilename("物料汇总"), headers, matrix) @@ -554,11 +604,11 @@ func exportStockHandler(ctx *svc.ServiceContext) http.HandlerFunc { fail(w, http.StatusInternalServerError, err.Error()) return } - headers := []string{"物料编码", "物料名称", "规格", "类型", "区域", "质量状态", "总数量", "锁定量", "可用量", "批次数", "SN数", "最近入库单", "最近入库时间"} + headers := []string{"ID", "物料编码", "物料名称", "规格", "类型", "区域", "质量状态", "总数量", "锁定量", "可用量", "批次数", "SN数", "最近入库单", "最近入库时间"} matrix := make([][]any, 0, len(rows)) for _, rw := range rows { matrix = append(matrix, []any{ - rw.MaterialCode, rw.MaterialName, rw.Spec, + rw.ID, rw.MaterialCode, rw.MaterialName, rw.Spec, manageModeLabel(rw.ManageMode), rw.ZoneCode, rw.QualityStatus, rw.TotalQty, rw.LockedQty, rw.AvailQty, rw.BatchCount, rw.SnCount, rw.LastInboundNo, unixFmt(rw.CreatedAt), @@ -567,54 +617,113 @@ func exportStockHandler(ctx *svc.ServiceContext) http.HandlerFunc { sendExcel(w, xlsxFilename("库存汇总"), headers, matrix) } } +// zoneSummRow 区域汇总行(按 区域 + 质量状态 + 类型 聚合) +// ID/ZoneCreatedAt 取区域主表(zone)主键与创建时间——聚合视图无自身实体,主表字段即行标识,禁止伪 id。 +type zoneSummRow struct { + ID int64 `json:"id"` // 区域主表 ID(zone.id,未分区时为 0) + ZoneCode string `json:"zoneCode"` + ZoneName string `json:"zoneName"` + Quality string `json:"quality"` + Type string `json:"type"` // 批次/精密件 + Qty int `json:"qty"` + ZoneCreatedAt int64 `json:"zoneCreatedAt"` // 区域主表创建时间(zone.created_at) + LastCreatedAt int64 `json:"lastCreatedAt"` // 组内最近一次入库时间 +} + +// buildZoneSummary 区域汇总聚合:供列表接口与导出共用。 +// 行排序固定(区域编码 → 质量 → 类型),避免 map 迭代顺序随机导致每次刷新行序跳动。 +func buildZoneSummary(ctx *svc.ServiceContext) ([]zoneSummRow, error) { + zones, err := ctx.EntClient.Zone.Query().All(ctx0()) + if err != nil { + return nil, err + } + zoneNameMap := map[string]string{} + zoneIDMap := map[string]int64{} + zoneCreatedAtMap := map[string]int64{} + for _, z := range zones { + zoneNameMap[z.ZoneCode] = z.ZoneName + zoneIDMap[z.ZoneCode] = int64(z.ID) + zoneCreatedAtMap[z.ZoneCode] = z.CreatedAt + } + + rows, err := ctx.EntClient.Inventory.Query(). + Where(inventory.StatusIn("在库", "锁定")).All(ctx0()) + if err != nil { + return nil, err + } + + type aggInfo struct { + qty int + lastCreated int64 + } + agg := map[string]*aggInfo{} // key: zc|quality|type + for _, inv := range rows { + zc := inv.ZoneCode + if zc == "" { + zc = "未分区" + } + t := "批次" + if inv.ManageMode == 2 { + t = "精密件" + } + key := zc + "|" + inv.QualityStatus + "|" + t + g, ok := agg[key] + if !ok { + g = &aggInfo{} + agg[key] = g + } + if inv.ManageMode == 1 { + g.qty += inv.Quantity + } else { + g.qty += 1 + } + if inv.CreatedAt > g.lastCreated { + g.lastCreated = inv.CreatedAt + } + } + + out := make([]zoneSummRow, 0, len(agg)) + for k, v := range agg { + parts := strings.Split(k, "|") + zc := parts[0] + name := zoneNameMap[zc] + if name == "" { + name = zc + } + out = append(out, zoneSummRow{ + ID: zoneIDMap[zc], + ZoneCode: zc, + ZoneName: name, + Quality: parts[1], + Type: parts[2], + Qty: v.qty, + ZoneCreatedAt: zoneCreatedAtMap[zc], + LastCreatedAt: v.lastCreated, + }) + } + sort.Slice(out, func(i, j int) bool { + a, b := out[i], out[j] + if a.ZoneCode != b.ZoneCode { + return a.ZoneCode < b.ZoneCode + } + if a.Quality != b.Quality { + return a.Quality < b.Quality + } + return a.Type < b.Type + }) + return out, nil +} + // zoneSummaryHandler 区域库存汇总(按 区域 + 质量状态 + 类型 聚合) // GET /api/stock/zone-summary -// 返回 rows: [{zoneCode, zoneName, quality, type(批次/精密件), qty}] +// 返回 rows: zoneSummRow(含区域主表 ID/创建时间 + 组内最近入库时间) // 仅按"区域(Z01~Z04)"维度聚合,不引入货架/层/格(业务决策:小货架高频取货无需精确货位索引)。 func zoneSummaryHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - zones, _ := ctx.EntClient.Zone.Query().All(ctx0()) - zoneNameMap := map[string]string{} - for _, z := range zones { - zoneNameMap[z.ZoneCode] = z.ZoneName - } - - rows, _ := ctx.EntClient.Inventory.Query(). - Where(inventory.StatusIn("在库", "锁定")).All(ctx0()) - - type summRow struct { - ZoneCode string `json:"zoneCode"` - ZoneName string `json:"zoneName"` - Quality string `json:"quality"` - Type string `json:"type"` // 批次/精密件 - Qty int `json:"qty"` - } - agg := map[string]int{} // key: zc|quality|type - for _, inv := range rows { - zc := inv.ZoneCode - if zc == "" { - zc = "未分区" - } - t := "批次" - if inv.ManageMode == 2 { - t = "精密件" - } - key := zc + "|" + inv.QualityStatus + "|" + t - if inv.ManageMode == 1 { - agg[key] += inv.Quantity - } else { - agg[key] += 1 - } - } - - out := make([]summRow, 0, len(agg)) - for k, v := range agg { - parts := strings.Split(k, "|") - name := zoneNameMap[parts[0]] - if name == "" { - name = parts[0] - } - out = append(out, summRow{parts[0], name, parts[1], parts[2], v}) + out, err := buildZoneSummary(ctx) + if err != nil { + fail(w, http.StatusInternalServerError, err.Error()) + return } ok(w, map[string]any{"rows": out}) } diff --git a/bj_power_wms_client/frontend/index.html b/bj_power_wms_client/frontend/index.html index cd43927..e0a5b0f 100644 --- a/bj_power_wms_client/frontend/index.html +++ b/bj_power_wms_client/frontend/index.html @@ -3,7 +3,7 @@ - 库房客户端 + WMS库房管理 diff --git a/bj_power_wms_client/frontend/src/components/MaterialSelect.vue b/bj_power_wms_client/frontend/src/components/MaterialSelect.vue new file mode 100644 index 0000000..c8388ed --- /dev/null +++ b/bj_power_wms_client/frontend/src/components/MaterialSelect.vue @@ -0,0 +1,53 @@ + + + diff --git a/bj_power_wms_client/frontend/src/help.js b/bj_power_wms_client/frontend/src/help.js index 9838556..202a964 100644 --- a/bj_power_wms_client/frontend/src/help.js +++ b/bj_power_wms_client/frontend/src/help.js @@ -1,4 +1,4 @@ -// WMS 库房客户端 页面级操作说明:整体流程 + 各字段 数据来源/用途/怎么填 +// WMS库房管理 页面级操作说明:整体流程 + 各字段 数据来源/用途/怎么填 // 由 components/PageHelp.vue 在左下角 "?" 按钮弹出展示。 // 说明:这里的"数据来源"特指该字段数据由谁产生、从哪个系统/页面来,帮助跑通真实流程。 @@ -60,7 +60,7 @@ export const helpOutbound = { export const helpInventory = { title: '库存查询', overview: - '查看当前库存:结构件按批次(数量/锁定/区域),精密件按 SN(状态/区域)。数据由入库、出库、检验、盘点等业务实时维护,本页只读。\n\n本页三个视图页签(首屏默认「物料汇总」):\n· 物料汇总:一物料一行,直接回答"这个物料总共有多少"——总量/锁定/可用 + 质量分布(合格/未检/不合格) + 分布在哪些区域;点行下钻该物料的批次/SN 明细。\n· 库存明细:按 物料 × 区域 × 质量 聚合,可组合 区域/质量状态/入库时间 筛选(物料汇总不受区域/质量筛选影响)。\n· 区域汇总:按 区域 × 质量状态 × 类型 看各库区的货量。\n\n层级概念:入库单 → 批次/SN → 库存行。区域/质量状态/批次/最近入库单都是某物料的一对多属性,放在下钻明细中查看,不在物料行上并排平铺。', + '查看当前库存:结构件按批次(数量/锁定/区域),精密件按 SN(状态/区域)。数据由入库、出库、检验、盘点等业务实时维护,本页只读。\n\n本页三个视图页签(首屏默认「物料汇总」):\n· 物料汇总:一物料一行,直接回答"这个物料总共有多少"——总量/锁定/可用 + 质量分布(合格/未检/不合格) + 分布在哪些区域;ID 与创建时间取自物料主表,另有最近入库单/最近入库时间;点行下钻该物料的批次/SN 明细。\n· 库存明细:按 物料 × 区域 × 质量 聚合,可组合 区域/质量状态/入库时间 筛选(物料汇总不受区域/质量筛选影响)。\n· 区域汇总:按 区域 × 质量状态 × 类型 看各库区的货量;ID 与创建时间取自区域主表,另有组内最近入库时间;支持全量导出。\n\n层级概念:入库单 → 批次/SN → 库存行。区域/质量状态/批次/最近入库单都是某物料的一对多属性,放在下钻明细中查看,不在物料行上并排平铺。', fields: [ { name: '物料编码 / 物料名称', source: '筛选条件。', purpose: '只看某个物料', fill: '输入编码或名称,可空=全部' }, { name: '库存类型', source: '单选(全部/结构件/精密件)。', purpose: '按管理粒度过滤', fill: '全部 / 结构件(批次) / 精密件(SN)' }, @@ -68,7 +68,8 @@ export const helpInventory = { { name: '物料汇总-分布区域', source: '系统统计该物料当前占用了哪些区域。', purpose: '一个物料可跨多个区域存放(批次可拆分),物料行不写死单一区域', fill: '如 Z02、Z05;点行下钻可看每个批次/SN 具体在哪个区' }, { name: '库存明细-区域/质量/入库时间', source: '仅「库存明细」视图生效的筛选。', purpose: '在物料基础上再按存放区/质量状态/入库时间收窄', fill: '可组合筛选' }, { name: '批次/SN 明细(下钻)', source: '点击任一行弹出抽屉。', purpose: '看该物料的每个批次/SN 所在区域、质量状态、锁定与来源入库单', fill: '抽屉内分页展示;点入库单号可跳转入库页' }, - { name: '导出', source: '按钮。后端按当前筛选全量导出 xlsx。', purpose: '把结果导出存档', fill: '物料汇总视图导出「物料汇总.xlsx」;库存明细视图导出「库存汇总.xlsx」' } + { name: 'ID / 创建时间', source: '物料汇总行取物料主表(material),区域汇总行取区域主表(zone)。', purpose: '行标识与主档建立时间,用于追溯与对账', fill: '系统自动带出,只读;物料档案/区域缺失时显示 -' }, + { name: '导出', source: '按钮。后端按当前筛选全量导出 xlsx。', purpose: '把结果导出存档', fill: '物料汇总视图导出「物料汇总.xlsx」;库存明细视图导出「库存汇总.xlsx」;区域汇总视图导出「区域汇总.xlsx」' } ] } diff --git a/bj_power_wms_client/frontend/src/layouts/MainLayout.vue b/bj_power_wms_client/frontend/src/layouts/MainLayout.vue index be64450..40e6807 100644 --- a/bj_power_wms_client/frontend/src/layouts/MainLayout.vue +++ b/bj_power_wms_client/frontend/src/layouts/MainLayout.vue @@ -167,22 +167,22 @@ function onLogout() { :class="{ 'drawer': isMobile, 'drawer-open': isMobile && mobileOpen }">
- 库房客户端 + WMS库房管理
- + {{ topMenu.title }} - + {{ m.title }} @@ -231,10 +231,9 @@ function onLogout() { .drawer-open { transform: translateX(0); } .drawer-mask { position: fixed; inset: 0; background: rgba(0, 0, 0, .45); z-index: 1000; } .brand { - display: flex; align-items: center; gap: 8px; - height: 56px; padding: 0 16px; - color: #fff; font-weight: 600; font-size: 15px; - border-bottom: 1px solid rgba(255, 255, 255, 0.08); + display: flex; align-items: center; justify-content: center; gap: 8px; + height: 60px; + color: #fff; font-weight: 600; font-size: 17px; white-space: nowrap; } .menu { border-right: none; } diff --git a/bj_power_wms_client/frontend/src/pages/Agv.vue b/bj_power_wms_client/frontend/src/pages/Agv.vue index 6a1db33..ab98b8d 100644 --- a/bj_power_wms_client/frontend/src/pages/Agv.vue +++ b/bj_power_wms_client/frontend/src/pages/Agv.vue @@ -162,6 +162,7 @@ onMounted(() => { loadDocks(); loadTasks() }) 查询 + @@ -186,6 +187,9 @@ onMounted(() => { loadDocks(); loadTasks() }) + + + diff --git a/bj_power_wms_client/frontend/src/pages/Inbound.vue b/bj_power_wms_client/frontend/src/pages/Inbound.vue index 3bc6a81..676f05c 100644 --- a/bj_power_wms_client/frontend/src/pages/Inbound.vue +++ b/bj_power_wms_client/frontend/src/pages/Inbound.vue @@ -9,6 +9,7 @@ import { fmtTime } from '../utils/format' import { exportXlsxFetch } from '../utils/export' import { can } from '../utils/perm' import PageHelp from '../components/PageHelp.vue' +import MaterialSelect from '../components/MaterialSelect.vue' import { helpInbound } from '../help' const route = useRoute() @@ -17,20 +18,14 @@ const operator = getRealName() // ===== 多 Tab:结构件入库 / 精密件 SN 入库 在前(常操作),入库记录在后 ===== const activeTab = ref('batch') // batch=结构件入库 / sn=精密件SN入库 / records=入库记录 -// 公共字典:物料 / 区域 -const materials = ref([]) +// 公共字典:区域(物料下拉由 MaterialSelect 组件按需远程搜索,最近 100 条) const zones = ref([]) async function loadDicts() { - if (materials.value.length && zones.value.length) return - const [ms, zs] = await Promise.all([request.get('/material/list'), request.get('/zone/picker')]) - materials.value = Array.isArray(ms) ? ms : ms?.list || [] + if (zones.value.length) return + const zs = await request.get('/zone/picker') zones.value = Array.isArray(zs) ? zs : zs?.list || [] } -// 按当前页过滤物料:结构件页只显示批次管理(manageMode=1),SN 页只显示序列号管理(manageMode=2) -const batchMaterials = computed(() => materials.value.filter((m) => Number(m.manageMode) === 1)) -const snMaterials = computed(() => materials.value.filter((m) => Number(m.manageMode) === 2)) - /* ===================== Tab1 结构件入库(批次,支持多批到货追加) ===================== */ const batchRef = ref() const submitting1 = ref(false) @@ -51,11 +46,6 @@ async function submitBatch() { ElMessage.warning('数量必须大于 0') return } - const mat1 = materials.value.find(m => m.code === form1.materialCode) - if (mat1 && Number(mat1.manageMode) === 2) { - ElMessage.warning('该物料为精密件(按 SN 管理),请切换到「精密件 SN 入库」页扫码录入后再提交') - return - } submitting1.value = true try { const data = await request.post('/inbound/create', { @@ -117,11 +107,6 @@ async function submitSn() { ElMessage.warning('请先扫码录入 SN(输入后点击【录入到列表】)') return } - const mat2 = materials.value.find(m => m.code === form2.materialCode) - if (mat2 && Number(mat2.manageMode) === 1) { - ElMessage.warning('该物料为结构件(按批次管理),请切换到「结构件入库」页按数量入库') - return - } submitting2.value = true try { const data = await request.post('/inbound/create', { @@ -299,10 +284,7 @@ onMounted(() => { - - - + @@ -355,10 +337,7 @@ onMounted(() => { - - - + @@ -451,6 +430,7 @@ onMounted(() => { +