From a98fdd0fcaa7f55ea34a8de1ad2124a22b1af3dc Mon Sep 17 00:00:00 2001 From: yanweidong Date: Mon, 27 Jul 2026 15:31:22 +0800 Subject: [PATCH] refactor(platform): remove deprecated reporting and audit modules --- backend/api/internal/initdb/platform.go | 3 - backend/api/internal/initdb/platform_test.go | 3 +- backend/api/internal/logic/platform/audit.go | 103 ------------- .../api/internal/logic/platform/audit_test.go | 135 ------------------ .../api/internal/logic/platform/platform.go | 6 +- .../api/internal/logic/platform/resource.go | 4 +- .../internal/logic/platform/resource_test.go | 42 +++--- .../logic/platform/task4_resources.go | 35 +---- backend/api/internal/models/audit_approval.go | 21 --- .../api/internal/models/audit_export_log.go | 19 --- .../internal/models/audit_operation_log.go | 17 --- .../models/{cnt_content.go => cms_content.go} | 8 +- backend/api/internal/models/dev_telemetry.go | 2 +- backend/api/internal/models/ec_order_item.go | 10 +- backend/api/internal/models/ntf_template.go | 14 -- backend/api/internal/models/report.go | 18 --- backend/api/internal/models/report_item.go | 15 -- .../internal/models/report_metric_snapshot.go | 19 --- backend/api/internal/models/safe_rule.go | 4 +- backend/api/internal/routers/platform.go | 14 +- backend/api/internal/routers/platform_test.go | 17 ++- docs/05-平台总后台需求-分析.md | 29 +--- docs/10-技术实现规划.md | 4 +- .../scripts/audit-check.test.mjs | 7 +- .../scripts/final-important.test.mjs | 24 ++-- .../platform_admin/src/api/resource-form.ts | 6 - frontend/platform_admin/src/api/resources.ts | 83 ++--------- .../src/router/routes/modules/audit.ts | 12 -- .../src/router/routes/modules/content.ts | 2 +- .../src/router/routes/modules/notification.ts | 10 -- .../src/router/routes/modules/platform.ts | 5 +- .../src/router/routes/modules/report.ts | 12 -- .../views/audit/audit_approval/ListPage.vue | 6 - .../views/audit/audit_export_log/ListPage.vue | 6 - .../audit/audit_operation_log/ListPage.vue | 6 - .../{cnt_content => cms_content}/ListPage.vue | 2 +- .../notification/ntf_template/ListPage.vue | 6 - .../src/views/report/report/ListPage.vue | 6 - .../src/views/report/report_item/ListPage.vue | 6 - .../report_metric_snapshot/ListPage.vue | 6 - .../src/views/shared/CrudListPage.vue | 11 +- .../src/views/shared/ReadOnlyListPage.vue | 2 +- 42 files changed, 86 insertions(+), 674 deletions(-) delete mode 100644 backend/api/internal/logic/platform/audit.go delete mode 100644 backend/api/internal/logic/platform/audit_test.go delete mode 100644 backend/api/internal/models/audit_approval.go delete mode 100644 backend/api/internal/models/audit_export_log.go delete mode 100644 backend/api/internal/models/audit_operation_log.go rename backend/api/internal/models/{cnt_content.go => cms_content.go} (83%) delete mode 100644 backend/api/internal/models/ntf_template.go delete mode 100644 backend/api/internal/models/report.go delete mode 100644 backend/api/internal/models/report_item.go delete mode 100644 backend/api/internal/models/report_metric_snapshot.go delete mode 100644 frontend/platform_admin/src/router/routes/modules/audit.ts delete mode 100644 frontend/platform_admin/src/router/routes/modules/notification.ts delete mode 100644 frontend/platform_admin/src/router/routes/modules/report.ts delete mode 100644 frontend/platform_admin/src/views/audit/audit_approval/ListPage.vue delete mode 100644 frontend/platform_admin/src/views/audit/audit_export_log/ListPage.vue delete mode 100644 frontend/platform_admin/src/views/audit/audit_operation_log/ListPage.vue rename frontend/platform_admin/src/views/content/{cnt_content => cms_content}/ListPage.vue (78%) delete mode 100644 frontend/platform_admin/src/views/notification/ntf_template/ListPage.vue delete mode 100644 frontend/platform_admin/src/views/report/report/ListPage.vue delete mode 100644 frontend/platform_admin/src/views/report/report_item/ListPage.vue delete mode 100644 frontend/platform_admin/src/views/report/report_metric_snapshot/ListPage.vue diff --git a/backend/api/internal/initdb/platform.go b/backend/api/internal/initdb/platform.go index 28c3042..99a6ea6 100644 --- a/backend/api/internal/initdb/platform.go +++ b/backend/api/internal/initdb/platform.go @@ -42,11 +42,8 @@ func InitPlatformAccess(database *gorm.DB) error { {Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "ec", Name: "电商管理", Icon: "icon-shopping", Path: "/ec/product", SortNo: 80}, {Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "finance", Name: "财务管理", Icon: "icon-safe", Path: "/finance/payment", SortNo: 90}, {Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "wallet", Name: "钱包中心", Icon: "icon-wallet", Path: "/wallet/list", SortNo: 100}, - {Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "report", Name: "统计报表", Icon: "icon-bar-chart", Path: "/report/list", SortNo: 110}, {Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "content", Name: "内容管理", Icon: "icon-file", Path: "/content", SortNo: 120}, - {Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "notification", Name: "通知管理", Icon: "icon-notification", Path: "/notification", SortNo: 130}, {Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "customer_service", Name: "客户服务", Icon: "icon-customer-service", Path: "/customer_service", SortNo: 140}, - {Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "audit", Name: "审计合规", Icon: "icon-history", Path: "/audit", SortNo: 150}, {Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "platform", Name: "平台配置", Icon: "icon-settings", Path: "/platform/account", SortNo: 160}, } for index := range menus { diff --git a/backend/api/internal/initdb/platform_test.go b/backend/api/internal/initdb/platform_test.go index 9b6ad07..5b79e7a 100644 --- a/backend/api/internal/initdb/platform_test.go +++ b/backend/api/internal/initdb/platform_test.go @@ -27,8 +27,7 @@ func TestInitPlatformAccessSeedsEveryProtectedFrontendDomain(t *testing.T) { domains := []string{ "dashboard", "gas", "delivery", "staff", "user", "device", "safety", - "ec", "finance", "wallet", "report", "content", "notification", - "customer_service", "audit", "platform", + "ec", "finance", "wallet", "content", "customer_service", "platform", } for index, domain := range domains { menuID := uint64(index + 10) diff --git a/backend/api/internal/logic/platform/audit.go b/backend/api/internal/logic/platform/audit.go deleted file mode 100644 index 1ede203..0000000 --- a/backend/api/internal/logic/platform/audit.go +++ /dev/null @@ -1,103 +0,0 @@ -package platform - -import ( - "encoding/json" - "errors" - "time" - - "git.apinb.com/bsm-sdk/core/errcode" - "git.apinb.com/bsm-sdk/core/infra" - "git.apinb.com/bsm-sdk/core/middleware" - "git.apinb.com/heqiapp/platforms/backend/api/internal/impl" - "git.apinb.com/heqiapp/platforms/backend/api/internal/models" - "github.com/gin-gonic/gin" - "gorm.io/gorm" -) - -var errApprovalNotProcessable = errors.New("approval is not processable") - -func approvalValues(status, opinion, operatorIdentity string) map[string]any { - return map[string]any{ - "status": status, - "opinion": opinion, - "handler_identity": operatorIdentity, - "handled_at": time.Now().UTC(), - } -} - -// ApproveAudit records the reviewer and decision without allowing an approval -// to mutate any business fields. The operation audit is created atomically with -// the approval update. -func ApproveAudit(ctx *gin.Context) { - claims, err := middleware.ParseAuth(ctx) - if err != nil { - infra.Response.Error(ctx, err) - return - } - var request struct { - Status string `json:"status" binding:"required,max=32"` - Opinion string `json:"opinion" binding:"max=2000"` - } - if err := ctx.ShouldBindJSON(&request); err != nil { - infra.Response.Error(ctx, errcode.ErrInvalidArgument) - return - } - if request.Status != "approved" && request.Status != "rejected" { - infra.Response.Error(ctx, errcode.ErrInvalidArgument) - return - } - - values := approvalValues(request.Status, request.Opinion, claims.Identity) - var approval models.AuditApproval - err = impl.DBService.Transaction(func(transaction *gorm.DB) error { - if err := transaction.Where("identity = ?", ctx.Param("identity")).First(&approval).Error; err != nil { - return err - } - if approval.Status != "pending" || approval.ApplicantIdentity == claims.Identity { - return errApprovalNotProcessable - } - before, err := json.Marshal(gin.H{ - "status": approval.Status, "opinion": approval.Opinion, - "handler_identity": approval.HandlerIdentity, "handled_at": approval.HandledAt, - }) - if err != nil { - return err - } - if result := transaction.Model(&models.AuditApproval{}).Where("identity = ? AND status = ?", approval.Identity, "pending").Updates(values); result.Error != nil { - return result.Error - } else if result.RowsAffected == 0 { - return errApprovalNotProcessable - } - after, err := json.Marshal(values) - if err != nil { - return err - } - return transaction.Create(&models.AuditOperationLog{ - Entity: newEntity("enabled"), - OperatorIdentity: claims.Identity, - Action: "approve", - ObjectType: "audit_approval", - ObjectIdentity: approval.Identity, - BeforeData: string(before), - AfterData: string(after), - }).Error - }) - if err != nil { - if errors.Is(err, errApprovalNotProcessable) { - infra.Response.Error(ctx, errcode.ErrInvalidArgument) - return - } - if errors.Is(err, gorm.ErrRecordNotFound) { - infra.Response.Error(ctx, errcode.ErrRecordNotFound) - return - } - infra.Response.Error(ctx, err) - return - } - approval.Status = request.Status - approval.Opinion = request.Opinion - approval.HandlerIdentity = claims.Identity - handledAt := values["handled_at"].(time.Time) - approval.HandledAt = &handledAt - infra.Response.Success(ctx, resourceResponse(approval)) -} diff --git a/backend/api/internal/logic/platform/audit_test.go b/backend/api/internal/logic/platform/audit_test.go deleted file mode 100644 index 7d473ff..0000000 --- a/backend/api/internal/logic/platform/audit_test.go +++ /dev/null @@ -1,135 +0,0 @@ -package platform - -import ( - "database/sql/driver" - "net/http" - "regexp" - "strings" - "testing" - "time" - - "git.apinb.com/bsm-sdk/core/errcode" - "git.apinb.com/bsm-sdk/core/types" - "github.com/DATA-DOG/go-sqlmock" - "google.golang.org/grpc/status" -) - -func TestApprovalValuesOnlyChangesApprovalFieldsAndRecordsOperator(t *testing.T) { - values := approvalValues("approved", "accepted", "operator-a") - - if values["status"] != "approved" || values["opinion"] != "accepted" || values["handler_identity"] != "operator-a" { - t.Fatalf("approval values do not retain the approved state, opinion, and operator: %#v", values) - } - if _, ok := values["handled_at"]; !ok { - t.Fatalf("approval values do not record handling time: %#v", values) - } - if len(values) != 4 { - t.Fatalf("approval update includes fields outside its whitelist: %#v", values) - } -} - -func TestApproveAuditOnlyUpdatesApprovalFieldsAndAppendsOperationAudit(t *testing.T) { - _, mock := setupPlatformRoleDatabase(t) - now := time.Now().UTC() - mock.ExpectBegin() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "audit_approval" WHERE identity = $1 ORDER BY "audit_approval"."id" LIMIT $2`)). - WithArgs("approval-a", 1). - WillReturnRows(sqlmock.NewRows([]string{"id", "identity", "created_at", "updated_at", "status", "version", "business_type", "business_identity", "applicant_identity", "opinion", "handler_identity", "handled_at"}). - AddRow(uint64(1), "approval-a", now, now, "pending", 1, "refund", "payment-a", "applicant-a", "", "", nil)) - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "audit_approval" SET "handled_at"=$1,"handler_identity"=$2,"opinion"=$3,"status"=$4,"updated_at"=$5 WHERE identity = $6 AND status = $7`)). - WithArgs(sqlmock.AnyArg(), "operator-a", "accepted", "approved", sqlmock.AnyArg(), "approval-a", "pending"). - WillReturnResult(sqlmock.NewResult(0, 1)) - mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "audit_operation_log" ("identity","created_at","updated_at","status","version","operator_identity","action","object_type","object_identity","before_data","after_data") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING "id"`)). - WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), "enabled", 1, "operator-a", "approve", "audit_approval", "approval-a", jsonContaining(`"status":"pending"`), jsonContaining(`"handler_identity":"operator-a"`)). - WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(uint64(2))) - mock.ExpectCommit() - - ctx, recorder := updateContext(http.MethodPost, "/audit/audit_approval/approval-a/approve", "approval-a", []byte(`{"status":"approved","opinion":"accepted","business_identity":"payment-b"}`)) - ctx.Set("Auth", &types.JwtClaims{Identity: "operator-a"}) - ApproveAudit(ctx) - - assertMockExpectations(t, mock) - assertResponseCode(t, recorder, 0) - if !strings.Contains(recorder.Body.String(), `"handler_identity":"operator-a"`) { - t.Fatalf("approval response omitted its handler: %s", recorder.Body.String()) - } -} - -func TestApproveAuditRejectsStatusesOutsideApprovedAndRejected(t *testing.T) { - for _, decision := range []string{"pending", "archived"} { - t.Run(decision, func(t *testing.T) { - _, mock := setupPlatformRoleDatabase(t) - ctx, recorder := updateContext(http.MethodPost, "/audit/audit_approval/approval-a/approve", "approval-a", []byte(`{"status":"`+decision+`"}`)) - ctx.Set("Auth", &types.JwtClaims{Identity: "operator-a"}) - - ApproveAudit(ctx) - - assertResponseCode(t, recorder, int32(status.Code(errcode.ErrInvalidArgument))) - assertMockExpectations(t, mock) - }) - } -} - -func TestApproveAuditRejectsAlreadyHandledApproval(t *testing.T) { - _, mock := setupPlatformRoleDatabase(t) - mock.ExpectBegin() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "audit_approval" WHERE identity = $1 ORDER BY "audit_approval"."id" LIMIT $2`)). - WithArgs("approval-a", 1). - WillReturnRows(approvalRows("approval-a", "approved", "applicant-a")) - mock.ExpectRollback() - - ctx, recorder := updateContext(http.MethodPost, "/audit/audit_approval/approval-a/approve", "approval-a", []byte(`{"status":"approved"}`)) - ctx.Set("Auth", &types.JwtClaims{Identity: "operator-a"}) - ApproveAudit(ctx) - - assertResponseCode(t, recorder, int32(status.Code(errcode.ErrInvalidArgument))) - assertMockExpectations(t, mock) -} - -func TestApproveAuditRejectsTheApplicant(t *testing.T) { - _, mock := setupPlatformRoleDatabase(t) - mock.ExpectBegin() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "audit_approval" WHERE identity = $1 ORDER BY "audit_approval"."id" LIMIT $2`)). - WithArgs("approval-a", 1). - WillReturnRows(approvalRows("approval-a", "pending", "operator-a")) - mock.ExpectRollback() - - ctx, recorder := updateContext(http.MethodPost, "/audit/audit_approval/approval-a/approve", "approval-a", []byte(`{"status":"approved"}`)) - ctx.Set("Auth", &types.JwtClaims{Identity: "operator-a"}) - ApproveAudit(ctx) - - assertResponseCode(t, recorder, int32(status.Code(errcode.ErrInvalidArgument))) - assertMockExpectations(t, mock) -} - -func TestApproveAuditRejectsAConcurrentSecondDecision(t *testing.T) { - _, mock := setupPlatformRoleDatabase(t) - mock.ExpectBegin() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "audit_approval" WHERE identity = $1 ORDER BY "audit_approval"."id" LIMIT $2`)). - WithArgs("approval-a", 1). - WillReturnRows(approvalRows("approval-a", "pending", "applicant-a")) - mock.ExpectExec(regexp.QuoteMeta(`UPDATE "audit_approval" SET "handled_at"=$1,"handler_identity"=$2,"opinion"=$3,"status"=$4,"updated_at"=$5 WHERE identity = $6 AND status = $7`)). - WithArgs(sqlmock.AnyArg(), "operator-a", "", "approved", sqlmock.AnyArg(), "approval-a", "pending"). - WillReturnResult(sqlmock.NewResult(0, 0)) - mock.ExpectRollback() - - ctx, recorder := updateContext(http.MethodPost, "/audit/audit_approval/approval-a/approve", "approval-a", []byte(`{"status":"approved"}`)) - ctx.Set("Auth", &types.JwtClaims{Identity: "operator-a"}) - ApproveAudit(ctx) - - assertResponseCode(t, recorder, int32(status.Code(errcode.ErrInvalidArgument))) - assertMockExpectations(t, mock) -} - -func approvalRows(identity, approvalStatus, applicant string) *sqlmock.Rows { - now := time.Now().UTC() - return sqlmock.NewRows([]string{"id", "identity", "created_at", "updated_at", "status", "version", "business_type", "business_identity", "applicant_identity", "opinion", "handler_identity", "handled_at"}). - AddRow(uint64(1), identity, now, now, approvalStatus, 1, "refund", "payment-a", applicant, "", "", nil) -} - -type jsonContaining string - -func (expected jsonContaining) Match(value driver.Value) bool { - actual, ok := value.(string) - return ok && strings.Contains(actual, string(expected)) -} diff --git a/backend/api/internal/logic/platform/platform.go b/backend/api/internal/logic/platform/platform.go index b66084e..c3a0da0 100644 --- a/backend/api/internal/logic/platform/platform.go +++ b/backend/api/internal/logic/platform/platform.go @@ -69,9 +69,7 @@ var keywordSafeColumns = map[string]bool{ "content_type": true, "publish_status": true, "template_code": true, "ticket_no": true, "category": true, "priority": true, "platform_role_code": true, "data_scope": true, "menu_code": true, - "path": true, "report_code": true, "report_type": true, - "stat_period": true, "dimension": true, "metric_code": true, - "scope_type": true, "business_type": true, "resource_type": true, + "path": true, "resource_type": true, } func applyKeywordFilter(ctx *gin.Context, query *gorm.DB, model any) *gorm.DB { @@ -100,7 +98,7 @@ func keywordColumns(model any) []string { columns := make([]string, 0) for index := 0; index < modelType.NumField(); index++ { field := modelType.Field(index) - if field.Anonymous || field.Type.Kind() != reflect.String || strings.Contains(field.Tag.Get("gorm"), "type:jsonb") { + if field.Anonymous || field.Type.Kind() != reflect.String { continue } column := gormColumn(field.Tag.Get("gorm")) diff --git a/backend/api/internal/logic/platform/resource.go b/backend/api/internal/logic/platform/resource.go index bbdbfbe..61c1fc0 100644 --- a/backend/api/internal/logic/platform/resource.go +++ b/backend/api/internal/logic/platform/resource.go @@ -79,11 +79,9 @@ func ExpectedResources() []ResourceContract { resourceContract("ec", "ec_category", Writable, "list"), resourceContract("ec", "ec_product", Writable, "list"), resourceContract("ec", "ec_product_attribute", Writable, "list"), resourceContract("ec", "ec_product_image", Writable, "list"), resourceContract("ec", "ec_cart", Writable, "list"), resourceContract("ec", "ec_order", Writable, "list"), resourceContract("ec", "ec_order_item", Writable, "list"), resourceContract("ec", "ec_review", Writable, "list"), resourceContract("delivery", "delivery_task", Writable, "list"), resourceContract("delivery", "delivery_track", Writable, "list"), resourceContract("delivery", "delivery_track_point", ReadOnly, "list"), resourceContract("finance", "fin_payment", Writable, "list"), resourceContract("finance", "fin_settlement", Writable, "list"), resourceContract("finance", "fin_reconciliation", Writable, "list"), - resourceContract("content", "cnt_content", Writable, "list"), resourceContract("notification", "ntf_template", Writable, "list"), resourceContract("customer_service", "cs_ticket", Writable, "list"), + resourceContract("content", "cms_content", Writable, "list"), resourceContract("customer_service", "cs_ticket", Writable, "list"), resourceContract("platform", "platfrom_account", Writable, "list"), resourceContract("platform", "platform_role", Writable, "list"), resourceContract("platform", "platform_menu", Writable, "tree"), resourceContract("wallet", "wallet", ReadOnly, "list"), resourceContract("wallet", "wallet_ledger", ReadOnly, "list"), resourceContract("wallet", "wallet_recharge", ReadOnly, "list"), resourceContract("wallet", "wallet_withdrawal", ReadOnly, "list"), - resourceContract("report", "report", ReadOnly, "list"), resourceContract("report", "report_item", ReadOnly, "list"), resourceContract("report", "report_metric_snapshot", ReadOnly, "list"), - resourceContract("audit", "audit_operation_log", ReadOnly, "list"), resourceContract("audit", "audit_export_log", ReadOnly, "list"), resourceContract("audit", "audit_approval", ReadOnly, "list"), } } diff --git a/backend/api/internal/logic/platform/resource_test.go b/backend/api/internal/logic/platform/resource_test.go index d775494..67a6caa 100644 --- a/backend/api/internal/logic/platform/resource_test.go +++ b/backend/api/internal/logic/platform/resource_test.go @@ -30,21 +30,27 @@ func TestExpectedResources(t *testing.T) { assertContract(t, ExpectedResources(), "delivery", "delivery_track_point", ReadOnly, "list") } -func TestSafeAndAuditResourceContracts(t *testing.T) { +func TestContentUsesCMSResourceAndRemovedDomainsAreAbsent(t *testing.T) { + resources := ExpectedResources() + assertContract(t, resources, "content", "cms_content", Writable, "list") + for _, removed := range []string{"ntf_template", "report", "report_item", "report_metric_snapshot", "audit_operation_log", "audit_export_log", "audit_approval"} { + for _, resource := range resources { + if resource.Name == removed { + t.Fatalf("removed resource %s remains registered", removed) + } + } + } +} + +func TestSafeResourceContracts(t *testing.T) { legacySafetyPrefix := "sa" + "f_" - legacyAuditPrefix := "au" + "d_" assertContract(t, ExpectedResources(), "safety", "safe_rule", Writable, "list") assertContract(t, ExpectedResources(), "safety", "safe_event", Writable, "list") assertContract(t, ExpectedResources(), "safety", "safe_inspection", Writable, "list") assertContract(t, ExpectedResources(), "safety", "safe_event_disposal", AppendOnly, "list") - assertContract(t, ExpectedResources(), "audit", "audit_operation_log", ReadOnly, "list") - assertContract(t, ExpectedResources(), "audit", "audit_export_log", ReadOnly, "list") - assertContract(t, ExpectedResources(), "audit", "audit_approval", ReadOnly, "list") - for _, contract := range ExpectedResources() { - if (contract.Domain == "safety" && strings.HasPrefix(contract.Name, legacySafetyPrefix)) || - (contract.Domain == "audit" && strings.HasPrefix(contract.Name, legacyAuditPrefix)) { + if contract.Domain == "safety" && strings.HasPrefix(contract.Name, legacySafetyPrefix) { t.Fatalf("legacy resource contract %s/%s must not be registered", contract.Domain, contract.Name) } } @@ -251,15 +257,15 @@ func TestPrepareResourceValuesResolvesRequiredIdentityRelationsAndRejectsInvalid } } -func TestPrepareResourceValuesNormalizesStringJSONBFields(t *testing.T) { +func TestPrepareResourceValuesAcceptsArbitraryTextFields(t *testing.T) { t.Run("safety rule", func(t *testing.T) { - ctx, _ := updateContext(http.MethodPost, "/safety/safe_rule", "", []byte(`{"rule_code":"pressure-limit","threshold":{"max":10},"action":"close-valve","gray_scope":["north"]}`)) + ctx, _ := updateContext(http.MethodPost, "/safety/safe_rule", "", []byte(`{"rule_code":"pressure-limit","threshold":"{invalid}","action":"close-valve","gray_scope":"north region"}`)) values, err := prepareResourceValues(ctx, &models.SafeRule{}, []string{"rule_code", "threshold", "action", "gray_scope"}, nil) if err != nil { t.Fatal(err) } - if values["threshold"] != `{"max":10}` || values["gray_scope"] != `["north"]` { - t.Fatalf("jsonb values were not normalized to strings: %#v", values) + if values["threshold"] != `{invalid}` || values["gray_scope"] != `north region` { + t.Fatalf("text values were not preserved: %#v", values) } }) @@ -272,7 +278,7 @@ func TestPrepareResourceValuesNormalizesStringJSONBFields(t *testing.T) { WithArgs("product-a", 1). WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(uint64(2))) - ctx, _ := updateContext(http.MethodPost, "/ec/ec_order_item", "", []byte(`{"ec_order_identity":"order-a","ec_product_identity":"product-a","product_snapshot":{"name":"液化气"},"quantity":2,"sale_amount":500}`)) + ctx, _ := updateContext(http.MethodPost, "/ec/ec_order_item", "", []byte(`{"ec_order_identity":"order-a","ec_product_identity":"product-a","product_snapshot":"arbitrary product snapshot text","quantity":2,"sale_amount":500}`)) values, err := prepareResourceValues(ctx, &models.EcOrderItem{}, []string{"product_snapshot", "quantity", "sale_amount"}, []ResourceRelation{ {Input: "ec_order_identity", Column: "ec_order_id", Model: &models.EcOrder{}, Required: true}, {Input: "ec_product_identity", Column: "ec_product_id", Model: &models.EcProduct{}, Required: true}, @@ -280,16 +286,16 @@ func TestPrepareResourceValuesNormalizesStringJSONBFields(t *testing.T) { if err != nil { t.Fatal(err) } - if values["product_snapshot"] != `{"name":"液化气"}` { + if values["product_snapshot"] != `arbitrary product snapshot text` { t.Fatalf("product snapshot was not normalized to a string: %#v", values) } assertMockExpectations(t, mock) }) - t.Run("invalid json string", func(t *testing.T) { + t.Run("arbitrary text", func(t *testing.T) { ctx, _ := updateContext(http.MethodPost, "/safety/safe_rule", "", []byte(`{"rule_code":"pressure-limit","threshold":"{invalid}","action":"close-valve"}`)) - if _, err := prepareResourceValues(ctx, &models.SafeRule{}, []string{"rule_code", "threshold", "action"}, nil); err == nil { - t.Fatal("invalid JSON string was accepted for a string/jsonb field") + if _, err := prepareResourceValues(ctx, &models.SafeRule{}, []string{"rule_code", "threshold", "action"}, nil); err != nil { + t.Fatalf("arbitrary text was rejected: %v", err) } }) } @@ -344,7 +350,7 @@ func TestKeywordColumnsUseSafeTextAllowlist(t *testing.T) { model any want []string }{ - {"safety rule excludes jsonb", &models.SafeRule{}, []string{"rule_code", "action"}}, + {"safety rule excludes arbitrary text fields", &models.SafeRule{}, []string{"rule_code", "action"}}, {"gas basic excludes sensitive fields", &models.GasBasic{}, []string{"code", "name"}}, {"user address has no searchable safe text", &models.UserAddress{}, []string{}}, } diff --git a/backend/api/internal/logic/platform/task4_resources.go b/backend/api/internal/logic/platform/task4_resources.go index 67c40b6..c1d4f55 100644 --- a/backend/api/internal/logic/platform/task4_resources.go +++ b/backend/api/internal/logic/platform/task4_resources.go @@ -266,7 +266,7 @@ func updateResource(ctx *gin.Context, model any, allowedFields []string, relatio return } values, err := resolveResourceRelations(input, allowedFields, relations, false) - if err != nil || normalizeStringJSONBFields(model, values) != nil { + if err != nil { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } @@ -283,42 +283,12 @@ func prepareResourceValues(ctx *gin.Context, model any, allowedFields []string, return nil, errors.New("invalid resource payload") } values, err := resolveResourceRelations(input, allowedFields, relations, true) - if err != nil || normalizeStringJSONBFields(model, values) != nil || len(values) == 0 { + if err != nil || len(values) == 0 { return nil, errors.New("invalid resource payload") } return values, nil } -func normalizeStringJSONBFields(model any, values map[string]any) error { - modelType := reflect.TypeOf(model) - for modelType.Kind() == reflect.Pointer { - modelType = modelType.Elem() - } - for index := 0; index < modelType.NumField(); index++ { - field := modelType.Field(index) - if field.Type.Kind() != reflect.String || !strings.Contains(field.Tag.Get("gorm"), "type:jsonb") { - continue - } - column := gormColumn(field.Tag.Get("gorm")) - value, exists := values[column] - if !exists { - continue - } - if text, ok := value.(string); ok { - if !json.Valid([]byte(text)) { - return errors.New("invalid jsonb string") - } - continue - } - encoded, err := json.Marshal(value) - if err != nil || !json.Valid(encoded) { - return errors.New("invalid jsonb value") - } - values[column] = string(encoded) - } - return nil -} - func resolveResourceRelations(input map[string]any, allowedFields []string, relations []ResourceRelation, requireRelations bool) (map[string]any, error) { values := filterFields(input, allowedFields) for _, relation := range relations { @@ -411,7 +381,6 @@ var relationIdentityModels = map[string]any{ "delivery_track_id": &models.DeliveryTrack{}, "platform_role_id": &models.PlatformRole{}, "platform_menu_id": &models.PlatformMenu{}, - "report_id": &models.Report{}, "wallet_id": &models.Wallet{}, } diff --git a/backend/api/internal/models/audit_approval.go b/backend/api/internal/models/audit_approval.go deleted file mode 100644 index ec5fcdf..0000000 --- a/backend/api/internal/models/audit_approval.go +++ /dev/null @@ -1,21 +0,0 @@ -package models - -import ( - "time" - - "git.apinb.com/bsm-sdk/core/database" -) - -// AuditApproval 对应 audit_approval,保存审批流与复核意见。 -type AuditApproval struct { - Entity // 公共实体字段 - BusinessType string `gorm:"column:business_type;type:varchar(64);not null" json:"business_type"` // business_type 业务字段 - BusinessIdentity string `gorm:"column:business_identity;type:varchar(36);not null;index" json:"business_identity"` // business_identity 业务字段 - ApplicantIdentity string `gorm:"column:applicant_identity;type:varchar(36);not null;index" json:"applicant_identity"` // applicant_identity 业务字段 - Opinion string `gorm:"column:opinion;type:text;not null;default:''" json:"opinion"` // opinion 业务字段 - HandlerIdentity string `gorm:"column:handler_identity;type:varchar(36);not null;default:'';index" json:"handler_identity"` // handler_identity 业务字段 - HandledAt *time.Time `gorm:"column:handled_at;type:timestamptz" json:"handled_at"` // handled_at 业务字段 -} - -func init() { database.AppendMigrate(&AuditApproval{}) } -func (table *AuditApproval) TableName() string { return "audit_approval" } diff --git a/backend/api/internal/models/audit_export_log.go b/backend/api/internal/models/audit_export_log.go deleted file mode 100644 index ff9fd86..0000000 --- a/backend/api/internal/models/audit_export_log.go +++ /dev/null @@ -1,19 +0,0 @@ -package models - -import ( - "git.apinb.com/bsm-sdk/core/database" - "time" -) - -// AuditExportLog 对应 audit_export_log,保存敏感导出审计。 -type AuditExportLog struct { - Entity // 公共实体字段 - ApplicantIdentity string `gorm:"column:applicant_identity;type:varchar(36);not null;index" json:"applicant_identity"` // applicant_identity 业务字段 - Purpose string `gorm:"column:purpose;type:varchar(255);not null" json:"purpose"` // purpose 业务字段 - FieldScope string `gorm:"column:field_scope;type:jsonb;not null;default:'{}'" json:"field_scope"` // field_scope 业务字段 - ApprovedAt *time.Time `gorm:"column:approved_at;type:timestamptz" json:"approved_at"` // approved_at 业务字段 - FileURI string `gorm:"column:file_uri;type:varchar(512);not null;default:''" json:"file_uri"` // file_uri 业务字段 -} - -func init() { database.AppendMigrate(&AuditExportLog{}) } -func (table *AuditExportLog) TableName() string { return "audit_export_log" } diff --git a/backend/api/internal/models/audit_operation_log.go b/backend/api/internal/models/audit_operation_log.go deleted file mode 100644 index 4b1b859..0000000 --- a/backend/api/internal/models/audit_operation_log.go +++ /dev/null @@ -1,17 +0,0 @@ -package models - -import "git.apinb.com/bsm-sdk/core/database" - -// AuditOperationLog 对应 audit_operation_log,保存不可变操作审计。 -type AuditOperationLog struct { - Entity // 公共实体字段 - OperatorIdentity string `gorm:"column:operator_identity;type:varchar(36);not null;index" json:"operator_identity"` // operator_identity 业务字段 - Action string `gorm:"column:action;type:varchar(64);not null" json:"action"` // action 业务字段 - ObjectType string `gorm:"column:object_type;type:varchar(64);not null" json:"object_type"` // object_type 业务字段 - ObjectIdentity string `gorm:"column:object_identity;type:varchar(36);not null;index" json:"object_identity"` // object_identity 业务字段 - BeforeData string `gorm:"column:before_data;type:jsonb;not null;default:'{}'" json:"before_data"` // before_data 业务字段 - AfterData string `gorm:"column:after_data;type:jsonb;not null;default:'{}'" json:"after_data"` // after_data 业务字段 -} - -func init() { database.AppendMigrate(&AuditOperationLog{}) } -func (table *AuditOperationLog) TableName() string { return "audit_operation_log" } diff --git a/backend/api/internal/models/cnt_content.go b/backend/api/internal/models/cms_content.go similarity index 83% rename from backend/api/internal/models/cnt_content.go rename to backend/api/internal/models/cms_content.go index b81c67e..2197101 100644 --- a/backend/api/internal/models/cnt_content.go +++ b/backend/api/internal/models/cms_content.go @@ -2,8 +2,8 @@ package models import "git.apinb.com/bsm-sdk/core/database" -// CntContent 对应 cnt_content,保存公告与协议内容。 -type CntContent struct { +// CmsContent 对应 cms_content,保存公告与协议内容。 +type CmsContent struct { Entity // 公共实体字段 ContentType string `gorm:"column:content_type;type:varchar(32);not null" json:"content_type"` // content_type 业务字段 Title string `gorm:"column:title;type:varchar(256);not null" json:"title"` // title 业务字段 @@ -12,5 +12,5 @@ type CntContent struct { PublishStatus string `gorm:"column:publish_status;type:varchar(32);not null;default:'draft'" json:"publish_status"` // publish_status 业务字段 } -func init() { database.AppendMigrate(&CntContent{}) } -func (table *CntContent) TableName() string { return "cnt_content" } +func init() { database.AppendMigrate(&CmsContent{}) } +func (table *CmsContent) TableName() string { return "cms_content" } diff --git a/backend/api/internal/models/dev_telemetry.go b/backend/api/internal/models/dev_telemetry.go index cbf2454..73a92de 100644 --- a/backend/api/internal/models/dev_telemetry.go +++ b/backend/api/internal/models/dev_telemetry.go @@ -10,7 +10,7 @@ type DevTelemetry struct { Entity // 公共实体字段 SmartCylinderValveIdentity string `gorm:"column:smart_cylinder_valve_identity;type:varchar(36);not null;index" json:"smart_cylinder_valve_identity"` // smart_cylinder_valve_identity 业务字段 ReportedAt time.Time `gorm:"column:reported_at;type:timestamptz;not null;index" json:"reported_at"` // reported_at 业务字段 - Payload string `gorm:"column:payload;type:jsonb;not null;default:'{}'" json:"payload"` // payload 业务字段 + Payload string `gorm:"column:payload;type:text;not null;default:''" json:"payload"` // payload 业务字段 QualityFlag string `gorm:"column:quality_flag;type:varchar(32);not null;default:'normal'" json:"quality_flag"` // quality_flag 业务字段 } diff --git a/backend/api/internal/models/ec_order_item.go b/backend/api/internal/models/ec_order_item.go index 88b62fb..cc715e6 100644 --- a/backend/api/internal/models/ec_order_item.go +++ b/backend/api/internal/models/ec_order_item.go @@ -5,11 +5,11 @@ import "git.apinb.com/bsm-sdk/core/database" // EcOrderItem 对应 ec_order_item,保存订单商品快照。 type EcOrderItem struct { Entity // 公共实体字段 - EcOrderID uint64 `gorm:"column:ec_order_id;not null;index" json:"ec_order_id"` // ec_order_id 业务字段 - EcProductID uint64 `gorm:"column:ec_product_id;not null;index" json:"ec_product_id"` // ec_product_id 业务字段 - ProductSnapshot string `gorm:"column:product_snapshot;type:jsonb;not null;default:'{}'" json:"product_snapshot"` // product_snapshot 业务字段 - Quantity int `gorm:"column:quantity;not null;default:1" json:"quantity"` // quantity 业务字段 - SaleAmount int64 `gorm:"column:sale_amount;not null;default:0" json:"sale_amount"` // sale_amount 业务字段 + EcOrderID uint64 `gorm:"column:ec_order_id;not null;index" json:"ec_order_id"` // ec_order_id 业务字段 + EcProductID uint64 `gorm:"column:ec_product_id;not null;index" json:"ec_product_id"` // ec_product_id 业务字段 + ProductSnapshot string `gorm:"column:product_snapshot;type:text;not null;default:''" json:"product_snapshot"` // product_snapshot 业务字段 + Quantity int `gorm:"column:quantity;not null;default:1" json:"quantity"` // quantity 业务字段 + SaleAmount int64 `gorm:"column:sale_amount;not null;default:0" json:"sale_amount"` // sale_amount 业务字段 } func init() { database.AppendMigrate(&EcOrderItem{}) } diff --git a/backend/api/internal/models/ntf_template.go b/backend/api/internal/models/ntf_template.go deleted file mode 100644 index c783d71..0000000 --- a/backend/api/internal/models/ntf_template.go +++ /dev/null @@ -1,14 +0,0 @@ -package models - -import "git.apinb.com/bsm-sdk/core/database" - -// NtfTemplate 对应 ntf_template,保存通知模板。 -type NtfTemplate struct { - Entity // 公共实体字段 - TemplateCode string `gorm:"column:template_code;type:varchar(64);not null;uniqueIndex" json:"template_code"` // template_code 业务字段 - Channel string `gorm:"column:channel;type:varchar(32);not null" json:"channel"` // channel 业务字段 - Content string `gorm:"column:content;type:text;not null" json:"content"` // content 业务字段 -} - -func init() { database.AppendMigrate(&NtfTemplate{}) } -func (table *NtfTemplate) TableName() string { return "ntf_template" } diff --git a/backend/api/internal/models/report.go b/backend/api/internal/models/report.go deleted file mode 100644 index e01d178..0000000 --- a/backend/api/internal/models/report.go +++ /dev/null @@ -1,18 +0,0 @@ -package models - -import ( - "git.apinb.com/bsm-sdk/core/database" - "time" -) - -// Report 对应 report,保存统计报表档案。 -type Report struct { - Entity // 公共实体字段 - ReportCode string `gorm:"column:report_code;type:varchar(64);not null;uniqueIndex" json:"report_code"` // report_code 业务字段 - ReportType string `gorm:"column:report_type;type:varchar(32);not null" json:"report_type"` // report_type 业务字段 - StatPeriod string `gorm:"column:stat_period;type:varchar(64);not null" json:"stat_period"` // stat_period 业务字段 - GeneratedAt time.Time `gorm:"column:generated_at;type:timestamptz;not null" json:"generated_at"` // generated_at 业务字段 -} - -func init() { database.AppendMigrate(&Report{}) } -func (table *Report) TableName() string { return "report" } diff --git a/backend/api/internal/models/report_item.go b/backend/api/internal/models/report_item.go deleted file mode 100644 index 7aaa52e..0000000 --- a/backend/api/internal/models/report_item.go +++ /dev/null @@ -1,15 +0,0 @@ -package models - -import "git.apinb.com/bsm-sdk/core/database" - -// ReportItem 对应 report_item,保存报表维度明细。 -type ReportItem struct { - Entity // 公共实体字段 - ReportID uint64 `gorm:"column:report_id;not null;index" json:"report_id"` // report_id 业务字段 - Dimension string `gorm:"column:dimension;type:varchar(128);not null" json:"dimension"` // dimension 业务字段 - MetricCode string `gorm:"column:metric_code;type:varchar(64);not null" json:"metric_code"` // metric_code 业务字段 - MetricValue string `gorm:"column:metric_value;type:varchar(128);not null" json:"metric_value"` // metric_value 业务字段 -} - -func init() { database.AppendMigrate(&ReportItem{}) } -func (table *ReportItem) TableName() string { return "report_item" } diff --git a/backend/api/internal/models/report_metric_snapshot.go b/backend/api/internal/models/report_metric_snapshot.go deleted file mode 100644 index 0871b53..0000000 --- a/backend/api/internal/models/report_metric_snapshot.go +++ /dev/null @@ -1,19 +0,0 @@ -package models - -import ( - "git.apinb.com/bsm-sdk/core/database" - "time" -) - -// ReportMetricSnapshot 对应 report_metric_snapshot,保存指标快照。 -type ReportMetricSnapshot struct { - Entity // 公共实体字段 - MetricCode string `gorm:"column:metric_code;type:varchar(64);not null;index" json:"metric_code"` // metric_code 业务字段 - ScopeType string `gorm:"column:scope_type;type:varchar(32);not null" json:"scope_type"` // scope_type 业务字段 - ScopeID uint64 `gorm:"column:scope_id;not null;default:0;index" json:"scope_id"` // scope_id 业务字段 - StatAt time.Time `gorm:"column:stat_at;type:timestamptz;not null;index" json:"stat_at"` // stat_at 业务字段 - MetricValue string `gorm:"column:metric_value;type:varchar(128);not null" json:"metric_value"` // metric_value 业务字段 -} - -func init() { database.AppendMigrate(&ReportMetricSnapshot{}) } -func (table *ReportMetricSnapshot) TableName() string { return "report_metric_snapshot" } diff --git a/backend/api/internal/models/safe_rule.go b/backend/api/internal/models/safe_rule.go index 7b45210..c9d3de4 100644 --- a/backend/api/internal/models/safe_rule.go +++ b/backend/api/internal/models/safe_rule.go @@ -7,9 +7,9 @@ type SafeRule struct { Entity // 公共实体字段 RuleCode string `gorm:"column:rule_code;type:varchar(64);not null;uniqueIndex" json:"rule_code"` // rule_code 业务字段 VersionNo int `gorm:"column:version_no;not null;default:1" json:"version_no"` // version_no 业务字段 - Threshold string `gorm:"column:threshold;type:jsonb;not null;default:'{}'" json:"threshold"` // threshold 业务字段 + Threshold string `gorm:"column:threshold;type:text;not null;default:''" json:"threshold"` // threshold 业务字段 Action string `gorm:"column:action;type:varchar(64);not null" json:"action"` // action 业务字段 - GrayScope string `gorm:"column:gray_scope;type:jsonb;not null;default:'{}'" json:"gray_scope"` // gray_scope 业务字段 + GrayScope string `gorm:"column:gray_scope;type:text;not null;default:''" json:"gray_scope"` // gray_scope 业务字段 } func init() { database.AppendMigrate(&SafeRule{}) } diff --git a/backend/api/internal/routers/platform.go b/backend/api/internal/routers/platform.go index 2ff207d..8d162a8 100644 --- a/backend/api/internal/routers/platform.go +++ b/backend/api/internal/routers/platform.go @@ -32,7 +32,6 @@ func RegisterPlatform(serviceKey string, engine *gin.Engine) { registerCommerceRoute(protected) registerFinanceRoute(protected) registerContentRoute(protected) - registerAuditRoute(protected) registerPlatformRoute(protected) } @@ -126,24 +125,13 @@ func registerFinanceRoute(group *gin.RouterGroup) { registerReadOnlyResource(group, "/wallet/wallet_ledger", &models.WalletLedger{}) registerReadOnlyResource(group, "/wallet/wallet_recharge", &models.WalletRecharge{}) registerReadOnlyResource(group, "/wallet/wallet_withdrawal", &models.WalletWithdrawal{}) - registerReadOnlyResource(group, "/report/report", &models.Report{}) - registerReadOnlyResource(group, "/report/report_item", &models.ReportItem{}) - registerReadOnlyResource(group, "/report/report_metric_snapshot", &models.ReportMetricSnapshot{}) } func registerContentRoute(group *gin.RouterGroup) { - registerRestrictedWritableResource(group, "/content/cnt_content", &models.CntContent{}, []string{"content_type", "title", "body", "version_no", "publish_status"}) - registerRestrictedWritableResource(group, "/notification/ntf_template", &models.NtfTemplate{}, []string{"template_code", "channel", "content"}) + registerRestrictedWritableResource(group, "/content/cms_content", &models.CmsContent{}, []string{"content_type", "title", "body", "version_no", "publish_status"}) registerRestrictedWritableResource(group, "/customer_service/cs_ticket", &models.CsTicket{}, []string{"ticket_no", "category", "priority"}, requiredRelation("user_account_identity", "user_account_id", &models.UserAccount{})) } -func registerAuditRoute(group *gin.RouterGroup) { - registerReadOnlyResource(group, "/audit/audit_operation_log", &models.AuditOperationLog{}) - registerReadOnlyResource(group, "/audit/audit_export_log", &models.AuditExportLog{}) - registerReadOnlyResource(group, "/audit/audit_approval", &models.AuditApproval{}) - group.POST("/audit/audit_approval/:identity/approve", platform.ApproveAudit) -} - func registerWritableResource(group *gin.RouterGroup, path string, list, create, get, update gin.HandlerFunc, model any) { resource := group.Group(path) resource.GET("", list) diff --git a/backend/api/internal/routers/platform_test.go b/backend/api/internal/routers/platform_test.go index c88157f..e8c9f4a 100644 --- a/backend/api/internal/routers/platform_test.go +++ b/backend/api/internal/routers/platform_test.go @@ -142,7 +142,7 @@ func TestPlatformDeviceSafetyCommerceAndDeliveryRoutesFollowTheirContracts(t *te assertNoRouteMethods(t, routes, "/heqi/platform/v1"+legacySafetyPrefix+"event/:identity/disposals", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete) } -func TestPlatformFinanceContentAndAuditRoutesFollowTheirContracts(t *testing.T) { +func TestPlatformFinanceContentRoutesFollowTheirContracts(t *testing.T) { engine := gin.New() RegisterPlatform("heqi", engine) @@ -156,7 +156,7 @@ func TestPlatformFinanceContentAndAuditRoutesFollowTheirContracts(t *testing.T) for _, resource := range []string{ "/finance/fin_payment", "/finance/fin_settlement", "/finance/fin_reconciliation", - "/content/cnt_content", "/notification/ntf_template", "/customer_service/cs_ticket", + "/content/cms_content", "/customer_service/cs_ticket", } { assertRouteMethods(t, routes, "/heqi/platform/v1"+resource, http.MethodGet, http.MethodPost) assertRouteMethods(t, routes, "/heqi/platform/v1"+resource+"/:identity", http.MethodGet, http.MethodPut, http.MethodDelete) @@ -165,8 +165,6 @@ func TestPlatformFinanceContentAndAuditRoutesFollowTheirContracts(t *testing.T) for _, resource := range []string{ "/wallet/wallet", "/wallet/wallet_ledger", "/wallet/wallet_recharge", "/wallet/wallet_withdrawal", - "/report/report", "/report/report_item", "/report/report_metric_snapshot", - "/audit/audit_operation_log", "/audit/audit_export_log", "/audit/audit_approval", } { path := "/heqi/platform/v1" + resource assertRouteMethods(t, routes, path, http.MethodGet) @@ -178,15 +176,16 @@ func TestPlatformFinanceContentAndAuditRoutesFollowTheirContracts(t *testing.T) } } - assertRouteMethods(t, routes, "/heqi/platform/v1/audit/audit_approval/:identity/approve", http.MethodPost) - - legacyAuditPrefix := "/audit/" + "au" + "d_" - for _, resource := range []string{legacyAuditPrefix + "operation_log", legacyAuditPrefix + "export_log", legacyAuditPrefix + "approval"} { + for _, resource := range []string{ + "/content/cnt_content", "/notification/ntf_template", + "/report/report", "/report/report_item", "/report/report_metric_snapshot", + "/audit/audit_operation_log", "/audit/audit_export_log", "/audit/audit_approval", + } { path := "/heqi/platform/v1" + resource assertNoRouteMethods(t, routes, path, http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete) assertNoRouteMethods(t, routes, path+"/:identity", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete) } - assertNoRouteMethods(t, routes, "/heqi/platform/v1"+legacyAuditPrefix+"approval/:identity/approve", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete) + assertNoRouteMethods(t, routes, "/heqi/platform/v1/audit/audit_approval/:identity/approve", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete) } func assertRouteMethods(t *testing.T, routes map[string]map[string]bool, path string, methods ...string) { diff --git a/docs/05-平台总后台需求-分析.md b/docs/05-平台总后台需求-分析.md index 37319d6..65c1c6f 100644 --- a/docs/05-平台总后台需求-分析.md +++ b/docs/05-平台总后台需求-分析.md @@ -87,7 +87,7 @@ | 配送轨迹 | `delivery_track` / `DeliveryTrack` / `delivery_track.go` | `id`、`identity`、`delivery_task_id`、`status`、`started_at`、`completed_at` | 用户可见简化配送轨迹 | | 配送轨迹点 | `delivery_track_point` / `DeliveryTrackPoint` / `delivery_track_point.go` | `id`、`identity`、`delivery_track_id`、`point_type`、`occurred_at`、`longitude`、`latitude` | 精确位置与任务节点;访问受授权控制 | -### 4.5 财务、钱包、统计报表、内容与审计 +### 4.5 财务、钱包与内容 | 实体 | 表 / 模型 / 文件 | 关键字段 | 说明 | | --- | --- | --- | --- | @@ -98,15 +98,8 @@ | 钱包流水 | `wallet_ledger` / `WalletLedger` / `wallet_ledger.go` | `id`、`identity`、`wallet_id`、`amount`、`direction`、`balance_after`、`reference_identity` | 钱包唯一资金事实流水 | | 钱包充值 | `wallet_recharge` / `WalletRecharge` / `wallet_recharge.go` | `id`、`identity`、`wallet_id`、`amount`、`channel`、`status` | 钱包充值申请和支付结果 | | 钱包提现 | `wallet_withdrawal` / `WalletWithdrawal` / `wallet_withdrawal.go` | `id`、`identity`、`wallet_id`、`amount`、`bank_account_masked`、`status` | 提现申请、审核与付款凭证 | -| 统计报表 | `report` / `Report` / `report.go` | `id`、`identity`、`report_code`、`report_type`、`stat_period`、`generated_at` | 可下载或在线查看的统计报表主档案 | -| 统计报表明细 | `report_item` / `ReportItem` / `report_item.go` | `id`、`identity`、`report_id`、`dimension`、`metric_code`、`metric_value` | 按组织、区域、商品、时间拆分的报表明细 | -| 内容 | `cnt_content` / `CntContent` / `cnt_content.go` | `content_type`、`title`、`version_no`、`publish_status` | 公告、协议、安全宣教内容 | -| 消息模板 | `ntf_template` / `NtfTemplate` / `ntf_template.go` | `template_code`、`channel`、`content`、`status` | 短信、推送、站内信模板 | +| 内容 | `cms_content` / `CmsContent` / `cms_content.go` | `content_type`、`title`、`version_no`、`publish_status` | 公告、协议、安全宣教内容 | | 客服工单 | `cs_ticket` / `CsTicket` / `cs_ticket.go` | `id`、`identity`、`ticket_no`、`user_id`、`category`、`status`、`priority` | 咨询、投诉、回访与升级 | -| 运营指标快照 | `report_metric_snapshot` / `ReportMetricSnapshot` / `report_metric_snapshot.go` | `id`、`identity`、`metric_code`、`scope_type`、`scope_id`、`stat_at`、`metric_value` | 看板与预警计算结果 | -| 操作审计 | `aud_operation_log` / `AudOperationLog` / `aud_operation_log.go` | `operator_identity`、`action`、`object_type`、`object_identity`、`before_data`、`after_data` | 不可由普通管理员改写或删除 | -| 导出审计 | `aud_export_log` / `AudExportLog` / `aud_export_log.go` | `applicant_identity`、`purpose`、`field_scope`、`approved_at`、`file_uri` | 敏感导出、下载与水印记录 | -| 审批单 | `aud_approval` / `AudApproval` / `aud_approval.go` | `business_type`、`business_identity`、`applicant_identity`、`status` | 双人复核、审批流与意见 | ## 5. API 路由规则 @@ -193,16 +186,8 @@ | `GET` | `/wallet/wallet_ledger/:identity` | 钱包流水详情查看 | | `GET` | `/wallet/wallet_recharge` | 钱包充值记录列表查询 | | `GET` | `/wallet/wallet_withdrawal` | 钱包提现记录列表查询 | -| `GET` | `/report/report` | 统计报表列表查询 | -| `GET` | `/report/report/:identity` | 统计报表详情查看 | -| `GET` | `/report/report_item` | 统计报表明细列表查询 | -| `GET` | `/report/report_item/:identity` | 统计报表明细详情查看 | -| `GET/POST/PUT/DELETE` | `/content/cnt_content` | 内容 CRUD | +| `GET/POST/PUT/DELETE` | `/content/cms_content` | 内容 CRUD | | `GET/POST/PUT/DELETE` | `/customer_service/cs_ticket` | 客服工单 CRUD | -| `GET` | `/report/report_metric_snapshot` | 运营指标看板查询 | -| `GET` | `/audit/aud_operation_log` | 操作审计查询 | -| `GET` | `/audit/aud_export_log` | 导出审计查询 | -| `POST` | `/audit/aud_approval/:identity/approve` | 审批通过或驳回 | ## 6. 前端页面规划 @@ -269,14 +254,8 @@ | 钱包中心 `WalletCards` | 钱包流水 `ListOrdered` | `/wallet/ledger` | `views/wallet/ledger` | `ListPage.vue` | `wallet_ledger` 只读列表、方向、金额和余额快照 | | 钱包中心 `WalletCards` | 充值记录 `CirclePlus` | `/wallet/recharge` | `views/wallet/recharge` | `ListPage.vue` | `wallet_recharge` 只读列表和详情 | | 钱包中心 `WalletCards` | 提现记录 `CircleMinus` | `/wallet/withdrawal` | `views/wallet/withdrawal` | `ListPage.vue` | `wallet_withdrawal` 只读列表和详情 | -| 统计报表 `ChartNoAxesCombined` | 报表列表 `FileBarChart` | `/report/list` | `views/report/list` | `ListPage.vue` | `report` 只读列表、周期、生成时间和下载入口 | -| 统计报表 `ChartNoAxesCombined` | 报表明细 `TableProperties` | `/report/item` | `views/report/item` | `ListPage.vue` | `report_item` 只读列表、维度和指标值 | -| 统计报表 `ChartNoAxesCombined` | 指标快照 `ChartLine` | `/report/metric-snapshot` | `views/report/metric-snapshot` | `DashboardPage.vue` | `report_metric_snapshot` 只读图表、同比和环比 | -| 内容客服 `MessagesSquare` | 内容管理 `FileText` | `/content/list` | `views/content/list` | `ListPage.vue` | `cnt_content` CRUD、类型、标题和发布状态 | -| 内容客服 `MessagesSquare` | 消息模板 `Send` | `/content/template` | `views/content/template` | `ListPage.vue` | `ntf_template` CRUD、渠道、模板编码和状态 | +| 内容客服 `MessagesSquare` | 内容管理 `FileText` | `/content/cms-content` | `views/content/cms_content` | `ListPage.vue` | `cms_content` CRUD、类型、标题和发布状态 | | 内容客服 `MessagesSquare` | 客服工单 `Headset` | `/content/ticket` | `views/content/ticket` | `ListPage.vue` | `cs_ticket` CRUD、客户、分类、优先级和状态 | -| 审计合规 `ScrollText` | 操作审计 `History` | `/audit/operation-log` | `views/audit/operation-log` | `ListPage.vue` | `aud_operation_log` 只读检索、对象、动作和前后值摘要 | -| 审计合规 `ScrollText` | 导出审计 `FileOutput` | `/audit/export-log` | `views/audit/export-log` | `ListPage.vue` | `aud_export_log` 只读检索、用途、字段范围和导出文件 | | 平台配置 `ShieldCheck` | 平台账户 `ContactRound` | `/platform/account` | `views/platform/account` | `ListPage.vue` | `platfrom_account` 列表、头像、角色、状态;新增/编辑抽屉 | | 平台配置 `ShieldCheck` | 角色管理 `BadgeCheck` | `/platform/role` | `views/platform/role` | `ListPage.vue` | `platform_role` CRUD,`root` 角色只读保护 | | 平台配置 `ShieldCheck` | 菜单管理 `MenuSquare` | `/platform/menu` | `views/platform/menu` | `TreePage.vue` | `platform_menu` 树、图标、路由、排序和角色菜单授权 | diff --git a/docs/10-技术实现规划.md b/docs/10-技术实现规划.md index d3e1ab2..a0b9c2d 100644 --- a/docs/10-技术实现规划.md +++ b/docs/10-技术实现规划.md @@ -141,10 +141,10 @@ platforms/ | 订单与履约 | `ord_` | `ord_order`、`ord_payment`、`ord_service_task`、`ord_delivery_track` | | 调度与配送 | `dsp_` | `dsp_assignment`、`dsp_shift`、`dsp_delivery_track_point` | | 钱包与结算 | `wal_` | `wal_wallet_ledger`、`wal_settlement`、`wal_withdrawal` | -| 内容与通知 | `cnt_`、`ntf_` | `cnt_article`、`cnt_banner`、`ntf_message`、`ntf_template` | +| 内容管理 | `cms_` | `cms_content` | | 生产与质量 | `mfg_` | `mfg_work_order`、`mfg_batch`、`mfg_quality_check` | | 开放接口 | `api_` | `api_product`、`api_client`、`api_subscription` | -| 审计与平台任务 | `aud_`、`sys_` | `aud_operation_log`、`sys_outbox_event`、`sys_dead_letter_event` | +| 平台任务 | `sys_` | `sys_outbox_event`、`sys_dead_letter_event` | - 所有主表必须包含 `identity` 字段,类型为 UUID V7,并作为该表的主键。UUID V7 由应用服务生成,保证时间有序性;禁止使用数据库自增主键、随机 UUID V4 或将业务编号作为主键。 - 引用主表时,外键字段命名为 `<实体名>_identity`,例如 `order_identity`、`service_person_identity`。业务展示编号(订单号、设备编码、站点编码等)应使用独立字段并设置唯一约束,不能替代 `identity`。 diff --git a/frontend/platform_admin/scripts/audit-check.test.mjs b/frontend/platform_admin/scripts/audit-check.test.mjs index bf666f7..710e222 100644 --- a/frontend/platform_admin/scripts/audit-check.test.mjs +++ b/frontend/platform_admin/scripts/audit-check.test.mjs @@ -7,19 +7,16 @@ const resourcesSource = readFileSync('src/api/resources.ts', 'utf8'); const legacySafetyPrefix = ['sa', 'f_'].join(''); const legacyAuditPrefix = ['au', 'd_'].join(''); -test('资源定义使用 safe 和 audit 前缀', () => { +test('资源定义使用 safe 前缀且不保留已删除审计资源', () => { assert.match(resourcesSource, /define\(\s*'safe_rule',\s*'\/safety\/safe_rule'/); assert.match(resourcesSource, /define\(\s*'safe_event',\s*'\/safety\/safe_event'/); assert.match(resourcesSource, /define\(\s*'safe_inspection',\s*'\/safety\/safe_inspection'/); assert.match(resourcesSource, /define\(\s*'safe_event_disposal',\s*'\/safety\/safe_event\/:identity\/disposals'/); - assert.match(resourcesSource, /define\(\s*'audit_operation_log',\s*'\/audit\/audit_operation_log'/); - assert.match(resourcesSource, /define\(\s*'audit_export_log',\s*'\/audit\/audit_export_log'/); - assert.match(resourcesSource, /define\(\s*'audit_approval',\s*'\/audit\/audit_approval'/); assert.doesNotMatch(resourcesSource, new RegExp(`define\\('${legacySafetyPrefix}(?:rule|event|inspection|event_disposal)',`)); assert.doesNotMatch(resourcesSource, new RegExp(`action\\('${legacySafetyPrefix}event_disposal',`)); assert.doesNotMatch(resourcesSource, new RegExp(`define\\('${legacyAuditPrefix}(?:operation_log|export_log|approval)',`)); - assert.doesNotMatch(resourcesSource, new RegExp(`/audit/${legacyAuditPrefix}approval/:identity/approve`)); + assert.doesNotMatch(resourcesSource, /audit_(?:operation_log|export_log|approval)/); }); test('只读页面将状态变更视为违规写操作', () => { diff --git a/frontend/platform_admin/scripts/final-important.test.mjs b/frontend/platform_admin/scripts/final-important.test.mjs index 2263517..16e1dd2 100644 --- a/frontend/platform_admin/scripts/final-important.test.mjs +++ b/frontend/platform_admin/scripts/final-important.test.mjs @@ -28,13 +28,13 @@ function loadResourceForm() { return resourceModule.exports; } -test('资源字段声明保留数字、布尔、时间与 JSON 类型', () => { +test('资源字段声明保留数字、布尔、时间与文本类型', () => { const resources = loadResources(); const field = (resource, key) => resources.find((item) => item.name === resource).fields.find((item) => item.key === key); assert.equal(field('ec_product', 'price_amount').type, 'number'); assert.equal(field('ec_product_image', 'is_cover').type, 'boolean'); assert.equal(field('delivery_track', 'started_at').type, 'datetime'); - assert.equal(field('dev_telemetry', 'payload').type, 'json'); + assert.equal(field('dev_telemetry', 'payload').type, 'textarea'); }); test('表单载荷构造器省略空的可选关系并转换字段类型', async () => { @@ -56,7 +56,7 @@ test('表单载荷构造器省略空的可选关系并转换字段类型', async ); }); -test('安全规则与订单明细将 JSON 字段作为有效 JSON 字符串提交', () => { +test('安全规则与订单明细的文本字段允许任意文本提交', () => { const resources = loadResources(); const { buildResourcePayload } = loadResourceForm(); const fields = (name) => resources.find((item) => item.name === name).fields; @@ -64,29 +64,29 @@ test('安全规则与订单明细将 JSON 字段作为有效 JSON 字符串提 assert.deepEqual( JSON.parse(JSON.stringify(buildResourcePayload(fields('safe_rule'), { rule_code: 'pressure-limit', - threshold: '{"max":10}', + threshold: '{invalid}', action: 'close-valve', - gray_scope: '["north"]', + gray_scope: 'north region only', }))), { rule_code: 'pressure-limit', - threshold: '{"max":10}', + threshold: '{invalid}', action: 'close-valve', - gray_scope: '["north"]', + gray_scope: 'north region only', }, ); assert.deepEqual( JSON.parse(JSON.stringify(buildResourcePayload(fields('ec_order_item'), { ec_order_identity: 'order-a', ec_product_identity: 'product-a', - product_snapshot: '{"name":"液化气"}', + product_snapshot: 'arbitrary product snapshot text', quantity: 2, sale_amount: 500, }))), { ec_order_identity: 'order-a', ec_product_identity: 'product-a', - product_snapshot: '{"name":"液化气"}', + product_snapshot: 'arbitrary product snapshot text', quantity: 2, sale_amount: 500, }, @@ -131,11 +131,11 @@ test('日期按 RFC3339 提交,密码只在创建时必填并提交', () => { ); }); -test('审批只读页提供同意和驳回操作', () => { +test('已删除的审批模块不再保留前端资源或页面', () => { const source = fs.readFileSync(fromProjectRoot('src/views/shared/ReadOnlyListPage.vue'), 'utf8'); const resources = fs.readFileSync(fromProjectRoot('src/api/resources.ts'), 'utf8'); - assert.match(resources, /audit_approval[\s\S]*\/audit\/audit_approval\/:identity\/approve/); - assert.match(source, /submitDetailAction/); + assert.doesNotMatch(resources, /audit_approval|audit_operation_log|audit_export_log/); + assert.equal(fs.existsSync(fromProjectRoot('src/views/audit')), false); }); test('树页面通过资源归档接口归档节点', () => { diff --git a/frontend/platform_admin/src/api/resource-form.ts b/frontend/platform_admin/src/api/resource-form.ts index 5e99cba..d285415 100644 --- a/frontend/platform_admin/src/api/resource-form.ts +++ b/frontend/platform_admin/src/api/resource-form.ts @@ -43,12 +43,6 @@ export function buildResourcePayload( case 'boolean': payload[field.key] = value === true || value === 'true'; break; - case 'json': - if (typeof value !== 'string') - throw new Error(`${field.label}必须是有效 JSON`); - JSON.parse(value); - payload[field.key] = value; - break; case 'date': case 'datetime': { const date = new Date(String(value)); diff --git a/frontend/platform_admin/src/api/resources.ts b/frontend/platform_admin/src/api/resources.ts index 7864b50..444f3f5 100644 --- a/frontend/platform_admin/src/api/resources.ts +++ b/frontend/platform_admin/src/api/resources.ts @@ -10,7 +10,6 @@ export type ResourceFieldType = | 'boolean' | 'date' | 'datetime' - | 'json' | 'textarea'; export type ResourceField = { key: string; @@ -175,14 +174,17 @@ const datetimeFields = new Set([ 'handled_at', 'created_at', ]); -const jsonFields = new Set([ +// 大文本字段按普通文本处理,不再要求或解析 JSON。 +const textareaFields = new Set([ + 'body', + 'content', + 'reason', + 'opinion', 'payload', 'threshold', 'gray_scope', 'product_snapshot', - 'field_scope', ]); -const textareaFields = new Set(['body', 'content', 'reason', 'opinion']); const fieldType = (key: string): ResourceFieldType => { if (key === 'platform_role_code') return 'role-code'; if (key === 'menu_identities') return 'menu-identities'; @@ -192,7 +194,6 @@ const fieldType = (key: string): ResourceFieldType => { if (booleanFields.has(key)) return 'boolean'; if (dateFields.has(key)) return 'date'; if (datetimeFields.has(key)) return 'datetime'; - if (jsonFields.has(key)) return 'json'; if (textareaFields.has(key)) return 'textarea'; return 'text'; }; @@ -237,8 +238,7 @@ const titles: Record = { fin_payment: '支付记录', fin_settlement: '财务结算', fin_reconciliation: '财务对账', - cnt_content: '内容管理', - ntf_template: '通知模板', + cms_content: '内容管理', cs_ticket: '客服工单', platfrom_account: '平台账户', platform_role: '平台角色', @@ -247,12 +247,6 @@ const titles: Record = { wallet_ledger: '钱包流水', wallet_recharge: '钱包充值', wallet_withdrawal: '钱包提现', - report: '报表', - report_item: '报表项目', - report_metric_snapshot: '指标快照', - audit_operation_log: '操作审计', - audit_export_log: '导出审计', - audit_approval: '审批审计', }; const define = ( name: string, @@ -523,18 +517,13 @@ export const resources: ResourceUiDefinition[] = [ 'list', ['channel!', 'bill_date!', 'difference_amount!'], ), - define('cnt_content', '/content/cnt_content', 'writable', 'list', [ + define('cms_content', '/content/cms_content', 'writable', 'list', [ 'content_type!', 'title!', 'body!', 'version_no', 'publish_status', ]), - define('ntf_template', '/notification/ntf_template', 'writable', 'list', [ - 'template_code!', - 'channel!', - 'content!', - ]), define('cs_ticket', '/customer_service/cs_ticket', 'writable', 'list', [ 'user_account_identity!', 'ticket_no!', @@ -592,62 +581,6 @@ export const resources: ResourceUiDefinition[] = [ 'amount', 'status', ]), - define('report', '/report/report', 'readonly', 'list', [ - 'report_code', - 'report_type', - 'stat_period', - 'generated_at', - 'status', - ]), - define('report_item', '/report/report_item', 'readonly', 'list', [ - 'report_identity', - 'dimension', - 'metric_code', - 'metric_value', - ]), - define( - 'report_metric_snapshot', - '/report/report_metric_snapshot', - 'readonly', - 'list', - ['metric_code', 'scope_type', 'stat_at', 'metric_value'], - ), - define('audit_operation_log', '/audit/audit_operation_log', 'readonly', 'list', [ - 'operator_identity', - 'action', - 'object_identity', - 'created_at', - ]), - define('audit_export_log', '/audit/audit_export_log', 'readonly', 'list', [ - 'applicant_identity', - 'purpose', - 'field_scope', - 'approved_at', - 'file_uri', - ]), - define( - 'audit_approval', - '/audit/audit_approval', - 'readonly', - 'list', - [ - 'business_type', - 'business_identity', - 'applicant_identity', - 'opinion', - 'handler_identity', - 'status', - 'handled_at', - ], - [ - action('同意', '/audit/audit_approval/:identity/approve', ['opinion'], { - status: 'approved', - }), - action('驳回', '/audit/audit_approval/:identity/approve', ['opinion!'], { - status: 'rejected', - }), - ], - ), ]; export const resourceByName = Object.fromEntries( diff --git a/frontend/platform_admin/src/router/routes/modules/audit.ts b/frontend/platform_admin/src/router/routes/modules/audit.ts deleted file mode 100644 index ca71e6f..0000000 --- a/frontend/platform_admin/src/router/routes/modules/audit.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { DEFAULT_LAYOUT } from '../base'; -import type { AppRouteRecordRaw } from '../types'; -const routes: AppRouteRecordRaw[] = [{ - path: '/audit', name: 'audit', component: DEFAULT_LAYOUT, - meta: { locale: 'menu.platform.audit', requiresAuth: true, icon: 'icon-apps', order: 22 }, - children: [ - { path: 'audit-operation-log', name: 'audit-audit-operation-log', component: () => import('@/views/audit/audit_operation_log/ListPage.vue'), meta: { locale: 'menu.platform.audit.audit_operation_log', requiresAuth: true, menuCode: 'audit' } }, - { path: 'audit-export-log', name: 'audit-audit-export-log', component: () => import('@/views/audit/audit_export_log/ListPage.vue'), meta: { locale: 'menu.platform.audit.audit_export_log', requiresAuth: true, menuCode: 'audit' } }, - { path: 'audit-approval', name: 'audit-audit-approval', component: () => import('@/views/audit/audit_approval/ListPage.vue'), meta: { locale: 'menu.platform.audit.audit_approval', requiresAuth: true, menuCode: 'audit' } } - ], -}]; -export default routes; diff --git a/frontend/platform_admin/src/router/routes/modules/content.ts b/frontend/platform_admin/src/router/routes/modules/content.ts index d002bce..8527763 100644 --- a/frontend/platform_admin/src/router/routes/modules/content.ts +++ b/frontend/platform_admin/src/router/routes/modules/content.ts @@ -4,7 +4,7 @@ const routes: AppRouteRecordRaw[] = [{ path: '/content', name: 'content', component: DEFAULT_LAYOUT, meta: { locale: 'menu.platform.content', requiresAuth: true, icon: 'icon-apps', order: 17 }, children: [ - { path: 'cnt-content', name: 'content-cnt-content', component: () => import('@/views/content/cnt_content/ListPage.vue'), meta: { locale: 'menu.platform.content.cnt_content', requiresAuth: true, menuCode: 'content' } } + { path: 'cms-content', name: 'content-cms-content', component: () => import('@/views/content/cms_content/ListPage.vue'), meta: { locale: 'menu.platform.content.cms_content', requiresAuth: true, menuCode: 'content' } } ], }]; export default routes; diff --git a/frontend/platform_admin/src/router/routes/modules/notification.ts b/frontend/platform_admin/src/router/routes/modules/notification.ts deleted file mode 100644 index 4847892..0000000 --- a/frontend/platform_admin/src/router/routes/modules/notification.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { DEFAULT_LAYOUT } from '../base'; -import type { AppRouteRecordRaw } from '../types'; -const routes: AppRouteRecordRaw[] = [{ - path: '/notification', name: 'notification', component: DEFAULT_LAYOUT, - meta: { locale: 'menu.platform.notification', requiresAuth: true, icon: 'icon-apps', order: 18 }, - children: [ - { path: 'ntf-template', name: 'notification-ntf-template', component: () => import('@/views/notification/ntf_template/ListPage.vue'), meta: { locale: 'menu.platform.notification.ntf_template', requiresAuth: true, menuCode: 'notification' } } - ], -}]; -export default routes; diff --git a/frontend/platform_admin/src/router/routes/modules/platform.ts b/frontend/platform_admin/src/router/routes/modules/platform.ts index 94b8aa7..f6d65c7 100644 --- a/frontend/platform_admin/src/router/routes/modules/platform.ts +++ b/frontend/platform_admin/src/router/routes/modules/platform.ts @@ -9,11 +9,8 @@ import safetyRoutes from './safety'; import ecRoutes from './ec'; import financeRoutes from './finance'; import contentRoutes from './content'; -import notificationRoutes from './notification'; import customerServiceRoutes from './customer_service'; import walletRoutes from './wallet'; -import reportRoutes from './report'; -import auditRoutes from './audit'; const platformRoutes: AppRouteRecordRaw[] = [ { path: '/dashboard', name: 'dashboard', component: DEFAULT_LAYOUT, meta: { locale: 'menu.dashboard', requiresAuth: true, icon: 'icon-dashboard', order: 0 }, children: [{ path: 'overview', name: 'DashboardOverview', component: () => import('@/views/dashboard/DashboardPage.vue'), meta: { locale: 'menu.platform.dashboard', requiresAuth: true, menuCode: 'dashboard' } }] }, { path: '/platform', name: 'platform', component: DEFAULT_LAYOUT, meta: { locale: 'menu.platform.config', requiresAuth: true, icon: 'icon-settings', order: 30 }, children: [ @@ -22,4 +19,4 @@ const platformRoutes: AppRouteRecordRaw[] = [ { path: 'platform-menu', name: 'platform-platform-menu', component: () => import('@/views/platform/platform_menu/TreePage.vue'), meta: { locale: 'menu.platform.platform.platform_menu', requiresAuth: true, menuCode: 'platform' } }, ] }, ]; -export default [...platformRoutes, ...gasRoutes, ...deliveryRoutes, ...staffRoutes, ...userRoutes, ...deviceRoutes, ...safetyRoutes, ...ecRoutes, ...financeRoutes, ...contentRoutes, ...notificationRoutes, ...customerServiceRoutes, ...walletRoutes, ...reportRoutes, ...auditRoutes]; +export default [...platformRoutes, ...gasRoutes, ...deliveryRoutes, ...staffRoutes, ...userRoutes, ...deviceRoutes, ...safetyRoutes, ...ecRoutes, ...financeRoutes, ...contentRoutes, ...customerServiceRoutes, ...walletRoutes]; diff --git a/frontend/platform_admin/src/router/routes/modules/report.ts b/frontend/platform_admin/src/router/routes/modules/report.ts deleted file mode 100644 index 86d46c5..0000000 --- a/frontend/platform_admin/src/router/routes/modules/report.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { DEFAULT_LAYOUT } from '../base'; -import type { AppRouteRecordRaw } from '../types'; -const routes: AppRouteRecordRaw[] = [{ - path: '/report', name: 'report', component: DEFAULT_LAYOUT, - meta: { locale: 'menu.platform.report', requiresAuth: true, icon: 'icon-apps', order: 21 }, - children: [ - { path: 'report', name: 'report-report', component: () => import('@/views/report/report/ListPage.vue'), meta: { locale: 'menu.platform.report.report', requiresAuth: true, menuCode: 'report' } }, - { path: 'report-item', name: 'report-report-item', component: () => import('@/views/report/report_item/ListPage.vue'), meta: { locale: 'menu.platform.report.report_item', requiresAuth: true, menuCode: 'report' } }, - { path: 'report-metric-snapshot', name: 'report-report-metric-snapshot', component: () => import('@/views/report/report_metric_snapshot/ListPage.vue'), meta: { locale: 'menu.platform.report.report_metric_snapshot', requiresAuth: true, menuCode: 'report' } } - ], -}]; -export default routes; diff --git a/frontend/platform_admin/src/views/audit/audit_approval/ListPage.vue b/frontend/platform_admin/src/views/audit/audit_approval/ListPage.vue deleted file mode 100644 index c62bef9..0000000 --- a/frontend/platform_admin/src/views/audit/audit_approval/ListPage.vue +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/frontend/platform_admin/src/views/audit/audit_export_log/ListPage.vue b/frontend/platform_admin/src/views/audit/audit_export_log/ListPage.vue deleted file mode 100644 index 271dfdf..0000000 --- a/frontend/platform_admin/src/views/audit/audit_export_log/ListPage.vue +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/frontend/platform_admin/src/views/audit/audit_operation_log/ListPage.vue b/frontend/platform_admin/src/views/audit/audit_operation_log/ListPage.vue deleted file mode 100644 index 68f0bde..0000000 --- a/frontend/platform_admin/src/views/audit/audit_operation_log/ListPage.vue +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/frontend/platform_admin/src/views/content/cnt_content/ListPage.vue b/frontend/platform_admin/src/views/content/cms_content/ListPage.vue similarity index 78% rename from frontend/platform_admin/src/views/content/cnt_content/ListPage.vue rename to frontend/platform_admin/src/views/content/cms_content/ListPage.vue index 253592c..19a149b 100644 --- a/frontend/platform_admin/src/views/content/cnt_content/ListPage.vue +++ b/frontend/platform_admin/src/views/content/cms_content/ListPage.vue @@ -2,5 +2,5 @@ diff --git a/frontend/platform_admin/src/views/notification/ntf_template/ListPage.vue b/frontend/platform_admin/src/views/notification/ntf_template/ListPage.vue deleted file mode 100644 index e63f989..0000000 --- a/frontend/platform_admin/src/views/notification/ntf_template/ListPage.vue +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/frontend/platform_admin/src/views/report/report/ListPage.vue b/frontend/platform_admin/src/views/report/report/ListPage.vue deleted file mode 100644 index 7070cb1..0000000 --- a/frontend/platform_admin/src/views/report/report/ListPage.vue +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/frontend/platform_admin/src/views/report/report_item/ListPage.vue b/frontend/platform_admin/src/views/report/report_item/ListPage.vue deleted file mode 100644 index 9142729..0000000 --- a/frontend/platform_admin/src/views/report/report_item/ListPage.vue +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/frontend/platform_admin/src/views/report/report_metric_snapshot/ListPage.vue b/frontend/platform_admin/src/views/report/report_metric_snapshot/ListPage.vue deleted file mode 100644 index 90768f1..0000000 --- a/frontend/platform_admin/src/views/report/report_metric_snapshot/ListPage.vue +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/frontend/platform_admin/src/views/shared/CrudListPage.vue b/frontend/platform_admin/src/views/shared/CrudListPage.vue index c0da12c..ae57ed4 100644 --- a/frontend/platform_admin/src/views/shared/CrudListPage.vue +++ b/frontend/platform_admin/src/views/shared/CrudListPage.vue @@ -37,7 +37,7 @@ - + {{ role.name }} @@ -61,7 +61,7 @@ - + {{ menu.name }} @@ -124,12 +124,7 @@ const detailEntries = computed(() => function resetForm(data?: Row) { for (const field of props.definition.fields) { const value = data?.[field.key]; - form[field.key] = - field.type === 'json' && value != null && typeof value !== 'string' - ? JSON.stringify(value, null, 2) - : value == null - ? undefined - : value; + form[field.key] = value == null ? undefined : value; } } diff --git a/frontend/platform_admin/src/views/shared/ReadOnlyListPage.vue b/frontend/platform_admin/src/views/shared/ReadOnlyListPage.vue index 1207ae1..d53da4b 100644 --- a/frontend/platform_admin/src/views/shared/ReadOnlyListPage.vue +++ b/frontend/platform_admin/src/views/shared/ReadOnlyListPage.vue @@ -23,7 +23,7 @@ - +