This commit is contained in:
@@ -23,7 +23,7 @@
|
|||||||
- Decision: 平台资源入口优先使用业务响应中的内部稳定 ID。只有调用上下文没有资源 ID、但明确持有 `iot_card/device/shop/order/refund` 的业务 Key 时,才进入精确资源搜索。
|
- Decision: 平台资源入口优先使用业务响应中的内部稳定 ID。只有调用上下文没有资源 ID、但明确持有 `iot_card/device/shop/order/refund` 的业务 Key 时,才进入精确资源搜索。
|
||||||
- Decision: 精确资源搜索不展示 `resource_id` 或身份快照中的内部关联 ID。候选项只展示按资源类型白名单选择的业务字段;选中后在内部原样使用返回的 `resource_type/resource_id`。
|
- Decision: 精确资源搜索不展示 `resource_id` 或身份快照中的内部关联 ID。候选项只展示按资源类型白名单选择的业务字段;选中后在内部原样使用返回的 `resource_type/resource_id`。
|
||||||
- Decision: 代理使用 ICCID、VirtualNo、分配单号、换货单号、店铺编号或企业编号;企业只使用当前授权卡的 ICCID 或设备 VirtualNo。主体身份和范围不作为前端参数。
|
- Decision: 代理使用 ICCID、VirtualNo、分配单号、换货单号、店铺编号或企业编号;企业只使用当前授权卡的 ICCID 或设备 VirtualNo。主体身份和范围不作为前端参数。
|
||||||
- Decision: `investigation_refs` 是调查节点续查的权威来源。字段缺失、目标与当前详情相同或 `fidelity=false` 时隐藏对应操作。
|
- Decision: `investigation_refs` 是来源详情续查的权威来源。字段缺失、目标与当前详情相同或 `fidelity=false` 时隐藏对应操作;共享调查抽屉内部保持只读,不重复嵌套一组调查按钮。
|
||||||
- Decision: 请求和业务链路 ID 默认不展示原值;仅在明确的超级管理员开发调查上下文中允许复制真实 request ID,且不得由前端生成。
|
- Decision: 请求和业务链路 ID 默认不展示原值;仅在明确的超级管理员开发调查上下文中允许复制真实 request ID,且不得由前端生成。
|
||||||
- Decision: 资金目标只提交一个已知稳定条件,其他订单、支付、退款、充值和钱包关联由服务端解析;金额始终按分保存在应用数据中。
|
- Decision: 资金目标只提交一个已知稳定条件,其他订单、支付、退款、充值和钱包关联由服务端解析;金额始终按分保存在应用数据中。
|
||||||
- Decision: `401/403`、授权撤销或主体资源不存在时显示中性“活动不可用”,不回退平台接口、精确资源搜索或旧 operation log。
|
- Decision: `401/403`、授权撤销或主体资源不存在时显示中性“活动不可用”,不回退平台接口、精确资源搜索或旧 operation log。
|
||||||
|
|||||||
@@ -94,13 +94,19 @@ The frontend SHALL implement the documented row-level navigation matrix for asse
|
|||||||
|
|
||||||
### Requirement: Investigation Reference Continuation
|
### Requirement: Investigation Reference Continuation
|
||||||
|
|
||||||
The frontend SHALL treat `investigation_refs` and documented Integration references as the authoritative sources for continuing from audit, risk, finance and link nodes. It SHALL expose only actions whose complete required reference is present and reliable.
|
The frontend SHALL treat `investigation_refs` and documented Integration references on supported source details as the authoritative sources for continuing an investigation. It SHALL expose only actions whose complete required reference is present and reliable. Shared investigation drawers SHALL remain read-only and SHALL NOT repeat nested investigation action groups inside their result nodes.
|
||||||
|
|
||||||
#### Scenario: Continue from a complete reference
|
#### Scenario: Continue from a complete reference
|
||||||
|
|
||||||
- **WHEN** a node contains a complete actor, resource, request, correlation or Integration reference
|
- **WHEN** a supported source detail contains a complete actor, resource, request, correlation or Integration reference
|
||||||
- **THEN** the frontend MUST pass its type and identifier unchanged to the matching shared investigation surface
|
- **THEN** the frontend MUST pass its type and identifier unchanged to the matching shared investigation surface
|
||||||
|
|
||||||
|
#### Scenario: Display a result inside the shared drawer
|
||||||
|
|
||||||
|
- **WHEN** a shared investigation drawer renders an event, resource, link, finance or Integration result
|
||||||
|
- **THEN** it MUST present that result as read-only evidence without nested investigation action buttons
|
||||||
|
- **AND** it MUST preserve documented source and reference-only boundaries in the displayed facts
|
||||||
|
|
||||||
#### Scenario: Suppress unavailable or self-referential navigation
|
#### Scenario: Suppress unavailable or self-referential navigation
|
||||||
|
|
||||||
- **WHEN** a reference is incomplete, its fidelity is false, or its event target is the detail already being displayed
|
- **WHEN** a reference is incomplete, its fidelity is false, or its event target is the detail already being displayed
|
||||||
|
|||||||
@@ -1,44 +1,44 @@
|
|||||||
## 1. Contract Audit and Shared Navigation
|
## 1. Contract Audit and Shared Navigation
|
||||||
|
|
||||||
- [ ] 1.1 建立文档导航矩阵与当前页面入口的差异清单,覆盖资产组织、账号交易、资金和调查节点。
|
- [x] 1.1 建立文档导航矩阵与当前页面入口的差异清单,覆盖资产组织、账号交易、资金和调查节点。
|
||||||
- [ ] 1.2 完善共享调查目标判别联合类型,覆盖事件、操作者、资源、请求、业务链路、资金、Integration、代理和企业活动。
|
- [x] 1.2 完善共享调查目标判别联合类型,覆盖事件、操作者、资源、请求、业务链路、资金、Integration、代理和企业活动。
|
||||||
- [ ] 1.3 将共享导航控制器改为唯一弹窗/抽屉打开入口,并处理目标切换、请求竞态、分页和关闭清理。
|
- [x] 1.3 将共享导航控制器改为唯一弹窗/抽屉打开入口,并处理目标切换、请求竞态、分页和关闭清理。
|
||||||
- [ ] 1.4 全局搜索并移除旧时间线路由、路由别名、页面和任意 URL 式审计跳转。
|
- [x] 1.4 全局搜索并移除旧时间线路由、路由别名、页面和任意 URL 式审计跳转。
|
||||||
|
|
||||||
## 2. Stable Reference and Search Rules
|
## 2. Stable Reference and Search Rules
|
||||||
|
|
||||||
- [ ] 2.1 实现平台内部 ID、主体业务 identifier、调查引用、资源 Key 搜索和隐藏入口的优先级解析。
|
- [x] 2.1 实现平台内部 ID、主体业务 identifier、调查引用、资源 Key 搜索和隐藏入口的优先级解析。
|
||||||
- [ ] 2.2 收紧精确资源搜索,仅允许 `iot_card/device/shop/order/refund` 和文档规定的精确业务 Key。
|
- [x] 2.2 收紧精确资源搜索,仅允许 `iot_card/device/shop/order/refund` 和文档规定的精确业务 Key。
|
||||||
- [ ] 2.3 精确资源结果只展示资源类型白名单业务字段,隐藏 `resource_id` 及快照内部关联 ID,并原样使用返回目标打开资源时间线。
|
- [x] 2.3 精确资源结果只展示资源类型白名单业务字段,隐藏 `resource_id` 及快照内部关联 ID,并原样使用返回目标打开资源时间线。
|
||||||
- [ ] 2.4 对零命中、多命中、历史快照和缺少稳定资源 ID 分别提供不推断的降级状态。
|
- [x] 2.4 对零命中、多命中、历史快照和缺少稳定资源 ID 分别提供不推断的降级状态。
|
||||||
- [ ] 2.5 对空引用、同目标引用和 `fidelity=false` 隐藏对应调查操作。
|
- [x] 2.5 对空引用、同目标引用和 `fidelity=false` 隐藏对应调查操作。
|
||||||
|
|
||||||
## 3. Asset and Organization Entry Matrix
|
## 3. Asset and Organization Entry Matrix
|
||||||
|
|
||||||
- [ ] 3.1 对齐卡、设备、设备卡槽和统一资产详情的平台/代理/企业入口与稳定字段映射。
|
- [x] 3.1 对齐卡、设备、设备卡槽和统一资产详情的平台/代理/企业入口与稳定字段映射。
|
||||||
- [ ] 3.2 对齐资产分配、换货、店铺和企业列表/详情入口及角色范围。
|
- [x] 3.2 对齐资产分配、换货、店铺和企业列表/详情入口及角色范围。
|
||||||
- [ ] 3.3 对齐企业卡和企业设备列表入口,确保企业只调用主体活动接口且不使用路由企业 ID 证明授权。
|
- [x] 3.3 对齐企业卡和企业设备列表入口,确保企业只调用主体活动接口且不使用路由企业 ID 证明授权。
|
||||||
- [ ] 3.4 缺少内部 ID、ICCID、VirtualNo 或业务编号时隐藏对应入口,不回退其他视角。
|
- [x] 3.4 缺少内部 ID、ICCID、VirtualNo 或业务编号时隐藏对应入口,不回退其他视角。
|
||||||
|
|
||||||
## 4. Account, Transaction, and Finance Entry Matrix
|
## 4. Account, Transaction, and Finance Entry Matrix
|
||||||
|
|
||||||
- [ ] 4.1 接入账号、店铺、企业、订单和退款的资源审计入口。
|
- [x] 4.1 接入账号、店铺、企业、订单和退款的资源审计入口。
|
||||||
- [ ] 4.2 接入订单、退款、代理充值、资产钱包、店铺资金概况和钱包流水的资金链路入口。
|
- [x] 4.2 接入订单、退款、代理充值、资产钱包、店铺资金概况和钱包流水的资金链路入口。
|
||||||
- [ ] 4.3 对审批实例、钱包资源和流水资产引用实行独立字段完整性判断,缺少一组字段不影响其他有效入口。
|
- [x] 4.3 对审批实例、钱包资源和流水资产引用实行独立字段完整性判断,缺少一组字段不影响其他有效入口。
|
||||||
- [ ] 4.4 资金查询仅传入当前业务返回的稳定条件,不由前端补齐 Payment、Refund、钱包或第三方流水关联。
|
- [x] 4.4 资金查询仅传入当前业务返回的稳定条件,不由前端补齐 Payment、Refund、钱包或第三方流水关联。
|
||||||
|
|
||||||
## 5. Investigation Node Continuation
|
## 5. Investigation Node Continuation
|
||||||
|
|
||||||
- [ ] 5.1 从风险、事件、资源、链路、资金和 Integration 节点按 `investigation_refs` 打开共享调查抽屉。
|
- [x] 5.1 从支持的来源详情按 `investigation_refs` 打开共享调查抽屉,调查弹窗内部保持只读且不嵌套无意义按钮。
|
||||||
- [ ] 5.2 支持事件、操作者、资源、请求、业务链路和外部交互的非空引用,并抑制跳回当前详情的无意义入口。
|
- [x] 5.2 支持事件、操作者、资源、请求、业务链路和外部交互的非空引用,并抑制跳回当前详情的无意义入口。
|
||||||
- [ ] 5.3 请求/业务链路节点保留 `record_source` 和 `reference_only` 事实边界,不把投递成功展示为业务成功。
|
- [x] 5.3 请求/业务链路节点保留 `record_source` 和 `reference_only` 事实边界,不把投递成功展示为业务成功。
|
||||||
- [ ] 5.4 通知 Integration 目标继续使用受控 target 解析,不接受任意 URL 或数据库主键替代稳定 Integration ID。
|
- [x] 5.4 通知 Integration 目标继续使用受控 target 解析,不接受任意 URL 或数据库主键替代稳定 Integration ID。
|
||||||
|
|
||||||
## 6. Authorization, Presentation, and Verification
|
## 6. Authorization, Presentation, and Verification
|
||||||
|
|
||||||
- [ ] 6.1 按平台、代理、企业及按钮权限验证所有入口可见性和接口选择。
|
- [x] 6.1 按平台、代理、企业及按钮权限验证所有入口可见性和接口选择。
|
||||||
- [ ] 6.2 验证抽屉 append-to-body、遮罩、底层滚动锁定、关闭销毁、分页和移动端布局。
|
- [x] 6.2 验证抽屉 append-to-body、遮罩、底层滚动锁定、关闭销毁、分页和移动端布局。
|
||||||
- [ ] 6.3 为目标解析、参数原样传递、空值隐藏、同目标抑制、`fidelity=false` 和禁止推断增加单元测试。
|
- [x] 6.3 为目标解析、参数原样传递、空值隐藏、同目标抑制、`fidelity=false` 和禁止推断增加单元测试。
|
||||||
- [ ] 6.4 为文档中资产组织和账号资金导航矩阵增加表驱动测试。
|
- [x] 6.4 为文档中资产组织和账号资金导航矩阵增加表驱动测试。
|
||||||
- [ ] 6.5 验证 `400/401/403/404/500`、授权撤销、历史快照、归档窗口和过期异步响应处理。
|
- [ ] 6.5 验证 `400/401/403/404/500`、授权撤销、历史快照、归档窗口和过期异步响应处理。
|
||||||
- [ ] 6.6 回归现有审计中心、风险中心、外部交互、通知导航和旧 operation log,确认只读边界未被破坏。
|
- [ ] 6.6 回归现有审计中心、风险中心、外部交互、通知导航和旧 operation log,确认只读边界未被破坏。
|
||||||
|
|||||||
@@ -119,3 +119,18 @@ test('finance targets preserve only complete backend-provided conditions', async
|
|||||||
await cleanup()
|
await cleanup()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('explicit investigation references suppress empty, unreliable and self targets', async () => {
|
||||||
|
const { module, cleanup } = await importAuditNavigation()
|
||||||
|
const { resolveInvestigationReference } = module
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert.equal(resolveInvestigationReference(' request-1 '), 'request-1')
|
||||||
|
assert.equal(resolveInvestigationReference(' '), null)
|
||||||
|
assert.equal(resolveInvestigationReference('event-1', { currentId: 'event-1' }), null)
|
||||||
|
assert.equal(resolveInvestigationReference('event-2', { currentId: 'event-1' }), 'event-2')
|
||||||
|
assert.equal(resolveInvestigationReference('correlation-1', { fidelity: false }), null)
|
||||||
|
} finally {
|
||||||
|
await cleanup()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@@ -20,19 +20,14 @@
|
|||||||
>
|
>
|
||||||
精确注册资源
|
精确注册资源
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<ElButton v-if="requestId" link type="primary" @click="openTimeline('request', requestId)">
|
||||||
v-if="refs?.request_id"
|
|
||||||
link
|
|
||||||
type="primary"
|
|
||||||
@click="openTimeline('request', refs.request_id)"
|
|
||||||
>
|
|
||||||
请求链路
|
请求链路
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
v-if="refs?.correlation_id"
|
v-if="correlationId"
|
||||||
link
|
link
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="openTimeline('correlation', refs.correlation_id)"
|
@click="openTimeline('correlation', correlationId)"
|
||||||
>
|
>
|
||||||
业务关联链路
|
业务关联链路
|
||||||
</ElButton>
|
</ElButton>
|
||||||
@@ -57,6 +52,7 @@
|
|||||||
AuditSearchResourceType
|
AuditSearchResourceType
|
||||||
} from '@/types/api'
|
} from '@/types/api'
|
||||||
import { openAuditInvestigation, openAuditResourceSearch } from './investigationController'
|
import { openAuditInvestigation, openAuditResourceSearch } from './investigationController'
|
||||||
|
import { resolveInvestigationReference } from '@/utils/business/auditNavigation'
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@@ -73,10 +69,16 @@
|
|||||||
'order',
|
'order',
|
||||||
'refund'
|
'refund'
|
||||||
])
|
])
|
||||||
const eventId = computed(() => {
|
const eventId = computed(
|
||||||
const id = props.refs?.event_id?.trim()
|
() =>
|
||||||
return id && id !== props.currentEventId ? id : ''
|
resolveInvestigationReference(props.refs?.event_id, {
|
||||||
})
|
currentId: props.currentEventId
|
||||||
|
}) || ''
|
||||||
|
)
|
||||||
|
const requestId = computed(() => resolveInvestigationReference(props.refs?.request_id) || '')
|
||||||
|
const correlationId = computed(
|
||||||
|
() => resolveInvestigationReference(props.refs?.correlation_id) || ''
|
||||||
|
)
|
||||||
const actorRef = computed(() => {
|
const actorRef = computed(() => {
|
||||||
const actor = props.refs?.actor_ref
|
const actor = props.refs?.actor_ref
|
||||||
return actor?.kind && actor.id ? actor : null
|
return actor?.kind && actor.id ? actor : null
|
||||||
@@ -107,8 +109,8 @@
|
|||||||
Boolean(
|
Boolean(
|
||||||
eventId.value ||
|
eventId.value ||
|
||||||
actorRef.value ||
|
actorRef.value ||
|
||||||
props.refs?.request_id ||
|
requestId.value ||
|
||||||
props.refs?.correlation_id ||
|
correlationId.value ||
|
||||||
stableResourceRefs.value.length ||
|
stableResourceRefs.value.length ||
|
||||||
searchableResourceRefs.value.length ||
|
searchableResourceRefs.value.length ||
|
||||||
stableIntegrationRefs.value.length
|
stableIntegrationRefs.value.length
|
||||||
|
|||||||
@@ -8,6 +8,22 @@ interface AuditResourceRouteOptions {
|
|||||||
businessIdentifier?: string | null
|
businessIdentifier?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AuditReferenceOptions {
|
||||||
|
currentId?: string | null
|
||||||
|
fidelity?: boolean | null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 仅保留非空、可靠且不会回到当前目标的显式调查引用。 */
|
||||||
|
export function resolveInvestigationReference(
|
||||||
|
value?: string | null,
|
||||||
|
options: AuditReferenceOptions = {}
|
||||||
|
): string | null {
|
||||||
|
if (options.fidelity === false) return null
|
||||||
|
const normalizedValue = value?.trim()
|
||||||
|
if (!normalizedValue || normalizedValue === options.currentId?.trim()) return null
|
||||||
|
return normalizedValue
|
||||||
|
}
|
||||||
|
|
||||||
/** 根据当前主体选择平台内部 ID 或主体安全业务标识。 */
|
/** 根据当前主体选择平台内部 ID 或主体安全业务标识。 */
|
||||||
export function resolveAuditResourceTarget(
|
export function resolveAuditResourceTarget(
|
||||||
options: AuditResourceRouteOptions
|
options: AuditResourceRouteOptions
|
||||||
|
|||||||
@@ -94,30 +94,28 @@
|
|||||||
<template #header>调查入口</template>
|
<template #header>调查入口</template>
|
||||||
<div v-if="hasAssociations" class="link-actions">
|
<div v-if="hasAssociations" class="link-actions">
|
||||||
<ElButton
|
<ElButton
|
||||||
v-if="detail.linkage.request_id"
|
v-if="requestReference"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
@click="
|
@click="openAuditInvestigation({ mode: 'request', id: requestReference })"
|
||||||
openAuditInvestigation({ mode: 'request', id: detail.linkage.request_id })
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
请求链路
|
请求链路
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
v-if="detail.linkage.correlation_id"
|
v-if="correlationReference"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
@click="
|
@click="
|
||||||
openAuditInvestigation({
|
openAuditInvestigation({
|
||||||
mode: 'correlation',
|
mode: 'correlation',
|
||||||
id: detail.linkage.correlation_id
|
id: correlationReference
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
业务关联链路
|
业务关联链路
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
v-if="detail.resource?.type && detail.resource?.id"
|
v-if="detail.resource?.type && resourceReference"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
@click="openResourceTimeline"
|
@click="openResourceTimeline"
|
||||||
@@ -258,6 +256,7 @@
|
|||||||
} from '@/utils/business/audit'
|
} from '@/utils/business/audit'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import { openAuditInvestigation } from '@/components/business/audit/investigationController'
|
import { openAuditInvestigation } from '@/components/business/audit/investigationController'
|
||||||
|
import { resolveInvestigationReference } from '@/utils/business/auditNavigation'
|
||||||
|
|
||||||
defineOptions({ name: 'AuditIntegrationDetail' })
|
defineOptions({ name: 'AuditIntegrationDetail' })
|
||||||
|
|
||||||
@@ -272,11 +271,24 @@
|
|||||||
const hasResource = computed(() =>
|
const hasResource = computed(() =>
|
||||||
Boolean(detail.value?.resource?.type || detail.value?.resource?.key)
|
Boolean(detail.value?.resource?.type || detail.value?.resource?.key)
|
||||||
)
|
)
|
||||||
|
const requestReference = computed(() =>
|
||||||
|
resolveInvestigationReference(detail.value?.linkage.request_id)
|
||||||
|
)
|
||||||
|
const correlationReference = computed(() =>
|
||||||
|
resolveInvestigationReference(detail.value?.linkage.correlation_id, {
|
||||||
|
fidelity: detail.value?.fidelity.correlation_available
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const resourceReference = computed(() =>
|
||||||
|
resolveInvestigationReference(detail.value?.resource?.id, {
|
||||||
|
fidelity: detail.value?.fidelity.resource_id_available
|
||||||
|
})
|
||||||
|
)
|
||||||
const hasAssociations = computed(() =>
|
const hasAssociations = computed(() =>
|
||||||
Boolean(
|
Boolean(
|
||||||
detail.value?.linkage.request_id ||
|
requestReference.value ||
|
||||||
detail.value?.linkage.correlation_id ||
|
correlationReference.value ||
|
||||||
(detail.value?.resource?.type && detail.value?.resource?.id)
|
(detail.value?.resource?.type && resourceReference.value)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
const hasJsonContent = (value?: Record<string, unknown> | null) =>
|
const hasJsonContent = (value?: Record<string, unknown> | null) =>
|
||||||
@@ -294,11 +306,11 @@
|
|||||||
integrationCategoryMeta[category as keyof typeof integrationCategoryMeta]?.type || 'info'
|
integrationCategoryMeta[category as keyof typeof integrationCategoryMeta]?.type || 'info'
|
||||||
const formatOptionalDate = (value?: string | null) => (value ? formatDateTime(value) : '-')
|
const formatOptionalDate = (value?: string | null) => (value ? formatDateTime(value) : '-')
|
||||||
const openResourceTimeline = () => {
|
const openResourceTimeline = () => {
|
||||||
if (!detail.value?.resource?.type || !detail.value.resource.id) return
|
if (!detail.value?.resource?.type || !resourceReference.value) return
|
||||||
openAuditInvestigation({
|
openAuditInvestigation({
|
||||||
mode: 'resource',
|
mode: 'resource',
|
||||||
resourceType: detail.value.resource.type,
|
resourceType: detail.value.resource.type,
|
||||||
id: detail.value.resource.id
|
id: resourceReference.value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user