refactor(platform): remove safety management module
This commit is contained in:
@@ -38,7 +38,6 @@ func InitPlatformAccess(database *gorm.DB) error {
|
||||
{Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "staff", Name: "服务人员", Icon: "icon-user", Path: "/staff/list", SortNo: 40},
|
||||
{Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "user", Name: "业主客户", Icon: "icon-user-group", Path: "/user/list", SortNo: 50},
|
||||
{Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "device", Name: "设备管理", Icon: "icon-storage", Path: "/device", SortNo: 60},
|
||||
{Entity: models.Entity{Identity: models.NewIdentity(), Status: "enabled", Version: 1}, MenuCode: "safety", Name: "安全治理", Icon: "icon-safe", Path: "/safety", SortNo: 70},
|
||||
{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},
|
||||
|
||||
@@ -26,7 +26,7 @@ func TestInitPlatformAccessSeedsEveryProtectedFrontendDomain(t *testing.T) {
|
||||
AddRow(uint64(1), "root-role", "enabled", 1, "root", "Root", "global", true))
|
||||
|
||||
domains := []string{
|
||||
"dashboard", "gas", "delivery", "staff", "user", "device", "safety",
|
||||
"dashboard", "gas", "delivery", "staff", "user", "device",
|
||||
"ec", "finance", "wallet", "content", "customer_service", "platform",
|
||||
}
|
||||
for index, domain := range domains {
|
||||
|
||||
@@ -15,7 +15,6 @@ func TestDashboardOverviewReturnsZeroValuesForAnEmptyDatabase(t *testing.T) {
|
||||
`SELECT count\(\*\) FROM "delivery_basic" WHERE status = \$1`,
|
||||
`SELECT count\(\*\) FROM "staff_account" WHERE work_status = \$1`,
|
||||
`SELECT count\(\*\) FROM "user_account" WHERE status = \$1`,
|
||||
`SELECT count\(\*\) FROM "safe_event" WHERE status = \$1`,
|
||||
} {
|
||||
mock.ExpectQuery(regexp.MustCompile(query).String()).WithArgs(sqlmock.AnyArg()).WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
|
||||
}
|
||||
|
||||
@@ -75,7 +75,6 @@ func ExpectedResources() []ResourceContract {
|
||||
resourceContract("staff", "staff_account", Writable, "list"), resourceContract("staff", "staff_credential", Writable, "list"),
|
||||
resourceContract("user", "user_account", Writable, "list"), resourceContract("user", "user_address", Writable, "list"), resourceContract("user", "user_service_relation", Writable, "list"),
|
||||
resourceContract("device", "dev_smart_cylinder_valve", Writable, "list"), resourceContract("device", "dev_device_binding", Writable, "list"), resourceContract("device", "dev_telemetry", ReadOnly, "list"),
|
||||
resourceContract("safety", "safe_rule", Writable, "list"), resourceContract("safety", "safe_event", Writable, "list"), resourceContract("safety", "safe_inspection", Writable, "list"), resourceContract("safety", "safe_event_disposal", AppendOnly, "list"),
|
||||
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"),
|
||||
@@ -101,8 +100,6 @@ func resourcePath(domain, name string) string {
|
||||
return "/user/address"
|
||||
case "user_service_relation":
|
||||
return "/user/service_relation"
|
||||
case "safe_event_disposal":
|
||||
return "/safety/safe_event/:identity/disposals"
|
||||
default:
|
||||
return "/" + domain + "/" + name
|
||||
}
|
||||
|
||||
@@ -42,16 +42,13 @@ func TestContentUsesCMSResourceAndRemovedDomainsAreAbsent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSafeResourceContracts(t *testing.T) {
|
||||
legacySafetyPrefix := "sa" + "f_"
|
||||
|
||||
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")
|
||||
func TestSafetyResourcesAreAbsent(t *testing.T) {
|
||||
removed := map[string]struct{}{
|
||||
"safe_rule": {}, "safe_event": {}, "safe_inspection": {}, "safe_event_disposal": {},
|
||||
}
|
||||
for _, contract := range ExpectedResources() {
|
||||
if contract.Domain == "safety" && strings.HasPrefix(contract.Name, legacySafetyPrefix) {
|
||||
t.Fatalf("legacy resource contract %s/%s must not be registered", contract.Domain, contract.Name)
|
||||
if _, exists := removed[contract.Name]; exists || contract.Domain == "safety" {
|
||||
t.Fatalf("removed safety resource %s/%s remains registered", contract.Domain, contract.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -258,17 +255,6 @@ func TestPrepareResourceValuesResolvesRequiredIdentityRelationsAndRejectsInvalid
|
||||
}
|
||||
|
||||
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":"{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"] != `{invalid}` || values["gray_scope"] != `north region` {
|
||||
t.Fatalf("text values were not preserved: %#v", values)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("order item", func(t *testing.T) {
|
||||
_, mock := setupPlatformRoleDatabase(t)
|
||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT "id" FROM "ec_order" WHERE identity = $1 ORDER BY "ec_order"."id" LIMIT $2`)).
|
||||
@@ -292,12 +278,6 @@ func TestPrepareResourceValuesAcceptsArbitraryTextFields(t *testing.T) {
|
||||
assertMockExpectations(t, mock)
|
||||
})
|
||||
|
||||
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.Fatalf("arbitrary text was rejected: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateGasAccountResolvesGasBasicIdentityBeforePersisting(t *testing.T) {
|
||||
@@ -350,7 +330,6 @@ func TestKeywordColumnsUseSafeTextAllowlist(t *testing.T) {
|
||||
model any
|
||||
want []string
|
||||
}{
|
||||
{"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{}},
|
||||
}
|
||||
@@ -655,26 +634,6 @@ func TestNonRootCannotAssignPlatformAccountRole(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestListSafetyEventDisposalsReturnsOnlyTheRequestedEventHistory(t *testing.T) {
|
||||
_, mock := setupPlatformRoleDatabase(t)
|
||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "safe_event_disposal" WHERE safe_event_identity = $1`)).
|
||||
WithArgs("event-a").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "safe_event_disposal" WHERE safe_event_identity = $1 ORDER BY created_at asc LIMIT $2`)).
|
||||
WithArgs("event-a", 20).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "identity", "created_at", "updated_at", "status", "version", "safe_event_identity", "action", "reason", "operator_identity"}).
|
||||
AddRow(uint64(9), "disposal-a", nil, nil, "enabled", 1, "event-a", "close", "resolved", "operator-a"))
|
||||
|
||||
ctx, recorder := updateContext(http.MethodGet, "/safety/safe_event/event-a/disposals", "event-a", nil)
|
||||
ListSafetyEventDisposals(ctx)
|
||||
|
||||
assertResponseCode(t, recorder, 0)
|
||||
if !strings.Contains(recorder.Body.String(), `"safe_event_identity":"event-a"`) || strings.Contains(recorder.Body.String(), `"id":`) {
|
||||
t.Fatalf("disposal history did not keep the event identity-only shape: %s", recorder.Body.String())
|
||||
}
|
||||
assertMockExpectations(t, mock)
|
||||
}
|
||||
|
||||
func TestListGasAccountProjectsGasBasicIdentityAndNeverReturnsRelationID(t *testing.T) {
|
||||
_, mock := setupPlatformRoleDatabase(t)
|
||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "gas_account"`)).
|
||||
@@ -820,33 +779,6 @@ func TestGetDeliveryTrackPointReturnsCoordinatesWithPreciseLocationScope(t *test
|
||||
assertMockExpectations(t, mock)
|
||||
}
|
||||
|
||||
func TestDisposeSafetyEventUpdatesEventAndAppendsOperatorActionTransactionally(t *testing.T) {
|
||||
_, mock := setupPlatformRoleDatabase(t)
|
||||
now := time.Now().UTC()
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "safe_event" WHERE identity = $1 ORDER BY "safe_event"."id" LIMIT $2`)).
|
||||
WithArgs("event-a", 1).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "identity", "created_at", "updated_at", "status", "version", "event_code", "level", "title", "smart_cylinder_valve_identity", "sla_at"}).
|
||||
AddRow(uint64(3), "event-a", now, now, "open", 1, "E-1", 2, "alarm", "valve-a", nil))
|
||||
mock.ExpectExec(regexp.QuoteMeta(`UPDATE "safe_event" SET "status"=$1,"updated_at"=$2 WHERE identity = $3`)).
|
||||
WithArgs("disposed", sqlmock.AnyArg(), "event-a").
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "safe_event_disposal" ("identity","created_at","updated_at","status","version","safe_event_identity","action","reason","operator_identity") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) RETURNING "id"`)).
|
||||
WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), "enabled", 1, "event-a", "close", "resolved", "operator-a").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(uint64(1)))
|
||||
mock.ExpectCommit()
|
||||
|
||||
ctx, recorder := updateContext(http.MethodPost, "/safety/safe_event/event-a/disposals", "event-a", []byte(`{"action":"close","reason":"resolved"}`))
|
||||
ctx.Set("Auth", &types.JwtClaims{Identity: "operator-a"})
|
||||
DisposeSafetyEvent(ctx)
|
||||
|
||||
assertResponseCode(t, recorder, 0)
|
||||
if !strings.Contains(recorder.Body.String(), `"operator_identity":"operator-a"`) {
|
||||
t.Fatalf("disposal omitted its operator: %s", recorder.Body.String())
|
||||
}
|
||||
assertMockExpectations(t, mock)
|
||||
}
|
||||
|
||||
func TestReplacePlatformRoleMenusAllowsAnEmptySetToClearAssignmentsTransactionally(t *testing.T) {
|
||||
_, mock := setupPlatformRoleDatabase(t)
|
||||
mock.ExpectBegin()
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
// ResourceRelation accepts a stable external identity while retaining the
|
||||
@@ -530,73 +529,6 @@ func stripInternalIDs(value any) any {
|
||||
return value
|
||||
}
|
||||
|
||||
// DisposeSafetyEvent atomically updates an event and appends its operator-owned
|
||||
// action record. Disposal records deliberately have no update or delete route.
|
||||
func ListSafetyEventDisposals(ctx *gin.Context) {
|
||||
page, size := pageSize(ctx)
|
||||
var list []models.SafeEventDisposal
|
||||
query := impl.DBService.Model(&models.SafeEventDisposal{}).Where("safe_event_identity = ?", ctx.Param("identity"))
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
if err := query.Order("created_at asc").Offset((page - 1) * size).Limit(size).Find(&list).Error; err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, gin.H{"total": total, "list": resourceResponse(list)})
|
||||
}
|
||||
|
||||
func DisposeSafetyEvent(ctx *gin.Context) {
|
||||
claims, err := middleware.ParseAuth(ctx)
|
||||
if err != nil {
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
var request struct {
|
||||
Action string `json:"action" binding:"required,max=64"`
|
||||
Reason string `json:"reason" binding:"max=2000"`
|
||||
Status string `json:"status" binding:"max=32"`
|
||||
}
|
||||
if err := ctx.ShouldBindJSON(&request); err != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
if request.Status == "" {
|
||||
request.Status = "disposed"
|
||||
}
|
||||
var disposal models.SafeEventDisposal
|
||||
err = impl.DBService.Transaction(func(transaction *gorm.DB) error {
|
||||
var event models.SafeEvent
|
||||
if err := transaction.Where("identity = ?", ctx.Param("identity")).First(&event).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if result := transaction.Model(&models.SafeEvent{}).Where("identity = ?", event.Identity).Update("status", request.Status); result.Error != nil {
|
||||
return result.Error
|
||||
} else if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
disposal = models.SafeEventDisposal{
|
||||
Entity: newEntity("enabled"),
|
||||
SafeEventIdentity: event.Identity,
|
||||
Action: request.Action,
|
||||
Reason: request.Reason,
|
||||
OperatorIdentity: claims.Identity,
|
||||
}
|
||||
return transaction.Create(&disposal).Error
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
infra.Response.Error(ctx, errcode.ErrRecordNotFound)
|
||||
return
|
||||
}
|
||||
infra.Response.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
infra.Response.Success(ctx, resourceResponse(disposal))
|
||||
}
|
||||
|
||||
// GetEcOrder returns the order together with its immutable item snapshots.
|
||||
func GetEcOrder(ctx *gin.Context) {
|
||||
var order models.EcOrder
|
||||
|
||||
@@ -8,7 +8,6 @@ type DashboardOverview struct {
|
||||
DeliveryBasicCount int64 `json:"delivery_basic_count"` // 启用配送点数量
|
||||
StaffCount int64 `json:"staff_count"` // 在岗服务人员数量
|
||||
UserCount int64 `json:"user_count"` // 启用业主客户数量
|
||||
PendingSafetyCount int64 `json:"pending_safety_count"` // 待处理安全事件数量
|
||||
}
|
||||
|
||||
// GetDashboardOverview 通过独立查询返回首页概览指标。
|
||||
@@ -26,9 +25,6 @@ func GetDashboardOverview() (DashboardOverview, error) {
|
||||
if err := impl.DBService.Model(&UserAccount{}).Where("status = ?", "enabled").Count(&overview.UserCount).Error; err != nil {
|
||||
return DashboardOverview{}, err
|
||||
}
|
||||
if err := impl.DBService.Model(&SafeEvent{}).Where("status = ?", "pending").Count(&overview.PendingSafetyCount).Error; err != nil {
|
||||
return DashboardOverview{}, err
|
||||
}
|
||||
return overview, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"git.apinb.com/bsm-sdk/core/database"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SafeEvent 对应 safe_event,保存安全事件统一入口。
|
||||
type SafeEvent struct {
|
||||
Entity // 公共实体字段
|
||||
EventCode string `gorm:"column:event_code;type:varchar(64);not null;uniqueIndex" json:"event_code"` // event_code 业务字段
|
||||
Level int `gorm:"column:level;not null;default:3" json:"level"` // level 业务字段
|
||||
Title string `gorm:"column:title;type:varchar(256);not null;default:''" json:"title"` // title 业务字段
|
||||
SmartCylinderValveIdentity string `gorm:"column:smart_cylinder_valve_identity;type:varchar(36);not null;default:'';index" json:"smart_cylinder_valve_identity"` // smart_cylinder_valve_identity 业务字段
|
||||
SLAAt *time.Time `gorm:"column:sla_at;type:timestamptz" json:"sla_at"` // sla_at 业务字段
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&SafeEvent{}) }
|
||||
func (table *SafeEvent) TableName() string { return "safe_event" }
|
||||
@@ -1,15 +0,0 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// SafeEventDisposal 对应 safe_event_disposal,保存安全处置记录。
|
||||
type SafeEventDisposal struct {
|
||||
Entity // 公共实体字段
|
||||
SafeEventIdentity string `gorm:"column:safe_event_identity;type:varchar(36);not null;index" json:"safe_event_identity"` // safe_event_identity 业务字段
|
||||
Action string `gorm:"column:action;type:varchar(64);not null" json:"action"` // action 业务字段
|
||||
Reason string `gorm:"column:reason;type:text;not null;default:''" json:"reason"` // reason 业务字段
|
||||
OperatorIdentity string `gorm:"column:operator_identity;type:varchar(36);not null;default:''" json:"operator_identity"` // operator_identity 业务字段
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&SafeEventDisposal{}) }
|
||||
func (table *SafeEventDisposal) TableName() string { return "safe_event_disposal" }
|
||||
@@ -1,15 +0,0 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// SafeInspection 对应 safe_inspection,保存安检与复检记录。
|
||||
type SafeInspection struct {
|
||||
Entity // 公共实体字段
|
||||
UserAccountID uint64 `gorm:"column:user_account_id;not null;index" json:"user_account_id"` // user_account_id 业务字段
|
||||
StaffAccountID uint64 `gorm:"column:staff_account_id;not null;index" json:"staff_account_id"` // staff_account_id 业务字段
|
||||
Result string `gorm:"column:result;type:varchar(32);not null" json:"result"` // result 业务字段
|
||||
EvidenceURI string `gorm:"column:evidence_uri;type:varchar(512);not null;default:''" json:"evidence_uri"` // evidence_uri 业务字段
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&SafeInspection{}) }
|
||||
func (table *SafeInspection) TableName() string { return "safe_inspection" }
|
||||
@@ -1,16 +0,0 @@
|
||||
package models
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// SafeRule 对应 safe_rule,保存安全规则。
|
||||
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: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:text;not null;default:''" json:"gray_scope"` // gray_scope 业务字段
|
||||
}
|
||||
|
||||
func init() { database.AppendMigrate(&SafeRule{}) }
|
||||
func (table *SafeRule) TableName() string { return "safe_rule" }
|
||||
@@ -28,7 +28,6 @@ func RegisterPlatform(serviceKey string, engine *gin.Engine) {
|
||||
registerStaffRoute(protected)
|
||||
registerUserRoute(protected)
|
||||
registerDeviceRoute(protected)
|
||||
registerSafetyRoute(protected)
|
||||
registerCommerceRoute(protected)
|
||||
registerFinanceRoute(protected)
|
||||
registerContentRoute(protected)
|
||||
@@ -60,14 +59,6 @@ func registerDeviceRoute(group *gin.RouterGroup) {
|
||||
telemetry.GET("/:identity", get)
|
||||
}
|
||||
|
||||
func registerSafetyRoute(group *gin.RouterGroup) {
|
||||
registerRestrictedWritableResource(group, "/safety/safe_rule", &models.SafeRule{}, []string{"rule_code", "version_no", "threshold", "action", "gray_scope"})
|
||||
registerRestrictedWritableResource(group, "/safety/safe_event", &models.SafeEvent{}, []string{"event_code", "level", "title", "smart_cylinder_valve_identity", "sla_at"})
|
||||
registerRestrictedWritableResource(group, "/safety/safe_inspection", &models.SafeInspection{}, []string{"result", "evidence_uri"}, requiredRelation("user_account_identity", "user_account_id", &models.UserAccount{}), requiredRelation("staff_account_identity", "staff_account_id", &models.StaffAccount{}))
|
||||
group.GET("/safety/safe_event/:identity/disposals", platform.ListSafetyEventDisposals)
|
||||
group.POST("/safety/safe_event/:identity/disposals", platform.DisposeSafetyEvent)
|
||||
}
|
||||
|
||||
func registerCommerceRoute(group *gin.RouterGroup) {
|
||||
categoryRelations := []platform.ResourceRelation{optionalRelation("parent_identity", "parent_id", &models.EcCategory{})}
|
||||
_, categoryCreate, _, categoryUpdate := platform.ResourceHandlers(&models.EcCategory{}, []string{"name", "sort_no"}, []string{"name", "sort_no"}, categoryRelations...)
|
||||
|
||||
@@ -87,7 +87,7 @@ func TestPlatformOrganizationAndAccountRoutesExposeResourceCRUD(t *testing.T) {
|
||||
assertRouteMethods(t, routes, "/heqi/platform/v1/platform/platform_role/:identity/menus", http.MethodPut)
|
||||
}
|
||||
|
||||
func TestPlatformDeviceSafetyCommerceAndDeliveryRoutesFollowTheirContracts(t *testing.T) {
|
||||
func TestPlatformDeviceCommerceAndDeliveryRoutesFollowTheirContracts(t *testing.T) {
|
||||
engine := gin.New()
|
||||
RegisterPlatform("heqi", engine)
|
||||
|
||||
@@ -101,7 +101,6 @@ func TestPlatformDeviceSafetyCommerceAndDeliveryRoutesFollowTheirContracts(t *te
|
||||
|
||||
for _, resource := range []string{
|
||||
"/device/dev_smart_cylinder_valve", "/device/dev_device_binding",
|
||||
"/safety/safe_rule", "/safety/safe_event", "/safety/safe_inspection",
|
||||
"/ec/ec_category", "/ec/ec_product", "/ec/ec_product_attribute", "/ec/ec_product_image", "/ec/ec_cart", "/ec/ec_order", "/ec/ec_order_item", "/ec/ec_review",
|
||||
"/delivery/delivery_task", "/delivery/delivery_track",
|
||||
} {
|
||||
@@ -126,20 +125,15 @@ func TestPlatformDeviceSafetyCommerceAndDeliveryRoutesFollowTheirContracts(t *te
|
||||
}
|
||||
}
|
||||
|
||||
disposal := "/heqi/platform/v1/safety/safe_event/:identity/disposals"
|
||||
assertRouteMethods(t, routes, disposal, http.MethodGet, http.MethodPost)
|
||||
if routes[disposal][http.MethodDelete] {
|
||||
t.Fatal("safety event disposals must be append-only")
|
||||
}
|
||||
|
||||
legacySafetyPrefix := "/safety/" + "sa" + "f_"
|
||||
for _, resource := range []string{legacySafetyPrefix + "rule", legacySafetyPrefix + "event", legacySafetyPrefix + "inspection"} {
|
||||
for _, resource := range []string{
|
||||
"/safety/safe_rule", "/safety/safe_event", "/safety/safe_inspection",
|
||||
"/safety/safe_event/:identity/disposals",
|
||||
} {
|
||||
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, path+"/:identity/status", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
||||
}
|
||||
assertNoRouteMethods(t, routes, "/heqi/platform/v1"+legacySafetyPrefix+"event/:identity/disposals", http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete)
|
||||
}
|
||||
|
||||
func TestPlatformFinanceContentRoutesFollowTheirContracts(t *testing.T) {
|
||||
|
||||
@@ -59,17 +59,13 @@
|
||||
| 用户地址 | `user_address` / `UserAddress` / `user_address.go` | `id`、`identity`、`user_id`、`address`、`longitude`、`latitude`、`is_default` | 地址历史和订单快照来源 |
|
||||
| 用户服务关系 | `user_service_relation` / `UserServiceRelation` / `user_service_relation.go` | `id`、`identity`、`user_id`、`gas_basic_id`、`delivery_basic_id`、`staff_id` | 用户与气站/配送点的服务归属 |
|
||||
|
||||
### 4.3 设备与安全
|
||||
### 4.3 设备
|
||||
|
||||
| 实体 | 表 / 模型 / 文件 | 关键字段 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| 智能瓶阀 | `dev_smart_cylinder_valve` / `DevSmartCylinderValve` / `dev_smart_cylinder_valve.go` | `device_no`、`model`、`online_status`、`owner_identity` | 智能瓶阀全生命周期档案 |
|
||||
| 设备绑定 | `dev_device_binding` / `DevDeviceBinding` / `dev_device_binding.go` | `id`、`identity`、`smart_cylinder_valve_id`、`user_id`、`effective_at`、`expired_at` | 用户/家庭与智能瓶阀授权关系 |
|
||||
| 遥测 | `dev_telemetry` / `DevTelemetry` / `dev_telemetry.go` | `smart_cylinder_valve_identity`、`reported_at`、`payload`、`quality_flag` | 遥测摘要与质量标记 |
|
||||
| 安全规则 | `saf_rule` / `SafRule` / `saf_rule.go` | `rule_code`、`version_no`、`threshold`、`action`、`gray_scope` | 告警、自动关阀、静默和灰度规则 |
|
||||
| 安全事件 | `saf_event` / `SafEvent` / `saf_event.go` | `event_code`、`level`、`smart_cylinder_valve_identity`、`status`、`sla_at` | 告警、安检和人工发现事件统一入口 |
|
||||
| 安全处置 | `saf_event_disposal` / `SafEventDisposal` / `saf_event_disposal.go` | `saf_event_identity`、`action`、`reason`、`operator_identity` | 派发、升级、关阀、复核和结案记录 |
|
||||
| 安检记录 | `saf_inspection` / `SafInspection` / `saf_inspection.go` | `id`、`identity`、`user_id`、`staff_id`、`result`、`evidence_uri` | 安检、整改和复检证据 |
|
||||
|
||||
### 4.4 电商、订单与配送轨迹
|
||||
|
||||
@@ -149,16 +145,12 @@
|
||||
| `PATCH` | `/user/:identity/status` | 冻结登录、限制下单或设备控制 |
|
||||
| `POST` | `/user/service_relation` | 建立或变更用户服务关系 |
|
||||
|
||||
### 5.4 智能瓶阀安全、订单与配送轨迹
|
||||
### 5.4 智能瓶阀、订单与配送轨迹
|
||||
|
||||
| 方法 | 路由 | 作用 |
|
||||
| --- | --- | --- |
|
||||
| `GET/POST` | `/device/dev_smart_cylinder_valve` | 智能瓶阀查询、建档 |
|
||||
| `GET/PUT/DELETE` | `/device/dev_smart_cylinder_valve/:identity` | 智能瓶阀详情、更新、逻辑删除/归档 |
|
||||
| `GET/POST` | `/safety/saf_event` | 安全事件查询、创建 |
|
||||
| `GET/PUT/DELETE` | `/safety/saf_event/:identity` | 安全事件详情、更新、逻辑删除/归档 |
|
||||
| `GET/POST` | `/safety/saf_rule` | 安全规则 CRUD 的列表与创建 |
|
||||
| `GET/PUT/DELETE` | `/safety/saf_rule/:identity` | 安全规则详情、更新、逻辑删除 |
|
||||
| `GET/POST` | `/ec/ec_order` | 电商订单查询、创建 |
|
||||
| `GET/PUT/DELETE` | `/ec/ec_order/:identity` | 电商订单详情、更新、逻辑删除/归档 |
|
||||
| `GET/POST` | `/delivery/delivery_track` | 配送轨迹查询、创建 |
|
||||
@@ -237,9 +229,6 @@
|
||||
| 业主客户 `UsersRound` | 服务关系 `GitFork` | `/user/service-relation` | `views/user/service-relation` | `ListPage.vue` | `user_service_relation` CRUD、气站/配送点归属 |
|
||||
| 设备管理 `ShieldAlert` | 智能瓶阀 `Gauge` | `/device/valve` | `views/device/valve` | `ListPage.vue` | `dev_smart_cylinder_valve` CRUD、型号、在线状态、归属 |
|
||||
| 设备管理 `ShieldAlert` | 设备绑定 `Link2` | `/device/binding` | `views/device/binding` | `ListPage.vue` | `dev_device_binding` CRUD、用户与智能瓶阀绑定 |
|
||||
| 设备管理 `ShieldAlert` | 安全规则 `Siren` | `/safety/rule` | `views/safety/rule` | `ListPage.vue` | `saf_rule` CRUD、阈值、规则版本和状态 |
|
||||
| 设备管理 `ShieldAlert` | 安全事件 `TriangleAlert` | `/safety/event` | `views/safety/event` | `ListPage.vue` | `saf_event` CRUD、等级、设备、用户和状态 |
|
||||
| 设备管理 `ShieldAlert` | 安检记录 `ClipboardCheck` | `/safety/inspection` | `views/safety/inspection` | `ListPage.vue` | `saf_inspection` CRUD、人员、结果和证据 |
|
||||
| 电商管理 `ShoppingBag` | 商品分类 `FolderTree` | `/ec/category` | `views/ec/category` | `TreePage.vue` | `ec_category` 分类树 CRUD、排序和状态 |
|
||||
| 电商管理 `ShoppingBag` | 商品管理 `PackageSearch` | `/ec/product` | `views/ec/product` | `ListPage.vue` | `ec_product` CRUD、分类、价格、库存和状态 |
|
||||
| 电商管理 `ShoppingBag` | 商品属性 `Tags` | `/ec/product-attribute` | `views/ec/product-attribute` | `ListPage.vue` | `ec_product_attribute` CRUD、商品规格与属性 |
|
||||
|
||||
@@ -113,7 +113,6 @@ platforms/
|
||||
- `identity`:登录、验证码、账号、RBAC、组织、数据范围、协议同意。
|
||||
- `organization`:气站、配送点、服务区域、人员归属、用户服务关系和邀请注册二维码。
|
||||
- `device`:设备注册/绑定、设备模型、遥测、命令、在线状态、固件。
|
||||
- `safety`:规则、告警事件、分级、SLA、整改、安检、复检、升级。
|
||||
- `catalog`:分类、商品、SKU、库存、优惠券、收藏、购物车。
|
||||
- `order`:订单、支付、合同、配送、安装、售后、评价。
|
||||
- `dispatch`:服务人员、区域、班次、任务、派单、导航、配送轨迹与现场取证。
|
||||
@@ -136,7 +135,6 @@ platforms/
|
||||
| 身份与权限 | `idn_` | `idn_account`、`idn_role`、`idn_permission` |
|
||||
| 组织与服务关系 | `org_` | `org_gas_station`、`org_delivery_point`、`org_service_person`、`org_user_service_relation`、`org_invitation_qr_code` |
|
||||
| 设备与物联网 | `dev_` | `dev_device`、`dev_telemetry`、`dev_command` |
|
||||
| 安全与安检 | `saf_` | `saf_event`、`saf_rule`、`saf_inspection`、`saf_rectification` |
|
||||
| 商品与营销 | `cat_` | `cat_product`、`cat_sku`、`cat_coupon`、`cat_cart` |
|
||||
| 订单与履约 | `ord_` | `ord_order`、`ord_payment`、`ord_service_task`、`ord_delivery_track` |
|
||||
| 调度与配送 | `dsp_` | `dsp_assignment`、`dsp_shift`、`dsp_delivery_track_point` |
|
||||
|
||||
@@ -73,15 +73,9 @@ export function auditPlatform({ manifest, resources, readOnlyPage, routeSources,
|
||||
}
|
||||
if (!/^[\u4e00-\u9fff]/.test(resource.title) || resource.fields.length === 0 || resource.fields.some((field) => field.key === 'id' || field.key.endsWith('_id')) || resource.fields.some((field) => !/^[\u4e00-\u9fff]/.test(field.label))) failures.push(`${label}: invalid frontend allowlist`);
|
||||
for (const expected of requiredBackendRoutes(contract)) if (!manifest.routes.some((route) => route.method === expected.method && route.path === expected.path)) failures.push(`${label}: missing backend ${expected.method}`);
|
||||
if (contract.mode === 'append_only') {
|
||||
const event = resources.find((item) => item.name === 'safe_event');
|
||||
if (!event?.detailActions?.some((action) => action.name === contract.name && action.resource === contract.path)) failures.push(`${label}: missing safe_event detail action`);
|
||||
if ([...viewSources.keys()].some((file) => file.includes(`/${contract.name}/`))) failures.push(`${label}: independent page exposed`);
|
||||
} else {
|
||||
const coverage = routeCoverage(contract, routeSources, viewSources);
|
||||
if (!coverage.hasPage) failures.push(`${label}: missing page`);
|
||||
if (!coverage.hasMenu) failures.push(`${label}: missing menu route`);
|
||||
}
|
||||
if (contract.mode === 'readonly') {
|
||||
const statusCall = new RegExp(`resourceApi\\.updateStatus\\(\\s*['\"]${escapeRegExp(contract.path)}['\"]`);
|
||||
for (const [file, source] of [...sourceEntries(routeSources, 'src/router'), ...sourceEntries(apiSources, 'src/api'), ...sourceEntries(viewSources, 'src/views')]) {
|
||||
|
||||
@@ -7,11 +7,8 @@ const resourcesSource = readFileSync('src/api/resources.ts', 'utf8');
|
||||
const legacySafetyPrefix = ['sa', 'f_'].join('');
|
||||
const legacyAuditPrefix = ['au', 'd_'].join('');
|
||||
|
||||
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'/);
|
||||
test('安全模块资源定义已完全删除', () => {
|
||||
assert.doesNotMatch(resourcesSource, /safe_(?:rule|event|inspection|event_disposal)|\/safety\//);
|
||||
|
||||
assert.doesNotMatch(resourcesSource, new RegExp(`define\\('${legacySafetyPrefix}(?:rule|event|inspection|event_disposal)',`));
|
||||
assert.doesNotMatch(resourcesSource, new RegExp(`action\\('${legacySafetyPrefix}event_disposal',`));
|
||||
@@ -76,25 +73,6 @@ test('每个资源必须由带菜单元数据的路由实际加载对应页面',
|
||||
assert.deepEqual(failures, ['gas/gas_basic: missing menu route']);
|
||||
});
|
||||
|
||||
test('仅追加处置必须挂在安全事件详情动作且不得有独立页面', () => {
|
||||
const failures = auditPlatform({
|
||||
manifest: { resources: [{ domain: 'safety', name: 'safe_event_disposal', path: '/safety/safe_event/:identity/disposals', mode: 'append_only', pageKind: 'list' }], routes: [
|
||||
{ method: 'GET', path: '/safety/safe_event/:identity/disposals' },
|
||||
{ method: 'POST', path: '/safety/safe_event/:identity/disposals' },
|
||||
] },
|
||||
resources: [{ name: 'safe_event_disposal', resource: '/safety/safe_event/:identity/disposals', mode: 'append_only', pageKind: 'list', title: '事件处置', fields: [{ key: 'action', label: '处置动作' }] }],
|
||||
readOnlyPage: '',
|
||||
routeSources: [],
|
||||
viewSources: new Map([['src/views/safety/safe_event_disposal/ListPage.vue', '<template />']]),
|
||||
apiSources: new Map(),
|
||||
});
|
||||
|
||||
assert.deepEqual(failures, [
|
||||
'safety/safe_event_disposal: missing safe_event detail action',
|
||||
'safety/safe_event_disposal: independent page exposed',
|
||||
]);
|
||||
});
|
||||
|
||||
test('只读资源拒绝 API 或路由中的状态写入', () => {
|
||||
const failures = auditPlatform({
|
||||
manifest: { resources: [{ domain: 'wallet', name: 'wallet', path: '/wallet/wallet', mode: 'readonly', pageKind: 'list' }], routes: [
|
||||
|
||||
@@ -56,25 +56,11 @@ test('表单载荷构造器省略空的可选关系并转换字段类型', async
|
||||
);
|
||||
});
|
||||
|
||||
test('安全规则与订单明细的文本字段允许任意文本提交', () => {
|
||||
test('订单明细文本字段允许任意文本提交', () => {
|
||||
const resources = loadResources();
|
||||
const { buildResourcePayload } = loadResourceForm();
|
||||
const fields = (name) => resources.find((item) => item.name === name).fields;
|
||||
|
||||
assert.deepEqual(
|
||||
JSON.parse(JSON.stringify(buildResourcePayload(fields('safe_rule'), {
|
||||
rule_code: 'pressure-limit',
|
||||
threshold: '{invalid}',
|
||||
action: 'close-valve',
|
||||
gray_scope: 'north region only',
|
||||
}))),
|
||||
{
|
||||
rule_code: 'pressure-limit',
|
||||
threshold: '{invalid}',
|
||||
action: 'close-valve',
|
||||
gray_scope: 'north region only',
|
||||
},
|
||||
);
|
||||
assert.deepEqual(
|
||||
JSON.parse(JSON.stringify(buildResourcePayload(fields('ec_order_item'), {
|
||||
ec_order_identity: 'order-a',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { platformAPI, type DashboardOverview, type OrgDeliveryPoint, type OrgGasStation, type OrgServicePerson, type PlatfromAccount, type Profile, type SafeEvent } from './api/platform';
|
||||
import { platformAPI, type DashboardOverview, type OrgDeliveryPoint, type OrgGasStation, type OrgServicePerson, type PlatfromAccount, type Profile } from './api/platform';
|
||||
|
||||
type Tab = 'dashboard' | 'station' | 'delivery' | 'person' | 'user' | 'safety' | 'trade' | 'finance' | 'content' | 'track' | 'operation' | 'audit';
|
||||
type Tab = 'dashboard' | 'station' | 'delivery' | 'person' | 'user' | 'trade' | 'finance' | 'content' | 'track' | 'operation' | 'audit';
|
||||
type CatalogItem = { title: string; description: string; operations: string[] };
|
||||
|
||||
const currentTab = ref<Tab>('dashboard');
|
||||
@@ -14,14 +14,13 @@ const stations = ref<OrgGasStation[]>([]);
|
||||
const deliveryPoints = ref<OrgDeliveryPoint[]>([]);
|
||||
const servicePeople = ref<OrgServicePerson[]>([]);
|
||||
const users = ref<PlatfromAccount[]>([]);
|
||||
const safetyEvents = ref<SafeEvent[]>([]);
|
||||
const profile = ref<Profile>();
|
||||
const loginForm = reactive({ username: 'root', password: '' });
|
||||
const stationForm = reactive({ stationCode: '', name: '', principal: '', serviceArea: '' });
|
||||
|
||||
const tabs: Array<{ key: Tab; label: string }> = [
|
||||
{ key: 'dashboard', label: '运营总览' }, { key: 'station', label: '气站管理' }, { key: 'delivery', label: '配送点管理' },
|
||||
{ key: 'person', label: '服务人员' }, { key: 'user', label: '用户管理' }, { key: 'safety', label: '安全运营' },
|
||||
{ key: 'person', label: '服务人员' }, { key: 'user', label: '用户管理' },
|
||||
{ key: 'trade', label: '商品交易' }, { key: 'finance', label: '资金结算' }, { key: 'content', label: '内容客服' },
|
||||
{ key: 'track', label: '邀请与轨迹' }, { key: 'operation', label: '全局运营' }, { key: 'audit', label: '审计合规' },
|
||||
];
|
||||
@@ -37,7 +36,7 @@ const catalog: Partial<Record<Tab, CatalogItem[]>> = {
|
||||
|
||||
const overviewCards = computed(() => [
|
||||
['启用气站', overview.value.gasStationCount ?? 0], ['启用配送点', overview.value.deliveryPointCount ?? 0],
|
||||
['在岗服务人员', overview.value.servicePersonCount ?? 0], ['服务用户', overview.value.userCount ?? 0], ['待处理安全事件', overview.value.pendingSafetyCount ?? 0],
|
||||
['在岗服务人员', overview.value.servicePersonCount ?? 0], ['服务用户', overview.value.userCount ?? 0],
|
||||
]);
|
||||
const activeLabel = computed(() => tabs.find((tab) => tab.key === currentTab.value)?.label ?? '平台总后台');
|
||||
|
||||
@@ -46,9 +45,9 @@ async function loadData() {
|
||||
loading.value = true;
|
||||
errorMessage.value = '';
|
||||
try {
|
||||
[profile.value, overview.value, stations.value, deliveryPoints.value, servicePeople.value, users.value, safetyEvents.value] = await Promise.all([
|
||||
[profile.value, overview.value, stations.value, deliveryPoints.value, servicePeople.value, users.value] = await Promise.all([
|
||||
platformAPI.getProfile(), platformAPI.getDashboard(), platformAPI.listOrgGasStation(), platformAPI.listOrgDeliveryPoint(),
|
||||
platformAPI.listOrgServicePerson(), platformAPI.listPlatfromAccount(), platformAPI.listSafeEvent(),
|
||||
platformAPI.listOrgServicePerson(), platformAPI.listPlatfromAccount(),
|
||||
]);
|
||||
} catch (error) {
|
||||
errorMessage.value = error instanceof Error ? error.message : '加载数据失败';
|
||||
@@ -103,7 +102,6 @@ onMounted(loadData);
|
||||
<template v-if="currentTab === 'delivery'"><article class="panel"><h2>配送点管理边界</h2><p>配送点归属气站,负责管理服务人员、用户、配送班次、订单交付与配送轨迹;平台可跨组织查看、审核、冻结和迁移。</p></article><DataTable :headers="['编码', '配送点', '归属气站', '服务区域', '状态']" :rows="deliveryPoints.map((item) => [item.deliveryCode, item.name, item.gasStationName, item.serviceArea, item.status])" /></template>
|
||||
<template v-if="currentTab === 'person'"><article class="panel"><h2>服务人员生命周期</h2><p>覆盖安装维修、安检、配送角色、资质、登录设备、接单状态、任务绩效及停用留档。</p></article><DataTable :headers="['姓名', '手机号', '角色', '工作状态', '资质状态']" :rows="servicePeople.map((item) => [item.name, item.phoneMasked, item.roles, item.workStatus, item.credentialStatus])" /></template>
|
||||
<template v-if="currentTab === 'user'"><article class="panel"><h2>平台账户管理</h2><p>统一查看平台账户资料、角色、头像、手机号、状态与操作记录;敏感信息按最小化原则展示。</p></article><DataTable :headers="['账号', '名称', '头像', '手机号', '角色', '状态']" :rows="users.map((item) => [item.username, item.displayName, item.avatar || '未设置', item.phoneMasked, item.roleCode, item.status])" /></template>
|
||||
<template v-if="currentTab === 'safety'"><article class="panel"><h2>安全运营中心</h2><p>对智能瓶阀告警、安检异常、可燃气体风险、资质临期与订单异常进行分级、派发、升级和复盘。</p></article><DataTable :headers="['事件编码', '等级', '事件说明', '状态', '创建时间']" :rows="safetyEvents.map((item) => [item.eventCode, `${item.level} 级`, item.title, item.status, new Date(item.createdAt).toLocaleString()])" /></template>
|
||||
<template v-if="catalog[currentTab]"><div class="catalog"><article v-for="item in catalog[currentTab]" :key="item.title" class="panel"><h2>{{ item.title }}</h2><p>{{ item.description }}</p><div class="tags"><span v-for="operation in item.operations" :key="operation">{{ operation }}</span></div></article></div></template>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@@ -62,17 +62,8 @@ const labels: Record<string, string> = {
|
||||
effective_at: '生效时间',
|
||||
reported_at: '上报时间',
|
||||
payload: '遥测数据',
|
||||
rule_code: '规则编码',
|
||||
version_no: '版本号',
|
||||
threshold: '阈值',
|
||||
action: '处置动作',
|
||||
gray_scope: '灰度范围',
|
||||
event_code: '事件编码',
|
||||
level: '事件等级',
|
||||
title: '标题',
|
||||
sla_at: '处置时限',
|
||||
result: '检查结果',
|
||||
evidence_uri: '凭证地址',
|
||||
reason: '处置原因',
|
||||
sort_no: '排序号',
|
||||
product_code: '商品编码',
|
||||
@@ -161,7 +152,6 @@ const datetimeFields = new Set([
|
||||
'expired_at',
|
||||
'effective_at',
|
||||
'reported_at',
|
||||
'sla_at',
|
||||
'occurred_at',
|
||||
'started_at',
|
||||
'completed_at',
|
||||
@@ -181,8 +171,6 @@ const textareaFields = new Set([
|
||||
'reason',
|
||||
'opinion',
|
||||
'payload',
|
||||
'threshold',
|
||||
'gray_scope',
|
||||
'product_snapshot',
|
||||
]);
|
||||
const fieldType = (key: string): ResourceFieldType => {
|
||||
@@ -223,10 +211,6 @@ const titles: Record<string, string> = {
|
||||
dev_smart_cylinder_valve: '智能钢瓶阀',
|
||||
dev_device_binding: '设备绑定',
|
||||
dev_telemetry: '设备遥测',
|
||||
safe_rule: '安全规则',
|
||||
safe_event: '安全事件',
|
||||
safe_inspection: '安全检查',
|
||||
safe_event_disposal: '事件处置',
|
||||
ec_category: '商品分类',
|
||||
ec_product: '商品管理',
|
||||
ec_product_attribute: '商品属性',
|
||||
@@ -406,45 +390,6 @@ export const resources: ResourceUiDefinition[] = [
|
||||
'reported_at',
|
||||
'payload',
|
||||
]),
|
||||
define('safe_rule', '/safety/safe_rule', 'writable', 'list', [
|
||||
'rule_code!',
|
||||
'version_no',
|
||||
'threshold',
|
||||
'action',
|
||||
'gray_scope',
|
||||
]),
|
||||
define(
|
||||
'safe_event',
|
||||
'/safety/safe_event',
|
||||
'writable',
|
||||
'list',
|
||||
[
|
||||
'event_code!',
|
||||
'level!',
|
||||
'title!',
|
||||
'smart_cylinder_valve_identity!',
|
||||
'sla_at',
|
||||
],
|
||||
[
|
||||
action('safe_event_disposal', '/safety/safe_event/:identity/disposals', [
|
||||
'action!',
|
||||
'reason!',
|
||||
]),
|
||||
],
|
||||
),
|
||||
define('safe_inspection', '/safety/safe_inspection', 'writable', 'list', [
|
||||
'user_account_identity!',
|
||||
'staff_account_identity!',
|
||||
'result!',
|
||||
'evidence_uri',
|
||||
]),
|
||||
define(
|
||||
'safe_event_disposal',
|
||||
'/safety/safe_event/:identity/disposals',
|
||||
'append_only',
|
||||
'list',
|
||||
['action!', 'reason!'],
|
||||
),
|
||||
define('ec_category', '/ec/ec_category', 'writable', 'tree', [
|
||||
'parent_identity',
|
||||
'name!',
|
||||
|
||||
@@ -5,7 +5,6 @@ import deliveryRoutes from './delivery';
|
||||
import staffRoutes from './staff';
|
||||
import userRoutes from './user';
|
||||
import deviceRoutes from './device';
|
||||
import safetyRoutes from './safety';
|
||||
import ecRoutes from './ec';
|
||||
import financeRoutes from './finance';
|
||||
import contentRoutes from './content';
|
||||
@@ -19,4 +18,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, ...customerServiceRoutes, ...walletRoutes];
|
||||
export default [...platformRoutes, ...gasRoutes, ...deliveryRoutes, ...staffRoutes, ...userRoutes, ...deviceRoutes, ...ecRoutes, ...financeRoutes, ...contentRoutes, ...customerServiceRoutes, ...walletRoutes];
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
const routes: AppRouteRecordRaw[] = [{ path: '/safety', name: 'safety', component: DEFAULT_LAYOUT, meta: { locale: 'menu.platform.safety', requiresAuth: true, icon: 'icon-apps', order: 15 }, children: [
|
||||
{ path: 'safe-rule', name: 'safety-safe-rule', component: () => import('@/views/safety/safe_rule/ListPage.vue'), meta: { locale: 'menu.platform.safety.safe_rule', requiresAuth: true, menuCode: 'safety' } },
|
||||
{ path: 'safe-event', name: 'safety-safe-event', component: () => import('@/views/safety/safe_event/ListPage.vue'), meta: { locale: 'menu.platform.safety.safe_event', requiresAuth: true, menuCode: 'safety' } },
|
||||
{ path: 'safe-inspection', name: 'safety-safe-inspection', component: () => import('@/views/safety/safe_inspection/ListPage.vue'), meta: { locale: 'menu.platform.safety.safe_inspection', requiresAuth: true, menuCode: 'safety' } }
|
||||
] }];
|
||||
export default routes;
|
||||
@@ -1,6 +0,0 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/safety/safe_event');
|
||||
</script>
|
||||
@@ -1,6 +0,0 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/safety/safe_inspection');
|
||||
</script>
|
||||
@@ -1,6 +0,0 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/safety/safe_rule');
|
||||
</script>
|
||||
Reference in New Issue
Block a user