完善运行轨迹点展示与模拟数据
This commit is contained in:
@@ -64,6 +64,13 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("mock gasorder statuses repaired: %d\n", count)
|
||||
case "repair-mock-track-point":
|
||||
count, err := repairMockTrackPoint()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("mock track points repaired: %d\n", count)
|
||||
case "migrate":
|
||||
if err := migrateDatabase(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
@@ -78,7 +85,7 @@ func main() {
|
||||
}
|
||||
|
||||
func printUsage() {
|
||||
fmt.Fprintln(os.Stderr, "usage: platform-cli <version|resource-contract|gas-resource-contract|delivery-resource-contract|migrate|mock-data|repair-mock-gasorder-status>")
|
||||
fmt.Fprintln(os.Stderr, "usage: platform-cli <version|resource-contract|gas-resource-contract|delivery-resource-contract|migrate|mock-data|repair-mock-gasorder-status|repair-mock-track-point>")
|
||||
}
|
||||
|
||||
type route struct {
|
||||
@@ -209,6 +216,27 @@ func repairMockGasorderStatus() (int64, error) {
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// repairMockTrackPoint 使用独立幂等命令补全固定演示轨迹点,不触发其他模拟数据写入。
|
||||
func repairMockTrackPoint() (int64, error) {
|
||||
config.New(serviceKey)
|
||||
if config.Spec.Databases == nil {
|
||||
return 0, fmt.Errorf("database configuration is required")
|
||||
}
|
||||
databaseService, err := database.NewDatabase(
|
||||
config.Spec.Databases.Driver,
|
||||
config.Spec.Databases.Source,
|
||||
dbsql.SetOptions(nil),
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("connect database: %w", err)
|
||||
}
|
||||
count, err := seed.RepairMockTrackPointSample(databaseService)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("repair mock track point: %w", err)
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func migrateDatabase() error {
|
||||
config.New(serviceKey)
|
||||
options := &types.SqlOptions{
|
||||
|
||||
@@ -252,7 +252,10 @@ func MockData(database *gorm.DB) error {
|
||||
trackPoint := models.GasorderTrackPoint{
|
||||
Entity: entity(23, common.StatusEnable), GasorderTrackID: track.ID,
|
||||
Longitude: address.Longitude, Latitude: address.Latitude,
|
||||
OccurredAt: trackCompletedAt, Source: "gps", Accuracy: "10m",
|
||||
RequestNo: mockTrackPointRequestNo, OccurredAt: trackCompletedAt,
|
||||
ReceivedAt: trackCompletedAt.Add(mockTrackPointReceiveDelay),
|
||||
Source: "gps", Accuracy: "10m", Speed: mockTrackPointSpeed,
|
||||
Direction: mockTrackPointDirection,
|
||||
}
|
||||
if err := put(tx, &trackPoint); err != nil {
|
||||
return err
|
||||
@@ -786,6 +789,43 @@ func RepairMockGasorderInitialStatuses(database *gorm.DB) (int64, error) {
|
||||
return result.RowsAffected, nil
|
||||
}
|
||||
|
||||
const (
|
||||
mockTrackPointRequestNo = "MOCK-TRACK-POINT-001"
|
||||
mockTrackPointSpeed = "12 km/h"
|
||||
mockTrackPointDirection = "东北(45°)"
|
||||
mockTrackPointReceiveDelay = 2 * time.Second
|
||||
)
|
||||
|
||||
// RepairMockTrackPointSample 仅补全固定演示轨迹点的缺失上报字段,不覆盖已有有效数据。
|
||||
func RepairMockTrackPointSample(database *gorm.DB) (int64, error) {
|
||||
var point models.GasorderTrackPoint
|
||||
identity := fmt.Sprintf("%s%012d", mockIdentityPrefix, 23)
|
||||
if err := database.Where("identity = ?", identity).First(&point).Error; err != nil {
|
||||
return 0, fmt.Errorf("find mock track point sample: %w", err)
|
||||
}
|
||||
updates := map[string]any{}
|
||||
if point.RequestNo == "" {
|
||||
updates["request_no"] = mockTrackPointRequestNo
|
||||
}
|
||||
if point.ReceivedAt.IsZero() {
|
||||
updates["received_at"] = point.OccurredAt.Add(mockTrackPointReceiveDelay)
|
||||
}
|
||||
if point.Speed == "" {
|
||||
updates["speed"] = mockTrackPointSpeed
|
||||
}
|
||||
if point.Direction == "" {
|
||||
updates["direction"] = mockTrackPointDirection
|
||||
}
|
||||
if len(updates) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
result := database.Model(&point).Updates(updates)
|
||||
if result.Error != nil {
|
||||
return 0, fmt.Errorf("repair mock track point sample: %w", result.Error)
|
||||
}
|
||||
return result.RowsAffected, nil
|
||||
}
|
||||
|
||||
// linkMockProductProducer 为未来生成的 Mock 智能气阀建立真实生产商关联。
|
||||
func linkMockProductProducer(product *models.ProductInfo, producer models.ProducerAccount) error {
|
||||
if product == nil || producer.ID == 0 {
|
||||
|
||||
@@ -93,3 +93,16 @@ func TestMockGasorderInitialStatusMatchesWorkflow(t *testing.T) {
|
||||
t.Fatal("Mock 配送订单不得使用通用待处理状态")
|
||||
}
|
||||
}
|
||||
|
||||
// TestMockTrackPointSampleRequiredFields 验证演示轨迹点符合真实上报的必填字段和可读展示要求。
|
||||
func TestMockTrackPointSampleRequiredFields(t *testing.T) {
|
||||
if mockTrackPointRequestNo == "" {
|
||||
t.Fatal("Mock 轨迹点必须包含上报流水号")
|
||||
}
|
||||
if mockTrackPointReceiveDelay <= 0 {
|
||||
t.Fatal("Mock 轨迹点接收时间必须晚于定位时间")
|
||||
}
|
||||
if mockTrackPointSpeed == "" || mockTrackPointDirection == "" {
|
||||
t.Fatal("演示轨迹点必须提供可读的速度和方向")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
- 工作人员列改为展示人员姓名,列名统一为“配送人员”。
|
||||
- 配送人员关联明确限定 `delivery` 岗位,但历史详情不附加启用、在岗或资质有效过滤,确保离岗人员的历史轨迹仍可回看。
|
||||
- 详情接口的 `{ track, points }` 聚合响应新增主记录解包与轨迹点固定中文列契约。
|
||||
- 固定 Mock 轨迹点补齐上报流水号、服务端接收时间、速度和方向;新增独立幂等修复命令,不修改真实轨迹。
|
||||
- 轨迹点“行进方向”按原始采集值展示,避免与钱包“收支方向”的同名字段枚举冲突。
|
||||
- 两列继续保留完整系统唯一标识的悬浮提示、复制入口和关联详情跳转能力。
|
||||
- 增加展示契约检查,防止运行轨迹退回裸唯一标识展示。
|
||||
|
||||
@@ -32,6 +34,9 @@
|
||||
- `frontend/platform_admin/scripts/check-staff-relation-policy.mjs`:补充历史轨迹人员角色与不过滤当前状态的契约检查。
|
||||
- `frontend/platform_admin/src/api/resource-display.ts`:支持运行轨迹主记录解包及轨迹点专属中文字段语义。
|
||||
- `frontend/platform_admin/src/api/resource-detail-contract.ts`:声明运行轨迹基本信息顺序、关联资源和轨迹点固定列。
|
||||
- `backend/api/internal/seed/mock.go`:修正演示轨迹点生成值并提供精确范围的数据修复函数。
|
||||
- `backend/api/cmd/cli/main.go`:新增 `repair-mock-track-point` 独立修复命令。
|
||||
- `backend/api/internal/seed/mock_test.go`:补充演示轨迹点字段完整性测试。
|
||||
|
||||
## 风险评估
|
||||
|
||||
@@ -47,3 +52,6 @@
|
||||
- Codex 内置浏览器刷新运行轨迹后,配送订单显示 `MOCK-GASORDER-001`,配送人员显示“王师傅”;原截断标识不再作为主文案,两个完整唯一标识仍可复制。
|
||||
- 修复详情页工作人员关联缺少显式角色策略导致的加载失败,并重新验证详情页。
|
||||
- 浏览器详情页验证通过:加载失败提示数量为 0;基本信息正确显示订单号、配送人员姓名、尝试次数和时间,关联记录按固定中文列显示轨迹点。
|
||||
- 执行 `go run ./cmd/cli repair-mock-track-point`,精确修复 1 条固定 Mock 轨迹点。
|
||||
- 最终浏览器验证:零时间数量为 0;接收时间为 `2026-07-01 08:50:02`,速度为 `12 km/h`,方向为“东北(45°)”,上报流水号为 `MOCK-TRACK-POINT-001`。
|
||||
- 后端 `go test ./...`、前端类型检查、生产构建和展示契约检查均通过。
|
||||
|
||||
@@ -116,5 +116,10 @@ assertIncludes(
|
||||
"if (detail.track && typeof detail.track === 'object')",
|
||||
'运行轨迹详情必须解包 track 聚合主记录',
|
||||
);
|
||||
assertIncludes(
|
||||
detailContract,
|
||||
"rawColumns: ['direction']",
|
||||
'轨迹点行进方向必须保留原始采集值,不得套用收支方向枚举',
|
||||
);
|
||||
|
||||
process.stdout.write('平台48类资源中文展示契约检查通过\n');
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
export type ResourceCollectionContract = {
|
||||
columns: string[];
|
||||
jsonColumns?: string[];
|
||||
rawColumns?: string[];
|
||||
relationIdentityKeys?: Record<string, string>;
|
||||
};
|
||||
|
||||
@@ -115,6 +116,7 @@ const contracts: Record<string, ResourceDetailContract> = {
|
||||
'occurred_at', 'received_at', 'longitude', 'latitude', 'accuracy',
|
||||
'speed', 'direction', 'source', 'request_no',
|
||||
],
|
||||
rawColumns: ['direction'],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -173,3 +175,15 @@ export function isCollectionJsonField(
|
||||
?.jsonColumns?.includes(column),
|
||||
);
|
||||
}
|
||||
|
||||
/** 判断集合字段是否必须保留接口原始值,避免同名全局枚举误用到其他业务语义。 */
|
||||
export function isCollectionRawField(
|
||||
resourceName: string,
|
||||
collectionKey: string,
|
||||
column: string,
|
||||
) {
|
||||
return Boolean(
|
||||
contracts[resourceName]?.collections?.[collectionKey]
|
||||
?.rawColumns?.includes(column),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
>
|
||||
<template #cell="{ record }">
|
||||
<div v-if="collectionRelationIdentityKey(definition.name, collection.key, column)" class="collection-relation-value">
|
||||
<span>{{ displayRawValue(column, record[column]) }}</span>
|
||||
<span>{{ displayCollectionValue(collection.key, column, record[column]) }}</span>
|
||||
<IdentityText
|
||||
v-if="record[collectionRelationIdentityKey(definition.name, collection.key, column)]"
|
||||
:value="String(record[collectionRelationIdentityKey(definition.name, collection.key, column)])"
|
||||
@@ -55,7 +55,7 @@
|
||||
<pre class="collection-json-value">{{ formatCollectionJson(record[column]) }}</pre>
|
||||
</template>
|
||||
</a-popover>
|
||||
<template v-else>{{ displayRawValue(column, record[column]) }}</template>
|
||||
<template v-else>{{ displayCollectionValue(collection.key, column, record[column]) }}</template>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</template>
|
||||
@@ -81,6 +81,7 @@ import {
|
||||
import {
|
||||
collectionRelationIdentityKey,
|
||||
isCollectionJsonField,
|
||||
isCollectionRawField,
|
||||
isResourceJsonField,
|
||||
resourceDetailContract,
|
||||
} from '@/api/resource-detail-contract';
|
||||
@@ -134,6 +135,18 @@ function collectionColumnWidth(collectionKey: string, column: string) {
|
||||
return 160;
|
||||
}
|
||||
|
||||
/** 按聚合集合契约格式化单元格,专属原始字段不得套用同名全局枚举。 */
|
||||
function displayCollectionValue(
|
||||
collectionKey: string,
|
||||
column: string,
|
||||
value: unknown,
|
||||
) {
|
||||
if (isCollectionRawField(props.definition.name, collectionKey, column)) {
|
||||
return value == null || value === '' ? '-' : String(value);
|
||||
}
|
||||
return displayRawValue(column, value);
|
||||
}
|
||||
|
||||
/** 将字符串或对象参数格式化为可读 JSON;历史非 JSON 文本保持原样。 */
|
||||
function formatCollectionJson(value: unknown) {
|
||||
if (value == null || value === '') return '暂无参数';
|
||||
|
||||
Reference in New Issue
Block a user