diff --git a/openspec/changes/update-asset-sync-status-trail/proposal.md b/openspec/changes/update-asset-sync-status-trail/proposal.md new file mode 100644 index 0000000..a7a730e --- /dev/null +++ b/openspec/changes/update-asset-sync-status-trail/proposal.md @@ -0,0 +1,32 @@ +# Change: 资产同步状态与同步轨迹入口 + +## Why + +当前资产信息页虽保留了手动刷新能力和部分同步时间展示,但运营无法直观看到资产当前轮询策略、最近活跃情况及下次轮询时间,也没有可直接进入该资产同步轨迹的入口。 + +## What Changes + +- 在资产信息页的卡和设备详情中新增同步状态区域,展示轮询启用状态、活跃级别、最后活跃时间、最后活跃场景和下次轮询时间。 +- 复用 `GET /api/admin/assets/resolve/{identifier}` 的新增 `polling` 响应对象;无下次轮询时间时展示 `-`。 +- 保留现有手动刷新按钮及其接口契约,不新增第二个同步或刷新按钮。 +- 新增“查看同步轨迹”入口,跳转全局审计外部集成页并带入当前资产的筛选参数。 +- 同步轨迹由目标页面按立即、3分钟、5分钟连续结果展示;本提案不改造全局审计外部集成页面本身。 +- 对 `rate_limited` 手动刷新结果仅展示本次失败,不展示自动退避倒计时。 + +## Impact + +- Affected specs: `asset-information` +- Related active changes: `update-asset-realtime-sync-timestamps-display`(共享资产同步展示区域) +- Affected code: + - `src/types/api/asset.ts` + - `src/api/modules/asset.ts` + - `src/views/asset-management/asset-information/**` + - 资产信息页到全局审计外部集成页的路由跳转参数 +- API contracts: + - `GET /api/admin/assets/resolve/{identifier}` + - `POST /api/admin/assets/{identifier}/refresh`(保持既有契约) + - `GET /api/admin/audit/integrations`(复用 `resource_type`、`resource_key`、`correlation_id` 查询参数) +- Out of scope: + - 全局审计外部集成页面自身的开发或改造 + - 新增第二个同步或刷新按钮 + - `rate_limited` 的自动退避倒计时展示 diff --git a/openspec/changes/update-asset-sync-status-trail/specs/asset-information/spec.md b/openspec/changes/update-asset-sync-status-trail/specs/asset-information/spec.md new file mode 100644 index 0000000..967d5a7 --- /dev/null +++ b/openspec/changes/update-asset-sync-status-trail/specs/asset-information/spec.md @@ -0,0 +1,72 @@ +## ADDED Requirements + +### Requirement: Asset Polling Status Display + +The asset information page SHALL display the current polling status for both card and device assets from the `polling` object returned by `GET /api/admin/assets/resolve/{identifier}`. + +#### Scenario: Display complete polling status for a card asset + +- **GIVEN** 用户正在查看卡资产信息页 +- **WHEN** 资产解析响应包含 `polling.enabled`、`activity_level`、`last_activity_at`、`last_activity_scene` 和 `next_poll_at` +- **THEN** 页面 MUST 展示轮询是否启用、活跃级别、最后活跃时间、最后活跃场景和下次轮询时间 +- **AND** 时间字段 MUST 使用资产信息页统一的时间格式 + +#### Scenario: Display complete polling status for a device asset + +- **GIVEN** 用户正在查看设备资产信息页 +- **WHEN** 资产解析响应包含 `polling` 对象 +- **THEN** 页面 MUST 展示轮询是否启用、活跃级别、最后活跃时间、最后活跃场景和下次轮询时间 + +#### Scenario: Display placeholders for unavailable polling values + +- **GIVEN** 用户正在查看卡或设备资产信息页 +- **WHEN** `last_activity_at`、`last_activity_scene` 或 `next_poll_at` 为 `null`、空字符串或未返回 +- **THEN** 页面 MUST 保留对应字段标签 +- **AND** `next_poll_at` MUST 显示 `-` +- **AND** 其他不可用字段 MUST 显示稳定占位内容 + +### Requirement: Asset Manual Refresh Remains the Only Refresh Entry + +The asset information page SHALL preserve the existing manual refresh button and interface contract as the only asset refresh entry. + +#### Scenario: Preserve existing manual refresh behavior + +- **WHEN** 用户在资产信息页发起手动刷新 +- **THEN** 系统 MUST 调用现有手动刷新接口 +- **AND** 页面 MUST NOT 新增第二个同步或刷新按钮 + +#### Scenario: Show rate limited result without countdown + +- **WHEN** 手动刷新请求返回 `rate_limited` 或等价限流失败结果 +- **THEN** 系统 MUST 仅展示本次请求失败信息 +- **AND** 系统 MUST NOT 展示自动退避倒计时 + +### Requirement: Asset Sync Trail Navigation + +The asset information page SHALL provide a `查看同步轨迹` entry that navigates to the global audit integration page with filters for the current asset. The target query SHALL support `resource_type`, `resource_key`, and `correlation_id` so the audit page can display the immediate, 3-minute, and 5-minute consecutive sync results for the asset. + +#### Scenario: Navigate to filtered sync trail from a card asset + +- **GIVEN** 用户正在查看卡资产信息页 +- **WHEN** 用户点击“查看同步轨迹” +- **THEN** 系统 MUST 跳转到全局审计外部集成页 +- **AND** 跳转参数 MUST 带入当前卡资产对应的 `resource_type`、`resource_key` 或 `correlation_id` 筛选信息 + +#### Scenario: Navigate to filtered sync trail from a device asset + +- **GIVEN** 用户正在查看设备资产信息页 +- **WHEN** 用户点击“查看同步轨迹” +- **THEN** 系统 MUST 跳转到全局审计外部集成页 +- **AND** 跳转参数 MUST 带入当前设备资产对应的 `resource_type`、`resource_key` 或 `correlation_id` 筛选信息 + +#### Scenario: Sync trail includes consecutive polling results + +- **GIVEN** 用户已通过资产信息页进入带筛选的同步轨迹 +- **WHEN** 全局审计外部集成页查询该资产轨迹 +- **THEN** 查询结果 MUST 支持按立即、3分钟、5分钟连续同步结果展示 + +#### Scenario: Audit integration page implementation is out of scope + +- **WHEN** 本次变更落地 +- **THEN** 资产信息页的同步轨迹跳转入口 MUST 可用 +- **AND** 全局审计外部集成页面自身 MUST NOT 被要求在本提案中改造 diff --git a/openspec/changes/update-asset-sync-status-trail/tasks.md b/openspec/changes/update-asset-sync-status-trail/tasks.md new file mode 100644 index 0000000..6713c63 --- /dev/null +++ b/openspec/changes/update-asset-sync-status-trail/tasks.md @@ -0,0 +1,24 @@ +## 1. API Contract + +- [x] 1.1 更新资产解析响应类型,支持 `polling.enabled`、`activity_level`、`last_activity_at`、`last_activity_scene`、`next_poll_at`。 +- [x] 1.2 保持现有资产手动刷新接口与调用方式不变,不新增同步接口或按钮。 +- [x] 1.3 定义同步轨迹跳转参数,复用 `resource_type`、`resource_key`、`correlation_id` 查询契约。 + +## 2. Asset Sync Status UI + +- [x] 2.1 在卡和设备资产信息页新增同步状态区域,展示轮询是否启用、活跃级别、最后活跃时间、最后活跃场景和下次轮询时间。 +- [x] 2.2 为空的 `last_activity_at`、`last_activity_scene`、`next_poll_at` 提供稳定占位;无下次轮询时间显示 `-`。 +- [x] 2.3 保留现有手动刷新按钮;`rate_limited` 时仅展示本次失败结果,不显示自动退避倒计时。 + +## 3. Sync Trail Navigation + +- [x] 3.1 增加受 `asset_info:view_sync_trail` 按钮权限控制的“查看同步轨迹”入口,跳转全局审计外部集成页并带入当前资产筛选。 +- [ ] 3.2 确保跳转筛选可支持立即、3分钟、5分钟连续同步结果的轨迹查询(当前仓库未提供全局审计外部集成页,待目标页接入后联调)。 +- [ ] 3.3 不改造全局审计外部集成页面本身。 + +## 4. Verification + +- [ ] 4.1 验证卡和设备均可展示完整、部分为空和全部为空的同步状态。 +- [x] 4.2 验证保留单一手动刷新入口,且 `rate_limited` 不展示倒计时。 +- [ ] 4.3 验证“查看同步轨迹”跳转携带当前资产筛选并能查看连续同步结果(待全局审计外部集成页提供后联调)。 +- [x] 4.4 运行相关前端校验,并执行 `openspec validate update-asset-sync-status-trail --strict`。 diff --git a/src/api/modules/asset.ts b/src/api/modules/asset.ts index 540e15d..d60d115 100644 --- a/src/api/modules/asset.ts +++ b/src/api/modules/asset.ts @@ -34,14 +34,13 @@ import type { } from '@/types/api' const runRateLimitedAssetAction = async ( - action: AssetRateLimitedAction, + action: Exclude, identifier: string, request: () => Promise ): Promise => { assertAssetActionAllowed(action, identifier) markAssetActionCalled(action, identifier) - const response = await request() - return response + return request() } export class AssetService extends BaseService { @@ -84,16 +83,13 @@ export class AssetService extends BaseService { /** * 主动调网关拉取最新数据后返回 * POST /api/admin/assets/:identifier/refresh - * 前端按资产标识限制 5 分钟内只能调用一次 * @param identifier 资产标识符(ICCID 或 VirtualNo) */ static refreshAsset(identifier: string): Promise> { - return runRateLimitedAssetAction('refresh', identifier, () => - this.post>( - `/api/admin/assets/${identifier}/refresh`, - {}, - { timeout: 60000 } - ) + return this.post>( + `/api/admin/assets/${identifier}/refresh`, + {}, + { timeout: 60000 } ) } diff --git a/src/types/api/asset.ts b/src/types/api/asset.ts index 304092a..fbf800a 100644 --- a/src/types/api/asset.ts +++ b/src/types/api/asset.ts @@ -46,6 +46,15 @@ export interface AssetResolveParams { include_usage_summary?: boolean // 是否返回当前世代流量汇总字段 } +// 资产轮询状态 +export interface AssetPollingStatus { + enabled: boolean // 是否启用轮询 + activity_level: string // 活跃级别 + last_activity_at?: string | null // 最后活跃时间 + last_activity_scene?: string | null // 最后活跃场景 + next_poll_at?: string | null // 下次轮询时间 +} + /** * 资产解析/详情响应 * 对应接口:GET /api/admin/assets/resolve/:identifier @@ -80,6 +89,7 @@ export interface AssetResolveResponse { updated_at: string // 更新时间 accumulated_recharge?: number // 累计充值金额(分) first_commission_paid?: boolean // 一次性佣金是否已发放 + polling?: AssetPollingStatus | null // 资产轮询状态 // ===== 卡专属字段 (asset_type === 'card' 时) ===== iccid?: string // ICCID diff --git a/src/views/asset-management/asset-information/components/AssetSearchCard.vue b/src/views/asset-management/asset-information/components/AssetSearchCard.vue index fb3f642..f504145 100644 --- a/src/views/asset-management/asset-information/components/AssetSearchCard.vue +++ b/src/views/asset-management/asset-information/components/AssetSearchCard.vue @@ -40,7 +40,7 @@ :icon="Refresh" :disabled="refreshDisabled" > - {{ refreshButtonText }} + 同步 @@ -48,7 +48,7 @@