From 2942d9a71c33adabbcd9414475336501c5ae060b Mon Sep 17 00:00:00 2001 From: czl231 <3286836406@qq.com> Date: Sun, 16 Aug 2026 00:47:35 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=95=86=E5=9F=8E=E8=AE=A2?= =?UTF-8?q?=E5=8D=95=E5=88=97=E8=A1=A8=E5=B1=95=E7=A4=BA=E4=B8=8E=E6=A8=A1?= =?UTF-8?q?=E6=8B=9F=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/cmd/cli/main.go | 30 ++++++- backend/api/internal/seed/mock.go | 87 ++++++++++++++++++- backend/api/internal/seed/mock_test.go | 29 +++++++ .../操作日志_商城订单列表展示修复_20260816.md | 56 ++++++++++++ docs/项目文档_平台资源中文展示统一_v1.0.md | 1 + .../check-resource-display-contracts.mjs | 35 ++++++++ frontend/platform_admin/src/api/resources.ts | 7 +- 7 files changed, 238 insertions(+), 7 deletions(-) create mode 100644 docs/操作日志_商城订单列表展示修复_20260816.md diff --git a/backend/api/cmd/cli/main.go b/backend/api/cmd/cli/main.go index e16523b..9f00537 100644 --- a/backend/api/cmd/cli/main.go +++ b/backend/api/cmd/cli/main.go @@ -64,6 +64,13 @@ func main() { os.Exit(1) } fmt.Printf("mock gasorder statuses repaired: %d\n", count) + case "repair-mock-ec-order": + count, err := repairMockEcOrder() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + fmt.Printf("mock ec orders repaired: %d\n", count) case "repair-mock-track-point": count, err := repairMockTrackPoint() if err != nil { @@ -85,7 +92,7 @@ func main() { } func printUsage() { - fmt.Fprintln(os.Stderr, "usage: platform-cli ") + fmt.Fprintln(os.Stderr, "usage: platform-cli ") } type route struct { @@ -216,6 +223,27 @@ func repairMockGasorderStatus() (int64, error) { return count, nil } +// repairMockEcOrder 使用独立幂等命令修复商城 Mock 订单,不补写其他模拟资源。 +func repairMockEcOrder() (int64, error) { + config.New(serviceKey) + if config.Spec.Databases == nil { + return 0, fmt.Errorf("database configuration is required") + } + databaseService, err := database.NewDatabase( + config.Spec.Databases.Driver, + config.Spec.Databases.Source, + dbsql.SetOptions(nil), + ) + if err != nil { + return 0, fmt.Errorf("connect database: %w", err) + } + count, err := seed.RepairMockEcOrders(databaseService) + if err != nil { + return 0, fmt.Errorf("repair mock ec orders: %w", err) + } + return count, nil +} + // repairMockTrackPoint 使用独立幂等命令补全固定演示轨迹点,不触发其他模拟数据写入。 func repairMockTrackPoint() (int64, error) { config.New(serviceKey) diff --git a/backend/api/internal/seed/mock.go b/backend/api/internal/seed/mock.go index 7075a1c..2c909e8 100644 --- a/backend/api/internal/seed/mock.go +++ b/backend/api/internal/seed/mock.go @@ -311,13 +311,17 @@ func MockData(database *gorm.DB) error { } ecOrder := models.EcOrder{ - Entity: entity(30, common.StatusEnable), OrderStatus: common.StatusPaid, OrderNo: "MOCK-ECORDER-001", + Entity: entity(30, common.StatusEnable), OrderStatus: mockEcOrderPaidStatus(), OrderNo: "MOCK-ECORDER-001", UserAccountID: user.ID, GasStationID: gas.ID, DeliveryPointID: delivery.ID, - TotalAmount: ecProduct.PriceAmount, + ProductAmount: ecProduct.PriceAmount, PayableAmount: ecProduct.PriceAmount, + TotalAmount: ecProduct.PriceAmount, PaidAt: &now, } if err := put(tx, &ecOrder); err != nil { return err } + if err := repairMockEcOrder(tx, ecOrder, mockEcOrderPaidStatus(), ecProduct.PriceAmount, &now); err != nil { + return err + } ecOrderItem := models.EcOrderItem{ Entity: entity(31, common.StatusEnable), EcOrderID: ecOrder.ID, EcProductID: ecProduct.ID, @@ -723,13 +727,18 @@ func seedAdditionalCoreScenarios(database *gorm.DB, passwordHash string, now tim } ecOrder := models.EcOrder{ - Entity: entity(sequence+16, common.StatusEnable), OrderStatus: common.StatusPending, + Entity: entity(sequence+16, common.StatusEnable), OrderStatus: mockEcOrderPendingStatus(), OrderNo: "MOCK-ECORDER-" + suffix, UserAccountID: user.ID, - GasStationID: gas.ID, DeliveryPointID: delivery.ID, TotalAmount: ecProduct.PriceAmount, + GasStationID: gas.ID, DeliveryPointID: delivery.ID, + ProductAmount: ecProduct.PriceAmount, PayableAmount: ecProduct.PriceAmount, + TotalAmount: ecProduct.PriceAmount, } if err := put(database, &ecOrder); err != nil { return err } + if err := repairMockEcOrder(database, ecOrder, mockEcOrderPendingStatus(), ecProduct.PriceAmount, nil); err != nil { + return err + } wallet := models.WalletBasic{ Entity: entity(sequence+17, common.StatusEnable), OwnerType: "user", OwnerID: user.ID, @@ -767,6 +776,76 @@ func mockGasorderInitialStatus() int { return common.StatusCreated } +// Mock 商城订单使用商城交易状态,不复用其他领域含义相近的通用状态。 +func mockEcOrderPendingStatus() int { return 16 } +func mockEcOrderPaidStatus() int { return 18 } + +// repairMockEcOrder 幂等修复固定 Mock 商城订单的状态、金额与支付时间。 +func repairMockEcOrder(database *gorm.DB, order models.EcOrder, status int, amount int64, paidAt *time.Time) error { + result := database.Model(&models.EcOrder{}). + Where("identity = ? AND order_no LIKE ?", order.Identity, "MOCK-ECORDER-%"). + Updates(map[string]any{ + "order_status": status, "product_amount": amount, "payable_amount": amount, + "total_amount": amount, "paid_at": paidAt, + }) + if result.Error != nil { + return fmt.Errorf("repair mock ec order: %w", result.Error) + } + return nil +} + +type mockEcOrderRepairSpec struct { + identity string + orderNo string + status int + amount int64 + paidAt *time.Time +} + +// mockEcOrderRepairSpecs 返回固定 Mock 商城订单的文档化交易状态与金额。 +func mockEcOrderRepairSpecs() []mockEcOrderRepairSpec { + paidAt := time.Date(2026, time.July, 1, 9, 0, 0, 0, time.Local) + specs := []mockEcOrderRepairSpec{{ + identity: entity(30, common.StatusEnable).Identity, + orderNo: "MOCK-ECORDER-001", + status: mockEcOrderPaidStatus(), + amount: 10300, + paidAt: &paidAt, + }} + for scenario := 2; scenario <= 10; scenario++ { + sequence := 1000 + scenario*100 + specs = append(specs, mockEcOrderRepairSpec{ + identity: entity(sequence+16, common.StatusEnable).Identity, + orderNo: fmt.Sprintf("MOCK-ECORDER-%03d", scenario), + status: mockEcOrderPendingStatus(), + amount: int64(9500 + scenario*100), + }) + } + return specs +} + +// RepairMockEcOrders 仅修复固定 Mock 商城订单,不触发其他模拟资源写入。 +func RepairMockEcOrders(database *gorm.DB) (int64, error) { + var repaired int64 + err := database.Transaction(func(tx *gorm.DB) error { + for _, spec := range mockEcOrderRepairSpecs() { + result := tx.Model(&models.EcOrder{}). + Where("identity = ? AND order_no = ?", spec.identity, spec.orderNo). + Updates(map[string]any{ + "order_status": spec.status, "product_amount": spec.amount, + "payable_amount": spec.amount, "total_amount": spec.amount, + "paid_at": spec.paidAt, + }) + if result.Error != nil { + return fmt.Errorf("repair mock ec order %s: %w", spec.orderNo, result.Error) + } + repaired += result.RowsAffected + } + return nil + }) + return repaired, err +} + // repairMockGasorderInitialStatus 幂等修复历史种子数据中误写为“待处理”的 Mock 配送订单。 func repairMockGasorderInitialStatus(database *gorm.DB, order models.GasorderBasic) error { result := database.Model(&models.GasorderBasic{}). diff --git a/backend/api/internal/seed/mock_test.go b/backend/api/internal/seed/mock_test.go index 81fe6b1..4c0b6a2 100644 --- a/backend/api/internal/seed/mock_test.go +++ b/backend/api/internal/seed/mock_test.go @@ -94,6 +94,35 @@ func TestMockGasorderInitialStatusMatchesWorkflow(t *testing.T) { } } +// TestMockEcOrderStatusesMatchWorkflow 验证 Mock 商城订单遵循商城独立交易状态。 +func TestMockEcOrderStatusesMatchWorkflow(t *testing.T) { + if got := mockEcOrderPendingStatus(); got != 16 { + t.Fatalf("Mock 商城待支付状态 = %d, want 16", got) + } + if got := mockEcOrderPaidStatus(); got != 18 { + t.Fatalf("Mock 商城已支付状态 = %d, want 18", got) + } + if mockEcOrderPendingStatus() == common.StatusPending || mockEcOrderPaidStatus() == common.StatusPaid { + t.Fatal("Mock 商城订单不得复用跨领域通用状态") + } +} + +// TestMockEcOrderRepairSpecs 验证修复命令只覆盖固定十条商城 Mock 订单。 +func TestMockEcOrderRepairSpecs(t *testing.T) { + specs := mockEcOrderRepairSpecs() + if len(specs) != 10 { + t.Fatalf("Mock 商城订单修复数量 = %d, want 10", len(specs)) + } + if specs[0].orderNo != "MOCK-ECORDER-001" || specs[0].status != 18 || specs[0].paidAt == nil { + t.Fatalf("首条已支付 Mock 订单修复配置错误:%#v", specs[0]) + } + for _, spec := range specs[1:] { + if spec.status != 16 || spec.paidAt != nil || spec.amount <= 0 { + t.Fatalf("待支付 Mock 订单修复配置错误:%#v", spec) + } + } +} + // TestMockTrackPointSampleRequiredFields 验证演示轨迹点符合真实上报的必填字段和可读展示要求。 func TestMockTrackPointSampleRequiredFields(t *testing.T) { if mockTrackPointRequestNo == "" { diff --git a/docs/操作日志_商城订单列表展示修复_20260816.md b/docs/操作日志_商城订单列表展示修复_20260816.md new file mode 100644 index 0000000..0b086df --- /dev/null +++ b/docs/操作日志_商城订单列表展示修复_20260816.md @@ -0,0 +1,56 @@ +# 商城订单列表展示修复操作日志 + +操作时间:2026-08-16 + +操作类型:修改 + +影响模块:平台总后台电商平台管理、商城订单只读列表 + +## 操作前状态 + +- 商城订单用户关系按普通唯一标识字段展示,管理员只能看到 UUID。 +- Mock 数据误用跨领域通用状态 `10`、`35`,与商城正式实现的独立交易状态不一致。 +- 已支付 Mock 订单缺少支付时间,部分 Mock 订单的商品金额和应付金额为零。 + +## 具体操作 + +1. 将商城订单用户列改为名称优先展示,并保留 UUID 复制与详情跳转。 +2. 保持商城独立交易状态:`16=待支付`、`18=已支付`、`22=已取消`。 +3. 将 Mock 待支付订单从 `10` 修复为 `16`,已支付订单从 `35` 修复为 `18`。 +4. 补齐 Mock 订单商品金额、应付金额及已支付订单的支付时间。 +5. 待支付订单的空支付时间明确显示为“未支付”,不再显示无语义短横线。 +6. 增加独立幂等修复命令,只更新固定十条商城 Mock 订单。 +7. 增加前端展示契约与后端种子测试,防止跨领域状态再次混入商城订单。 + +## 行为变化 + +- 修改前:用户 UUID 作为主要信息;Mock 订单状态、金额和支付时间与商城流程不一致。 +- 修改后:用户名称作为主要信息,待支付记录明确显示“未支付”;重新执行 Mock 数据命令时会幂等修复既有 Mock 订单。 +- 商城订单仍为只读资源,正式业务接口、权限和非 Mock 订单数据均未改变。 + +## 代码变更 + +- `frontend/platform_admin/src/api/resources.ts`:更新商城订单用户展示契约。 +- `frontend/platform_admin/scripts/check-resource-display-contracts.mjs`:增加商城订单回归检查。 +- `backend/api/internal/seed/mock.go`:修正并幂等修复 Mock 商城订单状态、金额和支付时间。 +- `backend/api/internal/seed/mock_test.go`:增加商城独立状态回归测试。 +- `backend/api/cmd/cli/main.go`:增加 `repair-mock-ec-order` 独立修复命令。 +- `docs/项目文档_平台资源中文展示统一_v1.0.md`:记录 v1.4 变更。 + +## 验证结果 + +- `pnpm resource-display-contracts:check`:通过。 +- `pnpm type:check`:通过。 +- `pnpm build`:通过,Vite 成功完成 2621 个模块的生产构建。 +- `go test ./internal/seed ./cmd/cli`:通过。 +- `go test ./...`:通过。 +- `go vet ./...`:通过。 +- `go build ./cmd/main/main.go`:通过。 +- `go run ./cmd/cli repair-mock-ec-order`:通过,固定十条商城 Mock 订单已全部修复。 +- 完整 `mock-data` 命令曾因既有 `mock_producer_010` 用户名冲突失败,事务已整体回滚;随后使用独立修复命令成功处理商城订单,未修改冲突的生产商数据。 + +## 风险评估 + +- 仅固定 `MOCK-ECORDER-*` 与 Mock identity 范围的数据会被种子逻辑修复。 +- 关系加载失败时继续显示“名称加载失败”,UUID 可用于排障。 +- 既有重复生产商 Mock 账号仍需单独排查,不属于本次商城订单展示修复范围。 diff --git a/docs/项目文档_平台资源中文展示统一_v1.0.md b/docs/项目文档_平台资源中文展示统一_v1.0.md index 945ddbd..8b2cfa6 100644 --- a/docs/项目文档_平台资源中文展示统一_v1.0.md +++ b/docs/项目文档_平台资源中文展示统一_v1.0.md @@ -65,3 +65,4 @@ frontend/platform_admin/ - v1.2:树形资源统一改为树形表格,明确展示名称、排序、状态、系统标识和操作,唯一标识降级为复制入口。 - v1.2.1:补齐 Arco 表格 `columns` 插槽,修复树形表格容器存在但列和数据不渲染的问题。 - v1.3:购物车列表的用户与商品关系列统一展示可读名称,UUID 降级为复制和排障信息。 +- v1.4:商城订单列表统一展示用户名称,未支付时间明确显示“未支付”;Mock 订单改用商城独立交易状态,并补齐金额与支付时间。 diff --git a/frontend/platform_admin/scripts/check-resource-display-contracts.mjs b/frontend/platform_admin/scripts/check-resource-display-contracts.mjs index f9e5ebd..baa9c00 100644 --- a/frontend/platform_admin/scripts/check-resource-display-contracts.mjs +++ b/frontend/platform_admin/scripts/check-resource-display-contracts.mjs @@ -102,6 +102,41 @@ for (const [key, label] of [ } } +// 商城订单必须遵循独立交易状态,并以名称展示用户关系。 +const orderDefinition = resources.match( + /define\('ec_order',[\s\S]*?\n\s*\]\),/, +)?.[0]; +if (!orderDefinition) throw new Error('无法读取商城订单资源定义'); +for (const [value, label] of [ + [16, '待支付'], + [18, '已支付'], + [22, '已取消'], +]) { + if (!orderDefinition.includes(`{ label: '${label}', value: ${value} }`)) { + throw new Error(`商城订单状态 ${value} 缺少中文展示:${label}`); + } +} +const orderUserField = orderDefinition.match( + /relation\('user_account_identity'[\s\S]*?\}\)/, +)?.[0]; +if ( + !orderUserField || + !orderUserField.includes("listLabel: '用户'") || + !orderUserField.includes('listRelationNameOnly: true') || + !orderUserField.includes('displayRelationLabel: true') || + !orderUserField.includes('showIdentityCopy: true') +) { + throw new Error('商城订单用户列必须以用户名称展示并保留唯一标识复制入口'); +} +for (const invalidStatus of [10, 35]) { + if (orderDefinition.includes(`value: ${invalidStatus}`)) { + throw new Error(`商城订单不得把跨领域通用状态 ${invalidStatus} 当作有效交易状态`); + } +} +if (!orderDefinition.includes("f('paid_at', { emptyText: '未支付' })")) { + throw new Error('商城订单空支付时间必须明确显示为未支付'); +} + if (/title="ID"|data-index="id"/.test(listPage)) { throw new Error('标准资源列表不得把数据库ID作为业务列展示'); } diff --git a/frontend/platform_admin/src/api/resources.ts b/frontend/platform_admin/src/api/resources.ts index 2f934e3..c2e9f7f 100644 --- a/frontend/platform_admin/src/api/resources.ts +++ b/frontend/platform_admin/src/api/resources.ts @@ -711,10 +711,13 @@ export const resources: ResourceUiDefinition[] = [ { label: '已支付', value: 18 }, { label: '已取消', value: 22 }, ], unknownValueLabel: '未知订单状态' }), - relation('user_account_identity', '/user_account'), relation('gas_basic_identity', '/gas_basic'), + relation('user_account_identity', '/user_account', false, { + label: '用户', listLabel: '用户', listRelationNameOnly: true, + displayRelationLabel: true, showIdentityCopy: true, + }), relation('gas_basic_identity', '/gas_basic'), relation('delivery_basic_identity', '/delivery_basic'), relation('user_address_identity', '/user_address'), f('contact_name'), f('contact_phone'), f('product_amount'), f('discount_amount'), f('payable_amount'), - f('total_amount'), f('paid_at'), f('logistics_no'), f('logistics_company'), f('logistics_status'), + f('total_amount'), f('paid_at', { emptyText: '未支付' }), f('logistics_no'), f('logistics_company'), f('logistics_status'), f('shipped_at'), f('received_at'), f('remark'), ]), define('ec_order_item', '商城订单明细', 'readonly', [