From 2252a9c2a01b0b79a046e169d027b32ff327f843 Mon Sep 17 00:00:00 2001 From: czl231 <3286836406@qq.com> Date: Thu, 13 Aug 2026 02:25:42 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BA=93=E6=88=BF=E5=9C=B0?= =?UTF-8?q?=E5=9D=80=E5=8F=97=E6=8E=A7=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为平台库房管理列表和详情增加专用响应处理,在保留全局敏感字段保护的前提下恢复库房自身地址。未填写地址时显示‘未填写’,创建和编辑继续复用原有白名单写入逻辑,不修改数据库及历史数据。补充回归测试、操作日志和项目文档。 --- .../logic/platform/product/warehouse.go | 71 +++++++++++++++++++ .../logic/platform/product/warehouse_test.go | 49 +++++++++++++ backend/api/internal/routers/platform.go | 4 +- docs/操作日志_库房地址受控展示_20260813.md | 43 +++++++++++ docs/项目文档_库房地址受控展示_v1.0.md | 39 ++++++++++ frontend/platform_admin/src/api/resources.ts | 2 +- 6 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 backend/api/internal/logic/platform/product/warehouse.go create mode 100644 backend/api/internal/logic/platform/product/warehouse_test.go create mode 100644 docs/操作日志_库房地址受控展示_20260813.md create mode 100644 docs/项目文档_库房地址受控展示_v1.0.md diff --git a/backend/api/internal/logic/platform/product/warehouse.go b/backend/api/internal/logic/platform/product/warehouse.go new file mode 100644 index 0000000..ee5dd05 --- /dev/null +++ b/backend/api/internal/logic/platform/product/warehouse.go @@ -0,0 +1,71 @@ +// 功能描述:提供平台总后台库房列表与详情,并受控恢复库房自身地址。 +// 版本:v1.0 +package product + +import ( + "git.apinb.com/bsm-sdk/core/infra" + "git.apinb.com/heqiapp/platforms/backend/api/internal/impl" + "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common" + "git.apinb.com/heqiapp/platforms/backend/api/internal/models" + "github.com/gin-gonic/gin" +) + +// ListProductWarehouse 查询库房分页列表,并向已鉴权的平台管理端恢复库房地址。 +func ListProductWarehouse(ctx *gin.Context) { + page, size := common.PageSize(ctx) + var list []models.ProductWarehouse + var total int64 + query := common.ApplyKeywordFilter(ctx, common.ActiveRecords(impl.DBService.Model(&models.ProductWarehouse{})), &models.ProductWarehouse{}) + if err := query.Count(&total).Error; err != nil { + infra.Response.Error(ctx, err) + return + } + if err := query.Order("created_at desc").Offset((page - 1) * size).Limit(size).Find(&list).Error; err != nil { + infra.Response.Error(ctx, err) + return + } + response, err := common.PublicResourceResponse(list) + if err != nil { + infra.Response.Error(ctx, err) + return + } + // 先执行全局敏感字段保护,再仅为库房管理资源恢复库房自身地址。 + protected := common.ProtectPreciseLocation(ctx, &models.ProductWarehouse{}, response) + infra.Response.Success(ctx, gin.H{"total": total, "list": restoreProductWarehouseAddresses(protected, list)}) +} + +// GetProductWarehouse 查询单个库房,并返回详情与编辑表单所需的库房地址。 +func GetProductWarehouse(ctx *gin.Context) { + var warehouse models.ProductWarehouse + if err := common.ActiveRecords(impl.DBService).Where("identity = ?", ctx.Param("identity")).First(&warehouse).Error; err != nil { + common.RespondRecordError(ctx, err) + return + } + response, err := common.PublicResourceResponse(warehouse) + if err != nil { + infra.Response.Error(ctx, err) + return + } + protected := common.ProtectPreciseLocation(ctx, &models.ProductWarehouse{}, response) + infra.Response.Success(ctx, restoreProductWarehouseAddresses(protected, []models.ProductWarehouse{warehouse})) +} + +// restoreProductWarehouseAddresses 将已鉴权库房记录的地址恢复到安全响应中。 +func restoreProductWarehouseAddresses(response any, warehouses []models.ProductWarehouse) any { + switch data := response.(type) { + case []any: + for index, item := range data { + if index >= len(warehouses) { + break + } + if record, ok := item.(map[string]any); ok { + record["address"] = warehouses[index].Address + } + } + case map[string]any: + if len(warehouses) > 0 { + data["address"] = warehouses[0].Address + } + } + return response +} diff --git a/backend/api/internal/logic/platform/product/warehouse_test.go b/backend/api/internal/logic/platform/product/warehouse_test.go new file mode 100644 index 0000000..dd30025 --- /dev/null +++ b/backend/api/internal/logic/platform/product/warehouse_test.go @@ -0,0 +1,49 @@ +// 功能描述:验证库房管理响应仅恢复库房自身地址。 +// 版本:v1.0 +package product + +import ( + "testing" + + "git.apinb.com/heqiapp/platforms/backend/api/internal/models" +) + +// TestRestoreProductWarehouseAddresses 验证库房列表按原顺序恢复地址。 +func TestRestoreProductWarehouseAddresses(t *testing.T) { + response := []any{ + map[string]any{"identity": "warehouse-1"}, + map[string]any{"identity": "warehouse-2"}, + } + warehouses := []models.ProductWarehouse{ + {Address: "海口市龙华区库房路 1 号"}, + {Address: "三亚市吉阳区库房路 2 号"}, + } + + restored := restoreProductWarehouseAddresses(response, warehouses).([]any) + for index, warehouse := range warehouses { + if restored[index].(map[string]any)["address"] != warehouse.Address { + t.Fatalf("第 %d 条库房地址未正确恢复:%#v", index+1, restored[index]) + } + } +} + +// TestRestoreProductWarehouseAddress 验证库房详情恢复编辑所需地址。 +func TestRestoreProductWarehouseAddress(t *testing.T) { + response := map[string]any{"identity": "warehouse-1"} + warehouse := models.ProductWarehouse{Address: "海口市秀英区库房路 18 号"} + + restored := restoreProductWarehouseAddresses(response, []models.ProductWarehouse{warehouse}).(map[string]any) + if restored["address"] != warehouse.Address { + t.Fatalf("库房详情地址未正确恢复:%#v", restored) + } +} + +// TestRestoreProductWarehouseEmptyAddress 验证未填写地址时保留空值。 +func TestRestoreProductWarehouseEmptyAddress(t *testing.T) { + response := map[string]any{"identity": "warehouse-1"} + + restored := restoreProductWarehouseAddresses(response, []models.ProductWarehouse{{}}).(map[string]any) + if restored["address"] != "" { + t.Fatalf("未填写库房地址应保留空值:%#v", restored) + } +} diff --git a/backend/api/internal/routers/platform.go b/backend/api/internal/routers/platform.go index 5342f70..16a703d 100644 --- a/backend/api/internal/routers/platform.go +++ b/backend/api/internal/routers/platform.go @@ -119,7 +119,9 @@ func registerProductRoute(group *gin.RouterGroup) { producer.DELETE("/:identity", product.DeleteProducerAccount) registerRestrictedNoDeleteResource(group, "/product_type", &models.ProductType{}, []string{"code", "name"}) - registerRestrictedNoDeleteResource(group, "/product_warehouse", &models.ProductWarehouse{}, []string{"code", "name", "address", "manager", "phone"}) + warehouseFields := []string{"code", "name", "address", "manager", "phone"} + _, warehouseCreate, _, warehouseUpdate := common.ResourceHandlers(&models.ProductWarehouse{}, warehouseFields, warehouseFields) + registerNoDeleteResource(group, "/product_warehouse", product.ListProductWarehouse, warehouseCreate, product.GetProductWarehouse, warehouseUpdate, &models.ProductWarehouse{}) infoRelations := []common.ResourceRelation{ requiredRelation("producer_account_identity", "producer_account_id", &models.ProducerAccount{}), diff --git a/docs/操作日志_库房地址受控展示_20260813.md b/docs/操作日志_库房地址受控展示_20260813.md new file mode 100644 index 0000000..779471b --- /dev/null +++ b/docs/操作日志_库房地址受控展示_20260813.md @@ -0,0 +1,43 @@ +# 库房地址受控展示操作日志 + +操作时间:2026-08-13 +操作类型:修改 +影响模块:平台总后台库房管理、平台管理 API + +## 操作前状态 + +库房地址已保存于 `product_warehouse.address`,新建与编辑接口也允许写入地址,但通用敏感字段保护会删除列表和详情响应中的 `address`,导致页面统一显示 `-`。 + +## 具体操作 + +1. 为库房管理增加专用列表与详情处理器。 +2. 保留全局敏感字段保护,并在保护后仅恢复当前库房资源自身的地址。 +3. 创建与编辑继续复用原有通用白名单写入逻辑。 +4. 未填写地址时在页面显示“未填写”。 +5. 新增列表、详情和空地址回归测试。 + +## 操作后状态 + +- 库房列表、详情和编辑页面可取得已保存地址。 +- 未填写地址时显示“未填写”。 +- 其他资源和公共接口继续执行原有地址保护。 +- 数据库结构与历史数据均未修改。 + +## 代码变更 + +- `backend/api/internal/logic/platform/product/warehouse.go`:新增库房地址受控列表、详情和恢复逻辑。 +- `backend/api/internal/logic/platform/product/warehouse_test.go`:新增地址恢复测试。 +- `backend/api/internal/routers/platform.go`:库房 GET 路由改用专用处理器,POST/PUT 保持通用处理器。 +- `frontend/platform_admin/src/api/resources.ts`:配置库房地址空值文案。 + +## 验证结果 + +- `go test ./internal/logic/platform/product ./internal/routers`:通过。 +- `npm.cmd run resource-pages:check`:通过,详情 46 类、新建 25 类、编辑 23 类。 +- `npm.cmd run build`:通过,TypeScript 检查与 Vite 生产构建成功。 +- 本地后端重新编译并重启,健康接口返回 `platform-api`、`ok`。 +- `git diff --check`:通过。 + +## 风险评估 + +完整库房地址仅在已鉴权的平台库房管理接口中恢复。全局敏感字段规则未放宽,数据库与其他终端不受影响。 diff --git a/docs/项目文档_库房地址受控展示_v1.0.md b/docs/项目文档_库房地址受控展示_v1.0.md new file mode 100644 index 0000000..46ade2e --- /dev/null +++ b/docs/项目文档_库房地址受控展示_v1.0.md @@ -0,0 +1,39 @@ +# 项目文档:库房地址受控展示 v1.0 + +## 1. 项目概述 + +本次修改解决平台总后台库房地址已保存但无法显示的问题。库房继续使用自身的 `product_warehouse.address` 字段,地址保持可选,不修改数据库结构或历史数据。 + +## 2. 目录结构说明 + +```text +platforms/ +├── backend/api/internal/logic/platform/product/ +│ ├── warehouse.go # 库房列表、详情及地址受控恢复 +│ └── warehouse_test.go # 库房地址响应测试 +├── backend/api/internal/routers/ +│ └── platform.go # 平台库房管理路由 +├── frontend/platform_admin/src/api/ +│ └── resources.ts # 库房地址空值展示配置 +└── docs/ + ├── 项目文档_库房地址受控展示_v1.0.md + └── 操作日志_库房地址受控展示_20260813.md +``` + +## 3. 核心文件说明 + +- `warehouse.go`:列表与详情先执行通用响应投影和敏感字段保护,再按当前查询记录恢复库房自身地址。 +- `warehouse_test.go`:覆盖列表顺序、单条详情及空地址场景。 +- `platform.go`:GET 使用库房专用处理器;创建、更新和状态修改继续沿用通用资源能力。 +- `resources.ts`:空地址显示“未填写”,编辑输入提示为“请输入地址”。 + +## 4. 变更记录 + +- 修复库房列表地址显示 `-`。 +- 支持详情和编辑页面读取已保存库房地址。 +- 保持地址可选,并为未填写状态提供明确文案。 +- 未新增依赖、数据库迁移或历史数据修改。 + +## 5. 维护指南 + +新增需要受控展示地址的管理资源时,应采用资源专用恢复逻辑,不得从全局敏感字段集合中移除 `address`。修改后至少运行平台产品逻辑测试、路由测试和平台前端构建。 diff --git a/frontend/platform_admin/src/api/resources.ts b/frontend/platform_admin/src/api/resources.ts index 517082b..309df60 100644 --- a/frontend/platform_admin/src/api/resources.ts +++ b/frontend/platform_admin/src/api/resources.ts @@ -446,7 +446,7 @@ export const resources: ResourceUiDefinition[] = [ define('producer_account', '生产商管理', 'writable', [f('producer_code', { required: true }), f('name', { required: true }), f('credit_code'), f('principal'), f('phone'), f('address', { emptyText: '未填写', placeholder: '请输入地址' }), f('username', { required: true }), f('password', { required: true }), f('display_name'), producerAdminRole(), f('remark')]), define('product_type', '智能气阀类型', 'editable', [f('code', { required: true }), f('name', { required: true })]), - define('product_warehouse', '智能气阀库房', 'editable', [f('code', { required: true }), f('name', { required: true }), f('address'), f('manager'), f('phone')]), + define('product_warehouse', '智能气阀库房', 'editable', [f('code', { required: true }), f('name', { required: true }), f('address', { emptyText: '未填写', placeholder: '请输入地址' }), f('manager'), f('phone')]), define('product_info', '智能气阀', 'editable', [f('code', { required: true }), f('name', { required: true }), relation('producer_account_identity', '/producer_account', true), relation('product_type_identity', '/product_type', true), f('params', { required: true }), relation('warehouse_identity', '/product_warehouse'), relation('gas_basic_identity', '/gas_basic'), relation('delivery_basic_identity', '/delivery_basic'), relation('user_account_identity', '/user_account'), f('produced_at', { required: true })], 'list', [ { name: '修改智能气阀状态', resource: '/product_info/:identity/lifecycle', method: 'PATCH', fields: [f('product_status', { required: true, type: 'select', options: [{ label: '待处理', value: 10 }, { label: '在库', value: 28 }, { label: '运输中', value: 29 }, { label: '使用中', value: 30 }, { label: '维修中', value: 31 }, { label: '已报废', value: 27 }] })] }, { name: '变更智能气阀归属', resource: '/product_info/:identity', method: 'PUT', fields: [relation('warehouse_identity', '/product_warehouse'), relation('gas_basic_identity', '/gas_basic'), relation('delivery_basic_identity', '/delivery_basic'), relation('user_account_identity', '/user_account'), f('action', { required: true, type: 'select', options: [{ label: '入库', value: 'warehouse' }, { label: '分配', value: 'assigned' }, { label: '归还', value: 'returned' }, { label: '人工调整', value: 'manual' }] }), f('reason', { required: true }), f('remark')] },