This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
# Change: 收紧审计接口的主体访问边界
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
平台 `/api/admin/audit/*` 接口仅允许超级管理员和平台账号访问。当前部分业务入口未传递当前账号类型,可能让代理或企业用户构造平台资源时间线目标;平台审计页面和调查抽屉也缺少统一的前端请求拦截。
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- 平台审计中心、平台资源时间线、资金链路、请求链路、风险和 Integration 等 `/api/admin/audit/*` 调用只允许超级管理员或平台账号发起。
|
||||||
|
- 代理账号仅通过代理主体活动接口查看其支持资源;企业账号仅通过企业主体活动接口查看卡和设备资源。
|
||||||
|
- 业务入口基于当前登录账号类型选择对应权限与调查目标,账号、店铺等不受主体活动支持的资源不向代理或企业显示平台审计入口。
|
||||||
|
- 在共享调查加载层增加防御性校验,越权目标不发起网络请求并提示不可用。
|
||||||
|
- 保持审计能力严格只读:所有相关前端 API 保持 GET,不新增修改、删除、导出、恢复、重试、补偿或风险处置操作。
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Affected specs: `audit-chain-frontend-integration`
|
||||||
|
- Affected code: `src/components/business/audit/`, `src/utils/business/auditNavigation.ts`, audit routes/views, and business pages with audit-entry buttons including account, shop, asset, order, refund and wallet views.
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: 审计接口按当前主体隔离
|
||||||
|
|
||||||
|
前端 SHALL 仅允许超级管理员和平台账号调用 `/api/admin/audit/*` 平台审计接口。代理账号 MUST 仅调用其支持资源的代理主体活动接口;企业账号 MUST 仅调用卡和设备资源的企业主体活动接口。共享调查加载层 MUST 在请求前拒绝不匹配当前主体的调查目标。
|
||||||
|
|
||||||
|
#### Scenario: 代理或企业用户访问业务审计入口
|
||||||
|
|
||||||
|
- **WHEN** 代理或企业用户打开业务列表、详情或资源信息
|
||||||
|
- **THEN** 前端仅显示当前主体支持的活动入口
|
||||||
|
- **AND** 前端不请求任何 `/api/admin/audit/*` 接口
|
||||||
|
|
||||||
|
#### Scenario: 平台用户访问审计调查
|
||||||
|
|
||||||
|
- **WHEN** 超级管理员或平台账号打开平台审计、资源时间线、资金链路、请求链路、风险或 Integration 调查
|
||||||
|
- **THEN** 前端使用对应的 `/api/admin/audit/*` GET 接口
|
||||||
|
|
||||||
|
### Requirement: 审计调查仅提供只读能力
|
||||||
|
|
||||||
|
审计调查前端 SHALL 仅使用 GET 请求读取平台审计或主体活动数据,且 MUST NOT 提供修改、删除、导出、恢复、重试、补偿或风险处置操作。
|
||||||
|
|
||||||
|
#### Scenario: 用户查看审计或主体活动记录
|
||||||
|
|
||||||
|
- **WHEN** 用户查询任一审计或主体活动视图
|
||||||
|
- **THEN** 页面只展示只读数据和受控跳转
|
||||||
|
- **AND** 页面不展示或调用任何写入、导出、恢复、重试、补偿或风险处置操作
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
## 1. Shared Access Rules
|
||||||
|
|
||||||
|
- [x] 1.1 Add shared current-user access checks for platform audit and subject activity APIs.
|
||||||
|
- [x] 1.2 Guard the shared investigation loader so it does not request a platform audit API for agent or enterprise users.
|
||||||
|
|
||||||
|
## 2. Entry Points and Pages
|
||||||
|
|
||||||
|
- [x] 2.1 Update business audit entry points to pass current user type and use only supported subject activity targets for agents and enterprises.
|
||||||
|
- [x] 2.2 Hide or block platform audit routes and actions for agent and enterprise users.
|
||||||
|
- [x] 2.3 Keep finance, risk, Integration and link-timeline entries platform-only.
|
||||||
|
|
||||||
|
## 3. Read-only Verification
|
||||||
|
|
||||||
|
- [x] 3.1 Verify all audit and subject-activity methods used by the UI are GET-only.
|
||||||
|
- [x] 3.2 Verify platform, agent and enterprise users cannot trigger the wrong API family.
|
||||||
|
- [x] 3.3 Run lint and type checks for changed files.
|
||||||
16
openspec/changes/enrich-actor-timeline-detail/proposal.md
Normal file
16
openspec/changes/enrich-actor-timeline-detail/proposal.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Change: 补全操作者行为时间线事件信息
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
操作者行为时间线接口已返回完整审计事件字段,但当前调查抽屉仅展示动作、结果、摘要、操作者名称和来源,无法支持对操作者所属组织、业务范围、请求上下文、批次结果、失败原因和关联资源的有效追溯。
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- 在操作者行为时间线的每个事件节点展示接口返回的分类、风险、操作者身份与组织快照、业务范围、请求摘要、批次统计和错误摘要。
|
||||||
|
- 展示关联资源的名称、类型、关系、业务角色和主体摘要;将资源快照与前后变更数据放入按需展开的只读区域。
|
||||||
|
- 对缺失或空字段使用明确的空态,不从名称、时间或相邻事件推断数据;保留分页、排序与只读边界。
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Affected specs: `audit-chain-frontend-integration`
|
||||||
|
- Affected code: `src/components/business/audit/AuditInvestigationDrawer.vue`
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: 操作者行为时间线完整事件展示
|
||||||
|
|
||||||
|
前端 SHALL 在操作者行为时间线中展示接口返回的事件分类、动作、结果、风险、摘要、操作者身份及所属组织快照、业务范围、批次统计、错误摘要和关联资源;分类、来源、操作者和业务范围 MUST 在同一行展示。请求摘要与扩展元数据 MUST 仅向超级管理员展示。资源身份快照、主体安全数据及变更前后数据 MUST 以按需展开的只读区域展示。
|
||||||
|
|
||||||
|
#### Scenario: 查看包含完整审计字段的操作者事件
|
||||||
|
|
||||||
|
- **WHEN** `/api/admin/audit/actors/{kind}/{id}/events` 返回包含操作者、范围、请求、批次、错误和资源字段的事件
|
||||||
|
- **THEN** 前端在对应时间线节点中展示这些字段的业务可读值
|
||||||
|
- **AND** 将资源快照与变更数据放入可折叠的只读区域
|
||||||
|
|
||||||
|
#### Scenario: 非超级管理员查看事件
|
||||||
|
|
||||||
|
- **WHEN** 非超级管理员打开操作者行为时间线
|
||||||
|
- **THEN** 前端不展示请求上下文和扩展元数据
|
||||||
|
- **AND** 其余允许展示的事件字段保持只读
|
||||||
|
|
||||||
|
#### Scenario: 字段缺失或资源为空
|
||||||
|
|
||||||
|
- **WHEN** 事件中可选字段为空或没有关联资源
|
||||||
|
- **THEN** 前端显示明确空态或隐藏对应区域
|
||||||
|
- **AND** 不从名称、时间或其他事件推断或补造数据
|
||||||
|
|
||||||
|
#### Scenario: 保持只读与分页语义
|
||||||
|
|
||||||
|
- **WHEN** 用户查看或翻页操作者行为时间线
|
||||||
|
- **THEN** 前端继续使用文档规定的分页与排序结果
|
||||||
|
- **AND** 不提供修改、删除、导出、恢复、重试、补偿或风险处置操作
|
||||||
10
openspec/changes/enrich-actor-timeline-detail/tasks.md
Normal file
10
openspec/changes/enrich-actor-timeline-detail/tasks.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
## 1. Implementation
|
||||||
|
|
||||||
|
- [x] 1.1 为操作者行为时间线事件节点补充接口已返回的事件、操作者、范围、请求、批次和错误字段。
|
||||||
|
- [x] 1.2 以可折叠只读区域展示关联资源与快照/变更数据,并处理空值和无资源场景。
|
||||||
|
- [x] 1.3 保持资源、请求和关联标识只读展示,不新增跳转或写操作。
|
||||||
|
|
||||||
|
## 2. Verification
|
||||||
|
|
||||||
|
- [x] 2.1 验证接口返回字段均按文档语义显示且不改变分页和排序。
|
||||||
|
- [x] 2.2 运行格式化、类型检查和 ESLint。
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Change: 补全业务关联链路节点展示
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
业务关联链路弹窗当前每个节点仅显示标题、结果、摘要和事实来源,无法清晰查看节点编码、关联范围和资源引用。操作者行为时间线已采用结构化、按需展开的只读展示,业务关联链路需要使用相同的视觉层级,但不能假设接口返回了操作者事件专有字段。
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- 基于 `AuditLinkTimelineNode` 的实际返回字段,使用与操作者行为时间线一致的卡片、标签和基础信息单行布局展示业务关联节点。
|
||||||
|
- 仅展示节点实际返回的事实来源和稳定编码;关联链路、父审计事件、请求和资源内部 ID 不在界面显示,数据完整性信息仅超级管理员可见。
|
||||||
|
- 当节点返回资源引用时,以折叠的只读区域展示资源名称、类型和业务 Key;不推断资源快照、操作者、风险或变更数据。
|
||||||
|
- 保持业务关联链路全量查询、排序、只读和无额外跳转的现有语义。
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Affected specs: `audit-chain-frontend-integration`
|
||||||
|
- Affected code: `src/components/business/audit/AuditInvestigationDrawer.vue`
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: 业务关联链路结构化节点展示
|
||||||
|
|
||||||
|
前端 SHALL 在业务关联链路中以与操作者行为时间线一致的层级展示每个 `AuditLinkTimelineNode`:节点标题、结果、事实来源和稳定编码 MUST 使用接口实际返回值;基础信息 MUST 在同一行展示。关联链路、父审计事件、请求和资源内部 ID MUST 不在界面显示;数据完整性信息 MUST 仅向超级管理员展示。资源引用 MUST 按需在折叠的只读区域展示。
|
||||||
|
|
||||||
|
#### Scenario: 查看包含资源引用的关联链路节点
|
||||||
|
|
||||||
|
- **WHEN** 业务关联链路返回含资源引用和关联标识的节点
|
||||||
|
- **THEN** 前端展示节点基础信息并提供只读、可折叠的资源引用区域
|
||||||
|
- **AND** 不推断操作者、分类、风险、资源快照或变更数据
|
||||||
|
|
||||||
|
#### Scenario: 非超级管理员查看关联链路
|
||||||
|
|
||||||
|
- **WHEN** 非超级管理员打开业务关联链路弹窗
|
||||||
|
- **THEN** 前端不展示请求标识和数据完整性信息
|
||||||
|
- **AND** 继续展示其余允许的节点字段
|
||||||
|
|
||||||
|
#### Scenario: 保持只读链路语义
|
||||||
|
|
||||||
|
- **WHEN** 用户查看业务关联链路
|
||||||
|
- **THEN** 前端不提供修改、删除、导出、恢复、重试、补偿或风险处置操作
|
||||||
10
openspec/changes/enrich-correlation-timeline-detail/tasks.md
Normal file
10
openspec/changes/enrich-correlation-timeline-detail/tasks.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
## 1. Implementation
|
||||||
|
|
||||||
|
- [x] 1.1 为业务关联链路节点增加与操作者时间线一致的基础信息单行布局。
|
||||||
|
- [x] 1.2 隐藏关联链路中的所有 ID,并按超级管理员边界展示数据完整性信息。
|
||||||
|
- [x] 1.3 对接口返回的资源引用增加折叠的只读展示,并处理空字段。
|
||||||
|
|
||||||
|
## 2. Verification
|
||||||
|
|
||||||
|
- [x] 2.1 验证只使用 `AuditLinkTimelineNode` 已返回字段,不补造操作者事件字段。
|
||||||
|
- [x] 2.2 运行格式化、ESLint、类型检查和 OpenSpec 严格校验。
|
||||||
17
openspec/changes/enrich-finance-timeline-detail/proposal.md
Normal file
17
openspec/changes/enrich-finance-timeline-detail/proposal.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Change: 补全财务审计时间线节点展示
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
资金调查时间线接口已返回金额、前后余额、金额权威来源、钱包引用和安全结构化事实,但当前弹窗只展示标题、结果、金额、编码和来源,无法支持资金事实核对。
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- 按“查询资金调查时间线”中的 `AuditFinanceTimelineNode` 响应结构,将财务审计节点改为与既有时间线一致的结构化卡片与折叠信息区。
|
||||||
|
- 显示金额、变更前后余额、币种、事实来源、稳定编码和钱包类型;金额均按分转换为展示金额。
|
||||||
|
- 金额权威来源与安全结构化事实使用按需展开的只读区域;不显示内部节点、店铺或钱包 ID,不新增跳转或写操作。
|
||||||
|
- 保留现有稳定条件查询、分页、排序和权威金额字段语义。
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Affected specs: `audit-chain-frontend-integration`
|
||||||
|
- Affected code: `src/components/business/audit/AuditInvestigationDrawer.vue`
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: 财务审计时间线完整资金事实展示
|
||||||
|
|
||||||
|
前端 SHALL 在财务审计时间线中按 `AuditFinanceTimelineNode` 展示标题、结果、金额、变更前后余额、币种、事实来源、稳定编码和钱包类型。金额与余额 MUST 按分转换为展示金额。金额权威来源和安全结构化事实 MUST 使用按需展开的只读区域;内部节点、店铺和钱包 ID MUST 不在界面显示。
|
||||||
|
|
||||||
|
#### Scenario: 查看带金额权威信息的资金节点
|
||||||
|
|
||||||
|
- **WHEN** 资金节点返回金额、余额、钱包和 `amount_authority`
|
||||||
|
- **THEN** 前端展示格式化后的金额与余额,并在折叠区域展示权威来源说明
|
||||||
|
- **AND** 不将非权威字段当作权威金额
|
||||||
|
|
||||||
|
#### Scenario: 查看安全结构化事实
|
||||||
|
|
||||||
|
- **WHEN** 资金节点返回非空 `facts`
|
||||||
|
- **THEN** 前端提供可折叠的只读 JSON 展示
|
||||||
|
- **AND** 不显示内部 ID 或提供写操作
|
||||||
|
|
||||||
|
#### Scenario: 金额字段为空
|
||||||
|
|
||||||
|
- **WHEN** 资金节点不承载金额或余额
|
||||||
|
- **THEN** 前端隐藏相应金额项或显示中性空态
|
||||||
|
- **AND** 保持分页和排序结果不变
|
||||||
10
openspec/changes/enrich-finance-timeline-detail/tasks.md
Normal file
10
openspec/changes/enrich-finance-timeline-detail/tasks.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
## 1. Implementation
|
||||||
|
|
||||||
|
- [x] 1.1 按 `AuditFinanceTimelineNode` 补充金额、余额、币种、来源、编码和钱包类型展示。
|
||||||
|
- [x] 1.2 增加金额权威来源和安全结构化事实的折叠只读区域。
|
||||||
|
- [x] 1.3 隐藏内部 ID,处理无金额、无钱包和空结构化字段场景。
|
||||||
|
|
||||||
|
## 2. Verification
|
||||||
|
|
||||||
|
- [x] 2.1 验证金额单位按分转换,并保持 `amount_authority.authoritative` 语义。
|
||||||
|
- [x] 2.2 运行格式化、ESLint、类型检查和 OpenSpec 严格校验。
|
||||||
17
openspec/changes/enrich-request-timeline-detail/proposal.md
Normal file
17
openspec/changes/enrich-request-timeline-detail/proposal.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Change: 补全请求链路节点展示
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
请求链路弹窗当前仅呈现标题、结果、摘要和事实来源,未充分利用接口返回的稳定编码、资源引用和关联完整性信息,且与已调整的操作者、业务关联链路节点样式不一致。
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- 基于 `查询请求关联时间线_简化版.md` 的 `AuditLinkTimelineNode`,将请求链路节点改为与既有时间线一致的结构化卡片与单行基础信息布局。
|
||||||
|
- 节点仅展示标题、结果、事实来源和稳定编码;不显示请求、业务关联、父审计事件、节点或资源内部 ID。
|
||||||
|
- 资源引用以折叠只读区域展示名称、类型和业务 Key;关联完整性信息仅超级管理员可见。
|
||||||
|
- 保持接口全量排序和只读语义,不新增跳转或写操作。
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Affected specs: `audit-chain-frontend-integration`
|
||||||
|
- Affected code: `src/components/business/audit/AuditInvestigationDrawer.vue`
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: 请求链路结构化节点展示
|
||||||
|
|
||||||
|
前端 SHALL 在请求链路中以与操作者和业务关联链路一致的层级展示每个 `AuditLinkTimelineNode`:节点标题、结果、事实来源和稳定编码 MUST 使用接口实际返回值,并在同一行展示基础信息。请求、业务关联、父审计事件、节点和资源内部 ID MUST 不在界面显示。资源引用 MUST 以按需展开的只读区域展示;数据完整性信息 MUST 仅向超级管理员展示。
|
||||||
|
|
||||||
|
#### Scenario: 查看含资源引用的请求链路节点
|
||||||
|
|
||||||
|
- **WHEN** 请求链路返回含资源引用和关联完整性信息的节点
|
||||||
|
- **THEN** 前端展示节点基础信息和可折叠的资源引用区域
|
||||||
|
- **AND** 不展示接口返回的任何 ID
|
||||||
|
|
||||||
|
#### Scenario: 非超级管理员查看请求链路
|
||||||
|
|
||||||
|
- **WHEN** 非超级管理员打开请求链路弹窗
|
||||||
|
- **THEN** 前端不展示数据完整性信息
|
||||||
|
- **AND** 继续展示其余允许的节点字段
|
||||||
|
|
||||||
|
#### Scenario: 保持请求链路只读语义
|
||||||
|
|
||||||
|
- **WHEN** 用户查看请求链路
|
||||||
|
- **THEN** 前端不提供修改、删除、导出、恢复、重试、补偿或风险处置操作
|
||||||
10
openspec/changes/enrich-request-timeline-detail/tasks.md
Normal file
10
openspec/changes/enrich-request-timeline-detail/tasks.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
## 1. Implementation
|
||||||
|
|
||||||
|
- [x] 1.1 为请求链路节点增加与既有时间线一致的基础信息布局。
|
||||||
|
- [x] 1.2 隐藏所有 ID,仅向超级管理员展示数据完整性信息。
|
||||||
|
- [x] 1.3 对请求链路返回的资源引用增加折叠只读展示并处理空字段。
|
||||||
|
|
||||||
|
## 2. Verification
|
||||||
|
|
||||||
|
- [x] 2.1 验证仅使用简化版文档已定义的 `AuditLinkTimelineNode` 字段。
|
||||||
|
- [x] 2.2 运行格式化、ESLint、类型检查和 OpenSpec 严格校验。
|
||||||
17
openspec/changes/enrich-resource-timeline-detail/proposal.md
Normal file
17
openspec/changes/enrich-resource-timeline-detail/proposal.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Change: 补全通用资源时间线事件展示
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
通用资源时间线接口返回完整 `AuditEventPage` / `AuditEventView`,但当前弹窗仅显示动作、结果、摘要、操作者和来源,未使用接口返回的分类、风险、操作者组织、业务范围、批次、错误、资源快照与变更数据。
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- 依据“查询通用资源时间线”响应结构,在资源审计时间线中复用操作者时间线的完整事件节点布局。
|
||||||
|
- 展示分类、来源、操作者、业务范围、风险、批次、错误及关联资源;基础信息同一行,资源 JSON 按需折叠。
|
||||||
|
- 请求上下文与扩展元数据仅超级管理员可见;不新增请求筛选、跳转或任何写操作。
|
||||||
|
- 保持接口现有分页、排序和“缺少 resource_id 隐藏入口”的调用边界。
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Affected specs: `audit-chain-frontend-integration`
|
||||||
|
- Affected code: `src/components/business/audit/AuditInvestigationDrawer.vue`
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: 通用资源时间线完整事件展示
|
||||||
|
|
||||||
|
前端 SHALL 按通用资源时间线返回的 `AuditEventPage` 展示完整 `AuditEventView` 节点:分类、来源、操作者、业务范围 MUST 在同一行展示;风险、批次统计、错误信息及关联资源 MUST 使用接口返回值。资源身份快照、主体安全数据及变更前后数据 MUST 以按需展开的只读区域展示。请求上下文与扩展元数据 MUST 仅向超级管理员展示。
|
||||||
|
|
||||||
|
#### Scenario: 查看包含资源快照的资源时间线事件
|
||||||
|
|
||||||
|
- **WHEN** 资源时间线返回包含批次、错误和关联资源快照的事件
|
||||||
|
- **THEN** 前端在对应节点展示完整事件字段和折叠的资源数据
|
||||||
|
- **AND** 不推断缺失字段或增加写操作
|
||||||
|
|
||||||
|
#### Scenario: 非超级管理员查看资源时间线
|
||||||
|
|
||||||
|
- **WHEN** 非超级管理员打开资源审计时间线
|
||||||
|
- **THEN** 前端不展示请求上下文和扩展元数据
|
||||||
|
- **AND** 继续展示其余允许的事件字段
|
||||||
|
|
||||||
|
#### Scenario: 保持资源时间线分页语义
|
||||||
|
|
||||||
|
- **WHEN** 用户翻页资源审计时间线
|
||||||
|
- **THEN** 前端继续使用接口返回的页码、每页数量和稳定排序结果
|
||||||
10
openspec/changes/enrich-resource-timeline-detail/tasks.md
Normal file
10
openspec/changes/enrich-resource-timeline-detail/tasks.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
## 1. Implementation
|
||||||
|
|
||||||
|
- [x] 1.1 将资源时间线接入完整 `AuditEventView` 节点展示。
|
||||||
|
- [x] 1.2 复用分类、来源、操作者、业务范围单行布局以及批次、错误和资源快照折叠区。
|
||||||
|
- [x] 1.3 确保请求上下文和扩展元数据仅超级管理员可见。
|
||||||
|
|
||||||
|
## 2. Verification
|
||||||
|
|
||||||
|
- [x] 2.1 验证接口分页、排序、空值和资源 ID 入口边界未改变。
|
||||||
|
- [x] 2.2 运行格式化、ESLint、类型检查和 OpenSpec 严格校验。
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import { BaseService } from '../BaseService'
|
import { BaseService } from '../BaseService'
|
||||||
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
import { isPlatformAuditAccount } from '@/utils/business/auditAccess'
|
||||||
import type {
|
import type {
|
||||||
AuditActorEventQuery,
|
AuditActorEventQuery,
|
||||||
AuditActorKind,
|
AuditActorKind,
|
||||||
@@ -26,12 +28,28 @@ import type {
|
|||||||
} from '@/types/api'
|
} from '@/types/api'
|
||||||
|
|
||||||
export class AuditService extends BaseService {
|
export class AuditService extends BaseService {
|
||||||
|
private static ensurePlatformAuditAccess() {
|
||||||
|
const userStore = useUserStore()
|
||||||
|
if (!isPlatformAuditAccount(userStore.info.user_type, userStore.isSuperAdmin)) {
|
||||||
|
throw new Error('当前账号无权访问平台审计接口')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ensureSubjectActivityAccess(subject: 'agent' | 'enterprise') {
|
||||||
|
const userType = Number(useUserStore().info.user_type)
|
||||||
|
if ((subject === 'agent' && userType !== 3) || (subject === 'enterprise' && userType !== 4)) {
|
||||||
|
throw new Error('当前账号无权访问主体活动接口')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static getEvents(params?: AuditEventQuery): Promise<BaseResponse<AuditEventPage>> {
|
static getEvents(params?: AuditEventQuery): Promise<BaseResponse<AuditEventPage>> {
|
||||||
|
this.ensurePlatformAuditAccess()
|
||||||
return this.get('/api/admin/audit/events', params)
|
return this.get('/api/admin/audit/events', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
static getEventDetail(eventId: string): Promise<BaseResponse<AuditEventDetail>> {
|
static getEventDetail(eventId: string): Promise<BaseResponse<AuditEventDetail>> {
|
||||||
return this.get(`/api/admin/audit/events/${encodeURIComponent(eventId)}`)
|
this.ensurePlatformAuditAccess()
|
||||||
|
return this.get(`/api/admin/audit/events/${eventId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
static getActorEvents(
|
static getActorEvents(
|
||||||
@@ -39,6 +57,7 @@ export class AuditService extends BaseService {
|
|||||||
id: string,
|
id: string,
|
||||||
params?: AuditActorEventQuery
|
params?: AuditActorEventQuery
|
||||||
): Promise<BaseResponse<AuditEventPage>> {
|
): Promise<BaseResponse<AuditEventPage>> {
|
||||||
|
this.ensurePlatformAuditAccess()
|
||||||
return this.get(
|
return this.get(
|
||||||
`/api/admin/audit/actors/${encodeURIComponent(kind)}/${encodeURIComponent(id)}/events`,
|
`/api/admin/audit/actors/${encodeURIComponent(kind)}/${encodeURIComponent(id)}/events`,
|
||||||
params
|
params
|
||||||
@@ -48,6 +67,7 @@ export class AuditService extends BaseService {
|
|||||||
static searchResources(
|
static searchResources(
|
||||||
params: AuditResourceSearchQuery
|
params: AuditResourceSearchQuery
|
||||||
): Promise<BaseResponse<AuditResourceSearchPage>> {
|
): Promise<BaseResponse<AuditResourceSearchPage>> {
|
||||||
|
this.ensurePlatformAuditAccess()
|
||||||
return this.get('/api/admin/audit/resources/search', params)
|
return this.get('/api/admin/audit/resources/search', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +76,7 @@ export class AuditService extends BaseService {
|
|||||||
resourceId: string,
|
resourceId: string,
|
||||||
params?: AuditResourceTimelineQuery
|
params?: AuditResourceTimelineQuery
|
||||||
): Promise<BaseResponse<AuditEventPage>> {
|
): Promise<BaseResponse<AuditEventPage>> {
|
||||||
|
this.ensurePlatformAuditAccess()
|
||||||
return this.get(
|
return this.get(
|
||||||
`/api/admin/audit/resources/${encodeURIComponent(resourceType)}/${encodeURIComponent(resourceId)}/timeline`,
|
`/api/admin/audit/resources/${encodeURIComponent(resourceType)}/${encodeURIComponent(resourceId)}/timeline`,
|
||||||
params
|
params
|
||||||
@@ -63,40 +84,48 @@ export class AuditService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static getRequestTimeline(requestId: string): Promise<BaseResponse<AuditLinkTimeline>> {
|
static getRequestTimeline(requestId: string): Promise<BaseResponse<AuditLinkTimeline>> {
|
||||||
|
this.ensurePlatformAuditAccess()
|
||||||
return this.get(`/api/admin/audit/requests/${encodeURIComponent(requestId)}/timeline`)
|
return this.get(`/api/admin/audit/requests/${encodeURIComponent(requestId)}/timeline`)
|
||||||
}
|
}
|
||||||
|
|
||||||
static getCorrelationTimeline(correlationId: string): Promise<BaseResponse<AuditLinkTimeline>> {
|
static getCorrelationTimeline(correlationId: string): Promise<BaseResponse<AuditLinkTimeline>> {
|
||||||
|
this.ensurePlatformAuditAccess()
|
||||||
return this.get(`/api/admin/audit/correlations/${encodeURIComponent(correlationId)}/timeline`)
|
return this.get(`/api/admin/audit/correlations/${encodeURIComponent(correlationId)}/timeline`)
|
||||||
}
|
}
|
||||||
|
|
||||||
static getFinanceTimeline(
|
static getFinanceTimeline(
|
||||||
params: AuditFinanceQuery
|
params: AuditFinanceQuery
|
||||||
): Promise<BaseResponse<AuditFinanceTimelinePage>> {
|
): Promise<BaseResponse<AuditFinanceTimelinePage>> {
|
||||||
|
this.ensurePlatformAuditAccess()
|
||||||
return this.get('/api/admin/audit/finance/timeline', params)
|
return this.get('/api/admin/audit/finance/timeline', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
static getRiskOverview(params?: AuditRiskQuery): Promise<BaseResponse<AuditRiskOverview>> {
|
static getRiskOverview(params?: AuditRiskQuery): Promise<BaseResponse<AuditRiskOverview>> {
|
||||||
|
this.ensurePlatformAuditAccess()
|
||||||
return this.get('/api/admin/audit/risks/overview', params)
|
return this.get('/api/admin/audit/risks/overview', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
static getRiskEvents(params?: AuditRiskEventQuery): Promise<BaseResponse<AuditRiskEventPage>> {
|
static getRiskEvents(params?: AuditRiskEventQuery): Promise<BaseResponse<AuditRiskEventPage>> {
|
||||||
|
this.ensurePlatformAuditAccess()
|
||||||
return this.get('/api/admin/audit/risks/events', params)
|
return this.get('/api/admin/audit/risks/events', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
static getIntegrationOverview(
|
static getIntegrationOverview(
|
||||||
params?: IntegrationQuery & { bucket?: 'hour' | 'day' }
|
params?: IntegrationQuery & { bucket?: 'hour' | 'day' }
|
||||||
): Promise<BaseResponse<IntegrationOverview>> {
|
): Promise<BaseResponse<IntegrationOverview>> {
|
||||||
|
this.ensurePlatformAuditAccess()
|
||||||
return this.get('/api/admin/audit/integrations/overview', params)
|
return this.get('/api/admin/audit/integrations/overview', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
static getIntegrations(params?: IntegrationQuery): Promise<BaseResponse<IntegrationListPage>> {
|
static getIntegrations(params?: IntegrationQuery): Promise<BaseResponse<IntegrationListPage>> {
|
||||||
|
this.ensurePlatformAuditAccess()
|
||||||
return this.get('/api/admin/audit/integrations', params)
|
return this.get('/api/admin/audit/integrations', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
static getIntegrationDetail(
|
static getIntegrationDetail(
|
||||||
integrationId: string
|
integrationId: string
|
||||||
): Promise<BaseResponse<IntegrationDetailResponse>> {
|
): Promise<BaseResponse<IntegrationDetailResponse>> {
|
||||||
|
this.ensurePlatformAuditAccess()
|
||||||
return this.get(`/api/admin/audit/integrations/${encodeURIComponent(integrationId)}`)
|
return this.get(`/api/admin/audit/integrations/${encodeURIComponent(integrationId)}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +134,7 @@ export class AuditService extends BaseService {
|
|||||||
identifier: string,
|
identifier: string,
|
||||||
params?: AuditSubjectActivityQuery
|
params?: AuditSubjectActivityQuery
|
||||||
): Promise<BaseResponse<AuditSubjectActivityPage>> {
|
): Promise<BaseResponse<AuditSubjectActivityPage>> {
|
||||||
|
this.ensureSubjectActivityAccess('agent')
|
||||||
return this.get(
|
return this.get(
|
||||||
`/api/admin/agent/resource-activities/${encodeURIComponent(resourceType)}/${encodeURIComponent(identifier)}`,
|
`/api/admin/agent/resource-activities/${encodeURIComponent(resourceType)}/${encodeURIComponent(identifier)}`,
|
||||||
params
|
params
|
||||||
@@ -116,6 +146,7 @@ export class AuditService extends BaseService {
|
|||||||
identifier: string,
|
identifier: string,
|
||||||
params?: AuditSubjectActivityQuery
|
params?: AuditSubjectActivityQuery
|
||||||
): Promise<BaseResponse<AuditSubjectActivityPage>> {
|
): Promise<BaseResponse<AuditSubjectActivityPage>> {
|
||||||
|
this.ensureSubjectActivityAccess('enterprise')
|
||||||
return this.get(
|
return this.get(
|
||||||
`/api/admin/enterprise/resource-activities/${encodeURIComponent(resourceType)}/${encodeURIComponent(identifier)}`,
|
`/api/admin/enterprise/resource-activities/${encodeURIComponent(resourceType)}/${encodeURIComponent(identifier)}`,
|
||||||
params
|
params
|
||||||
|
|||||||
@@ -12,7 +12,12 @@
|
|||||||
<template #default="{ row }">{{ formatDateTime(row.occurred_at) }}</template>
|
<template #default="{ row }">{{ formatDateTime(row.occurred_at) }}</template>
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
<ElTableColumn label="动作" min-width="180">
|
<ElTableColumn label="动作" min-width="180">
|
||||||
<template #default="{ row }">{{ row.action_name || '-' }}</template>
|
<template #default="{ row }">
|
||||||
|
<ElButton v-if="detailEnabled" link type="primary" @click="$emit('detail', row)">
|
||||||
|
{{ row.action_name || '-' }}
|
||||||
|
</ElButton>
|
||||||
|
<span v-else>{{ row.action_name || '-' }}</span>
|
||||||
|
</template>
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
<ElTableColumn label="摘要" prop="summary" min-width="240" show-overflow-tooltip />
|
<ElTableColumn label="摘要" prop="summary" min-width="240" show-overflow-tooltip />
|
||||||
<ElTableColumn label="操作者" min-width="150">
|
<ElTableColumn label="操作者" min-width="150">
|
||||||
@@ -30,11 +35,6 @@
|
|||||||
}}</ElTag></template
|
}}</ElTag></template
|
||||||
>
|
>
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
<ElTableColumn label="操作" width="110" fixed="right">
|
|
||||||
<template #default="{ row }"
|
|
||||||
><ElButton link type="primary" @click="$emit('detail', row)">查看详情</ElButton></template
|
|
||||||
>
|
|
||||||
</ElTableColumn>
|
|
||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
</template>
|
</template>
|
||||||
@@ -44,7 +44,12 @@
|
|||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import { auditResultMeta, auditRiskMeta } from '@/utils/business/audit'
|
import { auditResultMeta, auditRiskMeta } from '@/utils/business/audit'
|
||||||
|
|
||||||
defineProps<{ items: AuditEventView[]; loading?: boolean }>()
|
withDefaults(
|
||||||
|
defineProps<{ items: AuditEventView[]; loading?: boolean; detailEnabled?: boolean }>(),
|
||||||
|
{
|
||||||
|
detailEnabled: true
|
||||||
|
}
|
||||||
|
)
|
||||||
defineEmits<{ detail: [row: AuditEventView] }>()
|
defineEmits<{ detail: [row: AuditEventView] }>()
|
||||||
const resultMeta = (row: AuditEventView) =>
|
const resultMeta = (row: AuditEventView) =>
|
||||||
auditResultMeta[row.result] || { label: row.result, type: 'info' as const }
|
auditResultMeta[row.result] || { label: row.result, type: 'info' as const }
|
||||||
|
|||||||
@@ -21,12 +21,172 @@
|
|||||||
<article class="timeline-node">
|
<article class="timeline-node">
|
||||||
<div class="node-head">
|
<div class="node-head">
|
||||||
<strong>{{ item.action_name }}</strong>
|
<strong>{{ item.action_name }}</strong>
|
||||||
<ElTag size="small" :type="auditResultMeta[item.result]?.type">{{
|
<span class="node-tags">
|
||||||
auditResultMeta[item.result]?.label || item.result
|
<ElTag size="small" :type="auditResultMeta[item.result]?.type">{{
|
||||||
}}</ElTag>
|
auditResultMeta[item.result]?.label || item.result
|
||||||
|
}}</ElTag>
|
||||||
|
<ElTag
|
||||||
|
v-if="isFullEventTimeline"
|
||||||
|
size="small"
|
||||||
|
effect="plain"
|
||||||
|
:type="auditRiskMeta[item.risk_level]?.type"
|
||||||
|
>
|
||||||
|
{{ auditRiskMeta[item.risk_level]?.label || item.risk_level }}风险
|
||||||
|
</ElTag>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p>{{ item.summary || '-' }}</p>
|
<p v-if="!isFullEventTimeline">{{ item.summary || '-' }}</p>
|
||||||
<div class="node-meta">
|
<template v-if="isFullEventTimeline">
|
||||||
|
<ElDescriptions class="actor-event-summary" :column="4" size="small" border>
|
||||||
|
<ElDescriptionsItem label="分类">
|
||||||
|
{{ auditCategoryLabels[item.category] || item.category }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="来源">
|
||||||
|
{{ auditSourceLabels[item.source] || item.source }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="操作者">
|
||||||
|
{{ actorDisplay(item) }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="业务范围">
|
||||||
|
{{ scopeDisplay(item) }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
</ElDescriptions>
|
||||||
|
|
||||||
|
<ElCollapse class="actor-event-sections">
|
||||||
|
<ElCollapseItem
|
||||||
|
v-if="userStore.isSuperAdmin && hasRequestContext(item)"
|
||||||
|
title="请求上下文"
|
||||||
|
name="request"
|
||||||
|
>
|
||||||
|
<ElDescriptions :column="1" size="small" border>
|
||||||
|
<ElDescriptionsItem label="请求">
|
||||||
|
{{
|
||||||
|
[item.request_method, item.request_path].filter(Boolean).join(' ') || '-'
|
||||||
|
}}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="request_id">{{
|
||||||
|
item.request_id || '-'
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="correlation_id">{{
|
||||||
|
item.correlation_id || '-'
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="IP 地址">{{
|
||||||
|
item.ip_address || '-'
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="User-Agent">{{
|
||||||
|
item.user_agent || '-'
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
|
</ElDescriptions>
|
||||||
|
</ElCollapseItem>
|
||||||
|
<ElCollapseItem v-if="hasBatchData(item)" title="批次执行统计" name="batch">
|
||||||
|
<ElDescriptions :column="3" size="small" border>
|
||||||
|
<ElDescriptionsItem label="处理总数">{{
|
||||||
|
item.batch_total || 0
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="成功数">{{
|
||||||
|
item.success_count || 0
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="失败数">{{
|
||||||
|
item.fail_count || 0
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
|
</ElDescriptions>
|
||||||
|
</ElCollapseItem>
|
||||||
|
<ElCollapseItem
|
||||||
|
v-if="item.error_code || item.error_summary"
|
||||||
|
title="错误信息"
|
||||||
|
name="error"
|
||||||
|
>
|
||||||
|
<ElDescriptions :column="1" size="small" border>
|
||||||
|
<ElDescriptionsItem label="错误码">{{
|
||||||
|
item.error_code || '-'
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="错误摘要">{{
|
||||||
|
item.error_summary || '-'
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
|
</ElDescriptions>
|
||||||
|
</ElCollapseItem>
|
||||||
|
<ElCollapseItem
|
||||||
|
v-if="item.resources?.length"
|
||||||
|
title="相关资源与快照"
|
||||||
|
name="resources"
|
||||||
|
>
|
||||||
|
<div class="actor-resource-list">
|
||||||
|
<section
|
||||||
|
v-for="resource in item.resources"
|
||||||
|
:key="resourceKey(resource)"
|
||||||
|
class="actor-resource"
|
||||||
|
>
|
||||||
|
<div class="actor-resource__title">
|
||||||
|
<strong>{{
|
||||||
|
resource.display_name ||
|
||||||
|
resource.resource_key ||
|
||||||
|
resource.resource_id ||
|
||||||
|
'-'
|
||||||
|
}}</strong>
|
||||||
|
<ElTag size="small" effect="plain">
|
||||||
|
{{ auditResourceRelationLabels[resource.relation] || resource.relation }}
|
||||||
|
</ElTag>
|
||||||
|
</div>
|
||||||
|
<ElDescriptions :column="2" size="small">
|
||||||
|
<ElDescriptionsItem label="资源类型">
|
||||||
|
{{
|
||||||
|
auditResourceTypeLabels[resource.resource_type] ||
|
||||||
|
resource.resource_type
|
||||||
|
}}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="业务角色">{{
|
||||||
|
resource.role || '-'
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="资源标识">
|
||||||
|
{{ resource.resource_key || resource.resource_id || '-' }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="主体摘要">{{
|
||||||
|
resource.subject_summary || '-'
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
|
</ElDescriptions>
|
||||||
|
<ElCollapse class="resource-json-sections">
|
||||||
|
<ElCollapseItem
|
||||||
|
v-if="hasJsonContent(resource.identity_snapshot)"
|
||||||
|
title="身份快照"
|
||||||
|
name="identity"
|
||||||
|
>
|
||||||
|
<pre>{{ auditJson(resource.identity_snapshot) }}</pre>
|
||||||
|
</ElCollapseItem>
|
||||||
|
<ElCollapseItem
|
||||||
|
v-if="hasJsonContent(resource.subject_data)"
|
||||||
|
title="主体安全数据"
|
||||||
|
name="subject"
|
||||||
|
>
|
||||||
|
<pre>{{ auditJson(resource.subject_data) }}</pre>
|
||||||
|
</ElCollapseItem>
|
||||||
|
<ElCollapseItem
|
||||||
|
v-if="hasJsonContent(resource.before_data)"
|
||||||
|
title="变更前"
|
||||||
|
name="before"
|
||||||
|
>
|
||||||
|
<pre>{{ auditJson(resource.before_data) }}</pre>
|
||||||
|
</ElCollapseItem>
|
||||||
|
<ElCollapseItem
|
||||||
|
v-if="hasJsonContent(resource.after_data)"
|
||||||
|
title="变更后"
|
||||||
|
name="after"
|
||||||
|
>
|
||||||
|
<pre>{{ auditJson(resource.after_data) }}</pre>
|
||||||
|
</ElCollapseItem>
|
||||||
|
</ElCollapse>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</ElCollapseItem>
|
||||||
|
<ElCollapseItem
|
||||||
|
v-if="userStore.isSuperAdmin && hasJsonContent(item.metadata)"
|
||||||
|
title="扩展元数据"
|
||||||
|
name="metadata"
|
||||||
|
>
|
||||||
|
<pre>{{ auditJson(item.metadata) }}</pre>
|
||||||
|
</ElCollapseItem>
|
||||||
|
</ElCollapse>
|
||||||
|
</template>
|
||||||
|
<div v-else class="node-meta">
|
||||||
<span>{{ item.actor_name || item.actor_id || '-' }}</span>
|
<span>{{ item.actor_name || item.actor_id || '-' }}</span>
|
||||||
<span>{{ auditSourceLabels[item.source] || item.source }}</span>
|
<span>{{ auditSourceLabels[item.source] || item.source }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,16 +205,65 @@
|
|||||||
<article class="timeline-node">
|
<article class="timeline-node">
|
||||||
<div class="node-head">
|
<div class="node-head">
|
||||||
<strong>{{ node.title }}</strong>
|
<strong>{{ node.title }}</strong>
|
||||||
<ElTag size="small" effect="plain">{{
|
<span class="node-tags">
|
||||||
auditResultDisplay(node.result, node.result_name)
|
<ElTag size="small" effect="plain">{{
|
||||||
}}</ElTag>
|
auditResultDisplay(node.result, node.result_name)
|
||||||
|
}}</ElTag>
|
||||||
|
<ElTag v-if="node.reference_only" size="small" type="info" effect="plain">
|
||||||
|
只读引用
|
||||||
|
</ElTag>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p>{{ node.summary || '-' }}</p>
|
<p v-if="!isStructuredLinkTimeline">{{ node.summary || '-' }}</p>
|
||||||
<div class="node-meta">
|
<template v-if="isStructuredLinkTimeline">
|
||||||
|
<ElDescriptions class="actor-event-summary" :column="4" size="small" border>
|
||||||
|
<ElDescriptionsItem label="事实来源">
|
||||||
|
{{ recordSourceLabels[node.record_source] || node.record_source }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="稳定编码">{{ node.code || '-' }}</ElDescriptionsItem>
|
||||||
|
</ElDescriptions>
|
||||||
|
<ElCollapse class="actor-event-sections">
|
||||||
|
<ElCollapseItem
|
||||||
|
v-if="userStore.isSuperAdmin && hasJsonContent(node.fidelity)"
|
||||||
|
title="数据完整性"
|
||||||
|
name="fidelity"
|
||||||
|
>
|
||||||
|
<pre>{{ auditJson(node.fidelity) }}</pre>
|
||||||
|
</ElCollapseItem>
|
||||||
|
<ElCollapseItem v-if="node.resources?.length" title="相关资源" name="resources">
|
||||||
|
<div class="actor-resource-list">
|
||||||
|
<section
|
||||||
|
v-for="(resource, index) in node.resources"
|
||||||
|
:key="
|
||||||
|
[
|
||||||
|
resource.resource_type,
|
||||||
|
resource.resource_id || resource.resource_key,
|
||||||
|
index
|
||||||
|
].join(':')
|
||||||
|
"
|
||||||
|
class="actor-resource"
|
||||||
|
>
|
||||||
|
<div class="actor-resource__title">
|
||||||
|
<strong>{{ resource.display_name || resource.resource_key || '-' }}</strong>
|
||||||
|
</div>
|
||||||
|
<ElDescriptions :column="2" size="small">
|
||||||
|
<ElDescriptionsItem label="资源类型">
|
||||||
|
{{
|
||||||
|
auditResourceTypeLabels[resource.resource_type] ||
|
||||||
|
resource.resource_type
|
||||||
|
}}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem v-if="resource.resource_key" label="资源业务 Key">
|
||||||
|
{{ resource.resource_key }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
</ElDescriptions>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</ElCollapseItem>
|
||||||
|
</ElCollapse>
|
||||||
|
</template>
|
||||||
|
<div v-else class="node-meta">
|
||||||
<span>{{ recordSourceLabels[node.record_source] || node.record_source }}</span>
|
<span>{{ recordSourceLabels[node.record_source] || node.record_source }}</span>
|
||||||
<ElTag v-if="node.reference_only" size="small" type="info" effect="plain">
|
|
||||||
只读引用
|
|
||||||
</ElTag>
|
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</ElTimelineItem>
|
</ElTimelineItem>
|
||||||
@@ -75,13 +284,63 @@
|
|||||||
{{ auditResultDisplay(item.result, item.result_name) }}
|
{{ auditResultDisplay(item.result, item.result_name) }}
|
||||||
</ElTag>
|
</ElTag>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="item.amount !== null && item.amount !== undefined">
|
<ElDescriptions class="actor-event-summary" :column="4" size="small" border>
|
||||||
金额:{{ formatMoney(item.amount, item.currency) }}
|
<ElDescriptionsItem
|
||||||
</p>
|
v-if="item.amount !== null && item.amount !== undefined"
|
||||||
<div class="node-meta">
|
label="金额"
|
||||||
<span>{{ item.code || '-' }}</span>
|
>
|
||||||
<span>{{ item.record_source || '-' }}</span>
|
{{ formatMoney(item.amount, item.currency) }}
|
||||||
</div>
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem
|
||||||
|
v-if="item.balance_before !== null && item.balance_before !== undefined"
|
||||||
|
label="变更前余额"
|
||||||
|
>
|
||||||
|
{{ formatMoney(item.balance_before, item.currency) }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem
|
||||||
|
v-if="item.balance_after !== null && item.balance_after !== undefined"
|
||||||
|
label="变更后余额"
|
||||||
|
>
|
||||||
|
{{ formatMoney(item.balance_after, item.currency) }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="币种">{{ item.currency || '-' }}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="事实来源">
|
||||||
|
{{ financeRecordSourceLabel(item.record_source) }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="稳定编码">{{ item.code || '-' }}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="钱包类型">
|
||||||
|
{{ financeWalletTypeLabel(item.wallet?.resource_type) }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
</ElDescriptions>
|
||||||
|
<ElCollapse
|
||||||
|
v-if="item.amount_authority || hasJsonContent(item.facts)"
|
||||||
|
class="actor-event-sections"
|
||||||
|
>
|
||||||
|
<ElCollapseItem v-if="item.amount_authority" title="金额权威来源" name="authority">
|
||||||
|
<ElDescriptions :column="1" size="small" border>
|
||||||
|
<ElDescriptionsItem label="是否权威">
|
||||||
|
<ElTag
|
||||||
|
size="small"
|
||||||
|
:type="item.amount_authority.authoritative ? 'success' : 'info'"
|
||||||
|
>
|
||||||
|
{{ item.amount_authority.authoritative ? '是' : '否' }}
|
||||||
|
</ElTag>
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem v-if="item.amount_authority.table" label="业务表">
|
||||||
|
{{ item.amount_authority.table }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem v-if="item.amount_authority.field" label="金额字段">
|
||||||
|
{{ item.amount_authority.field }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem v-if="item.amount_authority.conflict_rule" label="冲突规则">
|
||||||
|
{{ item.amount_authority.conflict_rule }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
</ElDescriptions>
|
||||||
|
</ElCollapseItem>
|
||||||
|
<ElCollapseItem v-if="hasJsonContent(item.facts)" title="安全结构化事实" name="facts">
|
||||||
|
<pre>{{ auditJsonWithoutIds(item.facts) }}</pre>
|
||||||
|
</ElCollapseItem>
|
||||||
|
</ElCollapse>
|
||||||
</article>
|
</article>
|
||||||
</ElTimelineItem>
|
</ElTimelineItem>
|
||||||
</ElTimeline>
|
</ElTimeline>
|
||||||
@@ -138,63 +397,176 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else-if="integrationDetail">
|
<template v-else-if="integrationDetail">
|
||||||
<ElDescriptions :column="2" border>
|
<section class="integration-hero">
|
||||||
<ElDescriptionsItem label="提供方">
|
<div>
|
||||||
{{ integrationDetail.identity.provider_name || integrationDetail.identity.provider }}
|
<span class="integration-hero__eyebrow">
|
||||||
</ElDescriptionsItem>
|
{{ integrationDetail.identity.provider_name || integrationDetail.identity.provider }}
|
||||||
<ElDescriptionsItem label="方向">
|
·
|
||||||
{{ integrationDetail.identity.direction_name || integrationDetail.identity.direction }}
|
{{
|
||||||
</ElDescriptionsItem>
|
integrationDetail.identity.direction_name || integrationDetail.identity.direction
|
||||||
<ElDescriptionsItem label="操作">
|
}}
|
||||||
{{ integrationDetail.identity.operation_name || integrationDetail.identity.operation }}
|
</span>
|
||||||
</ElDescriptionsItem>
|
<h3>{{
|
||||||
<ElDescriptionsItem label="结果">
|
integrationDetail.identity.operation_name || integrationDetail.identity.operation
|
||||||
|
}}</h3>
|
||||||
|
<p>{{ formatDateTime(integrationDetail.timestamps.created_at) }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="integration-hero__result">
|
||||||
<ElTag
|
<ElTag
|
||||||
size="small"
|
|
||||||
:type="integrationCategoryMeta[integrationDetail.result.category]?.type || 'info'"
|
:type="integrationCategoryMeta[integrationDetail.result.category]?.type || 'info'"
|
||||||
>
|
>
|
||||||
{{ auditResultDisplay(integrationDetail.result.code, integrationDetail.result.name) }}
|
{{ auditResultDisplay(integrationDetail.result.code, integrationDetail.result.name) }}
|
||||||
</ElTag>
|
</ElTag>
|
||||||
</ElDescriptionsItem>
|
<strong>{{ integrationDetail.result.duration_ms ?? '-' }} ms</strong>
|
||||||
<ElDescriptionsItem label="耗时">
|
<span>处理耗时</span>
|
||||||
{{ integrationDetail.result.duration_ms }} ms
|
</div>
|
||||||
</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem label="HTTP 状态">
|
|
||||||
{{ integrationDetail.result.http_status ?? '-' }}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem v-if="integrationDetail.identity.external_id" label="外部业务标识">
|
|
||||||
{{ integrationDetail.identity.external_id }}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem v-if="integrationDetail.resource?.key" label="资源业务标识">
|
|
||||||
{{ integrationDetail.resource.key }}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem
|
|
||||||
v-if="integrationDetail.result.provider_message"
|
|
||||||
label="外部结果摘要"
|
|
||||||
:span="2"
|
|
||||||
>
|
|
||||||
{{ integrationDetail.result.provider_message }}
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
</ElDescriptions>
|
|
||||||
<section
|
|
||||||
v-if="
|
|
||||||
hasJsonContent(integrationDetail.content.request_summary) ||
|
|
||||||
hasJsonContent(integrationDetail.content.response_summary) ||
|
|
||||||
hasJsonContent(integrationDetail.content.metadata)
|
|
||||||
"
|
|
||||||
class="json-section"
|
|
||||||
>
|
|
||||||
<h4>交互内容摘要</h4>
|
|
||||||
<pre v-if="hasJsonContent(integrationDetail.content.request_summary)">{{
|
|
||||||
auditJson(integrationDetail.content.request_summary)
|
|
||||||
}}</pre>
|
|
||||||
<pre v-if="hasJsonContent(integrationDetail.content.response_summary)">{{
|
|
||||||
auditJson(integrationDetail.content.response_summary)
|
|
||||||
}}</pre>
|
|
||||||
<pre v-if="hasJsonContent(integrationDetail.content.metadata)">{{
|
|
||||||
auditJson(integrationDetail.content.metadata)
|
|
||||||
}}</pre>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<div class="integration-detail-grid">
|
||||||
|
<section class="integration-panel">
|
||||||
|
<h4>交互信息</h4>
|
||||||
|
<ElDescriptions :column="2" size="small">
|
||||||
|
<ElDescriptionsItem label="提供方">
|
||||||
|
{{
|
||||||
|
integrationDetail.identity.provider_name || integrationDetail.identity.provider
|
||||||
|
}}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="方向">
|
||||||
|
{{
|
||||||
|
integrationDetail.identity.direction_name || integrationDetail.identity.direction
|
||||||
|
}}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="操作" :span="2">
|
||||||
|
{{
|
||||||
|
integrationDetail.identity.operation_name || integrationDetail.identity.operation
|
||||||
|
}}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem
|
||||||
|
v-if="integrationDetail.result.http_status != null"
|
||||||
|
label="HTTP 状态"
|
||||||
|
>
|
||||||
|
{{ integrationDetail.result.http_status }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="状态是否变化">
|
||||||
|
{{ integrationDetail.result.state_changed ? '是' : '否' }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem
|
||||||
|
v-if="integrationDetail.result.provider_message"
|
||||||
|
label="外部结果说明"
|
||||||
|
:span="2"
|
||||||
|
>
|
||||||
|
{{ integrationDetail.result.provider_message }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
</ElDescriptions>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="integration-panel">
|
||||||
|
<h4>触发信息</h4>
|
||||||
|
<ElDescriptions :column="1" size="small">
|
||||||
|
<ElDescriptionsItem label="触发来源">
|
||||||
|
{{ triggerSourceLabel(integrationDetail.trigger.source) }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="业务场景">
|
||||||
|
{{ triggerSceneLabel(integrationDetail.trigger.scene) }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="开始时间">
|
||||||
|
{{ formatDateTime(integrationDetail.timestamps.started_at) }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
</ElDescriptions>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section v-if="hasVisibleIntegrationContent" class="integration-panel integration-content">
|
||||||
|
<h4>交互内容摘要</h4>
|
||||||
|
<div class="integration-content-grid">
|
||||||
|
<div
|
||||||
|
v-if="hasJsonContentWithoutIds(integrationDetail.content.request_summary)"
|
||||||
|
class="content-block"
|
||||||
|
>
|
||||||
|
<span>请求摘要</span>
|
||||||
|
<pre>{{ auditJsonWithoutIds(integrationDetail.content.request_summary) }}</pre>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="hasJsonContentWithoutIds(integrationDetail.content.response_summary)"
|
||||||
|
class="content-block"
|
||||||
|
>
|
||||||
|
<span>响应摘要</span>
|
||||||
|
<pre>{{ auditJsonWithoutIds(integrationDetail.content.response_summary) }}</pre>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="hasJsonContentWithoutIds(integrationDetail.content.metadata)"
|
||||||
|
class="content-block"
|
||||||
|
>
|
||||||
|
<span>扩展信息</span>
|
||||||
|
<pre>{{ auditJsonWithoutIds(integrationDetail.content.metadata) }}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<template v-if="integrationDetail && false">
|
||||||
|
<ElDescriptions :column="2" border>
|
||||||
|
<ElDescriptionsItem label="提供方">
|
||||||
|
{{ integrationDetail.identity.provider_name || integrationDetail.identity.provider }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="方向">
|
||||||
|
{{
|
||||||
|
integrationDetail.identity.direction_name || integrationDetail.identity.direction
|
||||||
|
}}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="操作">
|
||||||
|
{{
|
||||||
|
integrationDetail.identity.operation_name || integrationDetail.identity.operation
|
||||||
|
}}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="结果">
|
||||||
|
<ElTag
|
||||||
|
size="small"
|
||||||
|
:type="integrationCategoryMeta[integrationDetail.result.category]?.type || 'info'"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
auditResultDisplay(integrationDetail.result.code, integrationDetail.result.name)
|
||||||
|
}}
|
||||||
|
</ElTag>
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="耗时">
|
||||||
|
{{ integrationDetail.result.duration_ms }} ms
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="HTTP 状态">
|
||||||
|
{{ integrationDetail.result.http_status ?? '-' }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem v-if="integrationDetail.identity.external_id" label="外部业务标识">
|
||||||
|
{{ integrationDetail.identity.external_id }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem v-if="integrationDetail.resource?.key" label="资源业务标识">
|
||||||
|
{{ integrationDetail.resource.key }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem
|
||||||
|
v-if="integrationDetail.result.provider_message"
|
||||||
|
label="外部结果摘要"
|
||||||
|
:span="2"
|
||||||
|
>
|
||||||
|
{{ integrationDetail.result.provider_message }}
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
</ElDescriptions>
|
||||||
|
<section
|
||||||
|
v-if="
|
||||||
|
hasJsonContent(integrationDetail.content.request_summary) ||
|
||||||
|
hasJsonContent(integrationDetail.content.response_summary) ||
|
||||||
|
hasJsonContent(integrationDetail.content.metadata)
|
||||||
|
"
|
||||||
|
class="json-section"
|
||||||
|
>
|
||||||
|
<h4>交互内容摘要</h4>
|
||||||
|
<pre v-if="hasJsonContent(integrationDetail.content.request_summary)">{{
|
||||||
|
auditJson(integrationDetail.content.request_summary)
|
||||||
|
}}</pre>
|
||||||
|
<pre v-if="hasJsonContent(integrationDetail.content.response_summary)">{{
|
||||||
|
auditJson(integrationDetail.content.response_summary)
|
||||||
|
}}</pre>
|
||||||
|
<pre v-if="hasJsonContent(integrationDetail.content.metadata)">{{
|
||||||
|
auditJson(integrationDetail.content.metadata)
|
||||||
|
}}</pre>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<ElEmpty v-else-if="!loading" description="当前在线窗口内没有记录" />
|
<ElEmpty v-else-if="!loading" description="当前在线窗口内没有记录" />
|
||||||
@@ -226,13 +598,19 @@
|
|||||||
} from '@/types/api'
|
} from '@/types/api'
|
||||||
import {
|
import {
|
||||||
auditJson,
|
auditJson,
|
||||||
|
auditCategoryLabels,
|
||||||
|
auditResourceRelationLabels,
|
||||||
|
auditResourceTypeLabels,
|
||||||
auditResultDisplay,
|
auditResultDisplay,
|
||||||
auditResultMeta,
|
auditResultMeta,
|
||||||
auditRiskMeta,
|
auditRiskMeta,
|
||||||
|
auditScopeTypeLabels,
|
||||||
auditSourceLabels,
|
auditSourceLabels,
|
||||||
integrationCategoryMeta
|
integrationCategoryMeta
|
||||||
} from '@/utils/business/audit'
|
} from '@/utils/business/audit'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
|
import { canLoadAuditInvestigation } from '@/utils/business/auditAccess'
|
||||||
|
import { useUserStore } from '@/store/modules/user'
|
||||||
import type { AuditInvestigationTarget } from './types'
|
import type { AuditInvestigationTarget } from './types'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -240,6 +618,7 @@
|
|||||||
target?: AuditInvestigationTarget | null
|
target?: AuditInvestigationTarget | null
|
||||||
}>()
|
}>()
|
||||||
const emit = defineEmits<{ 'update:modelValue': [value: boolean] }>()
|
const emit = defineEmits<{ 'update:modelValue': [value: boolean] }>()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
const visible = computed({
|
const visible = computed({
|
||||||
get: () => props.modelValue,
|
get: () => props.modelValue,
|
||||||
@@ -268,6 +647,12 @@
|
|||||||
const eventDetail = ref<AuditEventDetail>()
|
const eventDetail = ref<AuditEventDetail>()
|
||||||
const integrationDetail = ref<IntegrationDetailResponse>()
|
const integrationDetail = ref<IntegrationDetailResponse>()
|
||||||
let loadSequence = 0
|
let loadSequence = 0
|
||||||
|
const isFullEventTimeline = computed(() =>
|
||||||
|
['actor', 'resource'].includes(props.target?.mode || '')
|
||||||
|
)
|
||||||
|
const isStructuredLinkTimeline = computed(() =>
|
||||||
|
['request', 'correlation'].includes(props.target?.mode || '')
|
||||||
|
)
|
||||||
|
|
||||||
const recordSourceLabels: Record<string, string> = {
|
const recordSourceLabels: Record<string, string> = {
|
||||||
audit_event: '审计事件',
|
audit_event: '审计事件',
|
||||||
@@ -276,6 +661,21 @@
|
|||||||
asynq_task: '异步任务引用',
|
asynq_task: '异步任务引用',
|
||||||
domain_ledger_ref: '业务台账引用'
|
domain_ledger_ref: '业务台账引用'
|
||||||
}
|
}
|
||||||
|
const financeRecordSourceLabels: Record<string, string> = {
|
||||||
|
audit_event: '审计事件',
|
||||||
|
domain_ledger_ref: '业务台账引用',
|
||||||
|
agent_wallet_transaction: '代理钱包流水',
|
||||||
|
asset_wallet_transaction: '资产钱包流水',
|
||||||
|
agent_wallet_reservation: '代理钱包预留',
|
||||||
|
order: '订单',
|
||||||
|
payment: '支付',
|
||||||
|
refund: '退款',
|
||||||
|
agent_recharge: '代理充值',
|
||||||
|
recharge_order: '充值订单',
|
||||||
|
commission_record: '佣金记录',
|
||||||
|
commission_withdrawal: '佣金提现',
|
||||||
|
approval_instance: '审批实例'
|
||||||
|
}
|
||||||
|
|
||||||
const clear = () => {
|
const clear = () => {
|
||||||
eventItems.value = []
|
eventItems.value = []
|
||||||
@@ -288,12 +688,96 @@
|
|||||||
}
|
}
|
||||||
const hasJsonContent = (value?: Record<string, unknown> | null) =>
|
const hasJsonContent = (value?: Record<string, unknown> | null) =>
|
||||||
Boolean(value && Object.keys(value).length)
|
Boolean(value && Object.keys(value).length)
|
||||||
|
const auditJsonWithoutIds = (value?: Record<string, unknown> | null) => {
|
||||||
|
const removeIds = (current: unknown): unknown => {
|
||||||
|
if (Array.isArray(current)) return current.map(removeIds)
|
||||||
|
if (!current || typeof current !== 'object') return current
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(current as Record<string, unknown>)
|
||||||
|
.filter(([key]) => !/(^|[_-])ids?$/i.test(key))
|
||||||
|
.map(([key, nested]) => [key, removeIds(nested)])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return auditJson(removeIds(value))
|
||||||
|
}
|
||||||
|
const withoutIds = (value?: Record<string, unknown> | null) => {
|
||||||
|
const removeIds = (current: unknown): unknown => {
|
||||||
|
if (Array.isArray(current)) return current.map(removeIds)
|
||||||
|
if (!current || typeof current !== 'object') return current
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(current as Record<string, unknown>)
|
||||||
|
.filter(([key]) => !/(^|[_-])ids?$/i.test(key))
|
||||||
|
.map(([key, nested]) => [key, removeIds(nested)])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return removeIds(value) as Record<string, unknown> | null | undefined
|
||||||
|
}
|
||||||
|
const hasJsonContentWithoutIds = (value?: Record<string, unknown> | null) =>
|
||||||
|
hasJsonContent(withoutIds(value))
|
||||||
|
const hasVisibleIntegrationContent = computed(() =>
|
||||||
|
Boolean(
|
||||||
|
integrationDetail.value &&
|
||||||
|
[
|
||||||
|
integrationDetail.value.content.request_summary,
|
||||||
|
integrationDetail.value.content.response_summary,
|
||||||
|
integrationDetail.value.content.metadata
|
||||||
|
].some(hasJsonContentWithoutIds)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const triggerSourceLabel = (source?: string | null) =>
|
||||||
|
({ polling: '定时轮询', webhook: '外部回调', manual: '人工触发' })[source || ''] ||
|
||||||
|
source ||
|
||||||
|
'-'
|
||||||
|
const triggerSceneLabel = (scene?: string | null) =>
|
||||||
|
({ network_polling: '网络状态轮询' })[scene || ''] || scene || '-'
|
||||||
|
const financeRecordSourceLabel = (source: string) =>
|
||||||
|
financeRecordSourceLabels[source] || source || '-'
|
||||||
|
const financeWalletTypeLabel = (type?: 'agent_wallet' | 'asset_wallet') =>
|
||||||
|
type === 'agent_wallet' ? '代理钱包' : type === 'asset_wallet' ? '资产钱包' : '-'
|
||||||
|
const actorDisplay = (item: AuditEventView) => {
|
||||||
|
const actor = item.actor_name || item.actor_id || '-'
|
||||||
|
const organizations = [
|
||||||
|
item.actor_shop_name || (item.actor_shop_id ? `店铺 ID: ${item.actor_shop_id}` : ''),
|
||||||
|
item.actor_enterprise_name ||
|
||||||
|
(item.actor_enterprise_id ? `企业 ID: ${item.actor_enterprise_id}` : '')
|
||||||
|
].filter(Boolean)
|
||||||
|
return organizations.length ? `${actor}(${organizations.join(' · ')})` : actor
|
||||||
|
}
|
||||||
|
const scopeDisplay = (item: AuditEventView) => {
|
||||||
|
const scope = item.scope_name || item.scope_id
|
||||||
|
const type = auditScopeTypeLabels[item.scope_type] || item.scope_type
|
||||||
|
return scope ? `${type} · ${scope}` : type || '-'
|
||||||
|
}
|
||||||
|
const hasRequestContext = (item: AuditEventView) =>
|
||||||
|
Boolean(
|
||||||
|
item.request_method ||
|
||||||
|
item.request_path ||
|
||||||
|
item.request_id ||
|
||||||
|
item.correlation_id ||
|
||||||
|
item.ip_address ||
|
||||||
|
item.user_agent
|
||||||
|
)
|
||||||
|
const hasBatchData = (item: AuditEventView) =>
|
||||||
|
Boolean(
|
||||||
|
(item.batch_total || 0) > 0 || (item.success_count || 0) > 0 || (item.fail_count || 0) > 0
|
||||||
|
)
|
||||||
|
const resourceKey = (resource: AuditEventView['resources'][number]) =>
|
||||||
|
[resource.resource_type, resource.resource_id || resource.resource_key, resource.sort_order]
|
||||||
|
.filter((value) => value !== null && value !== undefined && value !== '')
|
||||||
|
.join(':')
|
||||||
const formatMoney = (amount: number, currency?: string | null) => {
|
const formatMoney = (amount: number, currency?: string | null) => {
|
||||||
const value = (amount / 100).toFixed(2)
|
const value = (amount / 100).toFixed(2)
|
||||||
return currency === 'CNY' || !currency ? `¥${value}` : `${value} ${currency}`
|
return currency === 'CNY' || !currency ? `¥${value}` : `${value} ${currency}`
|
||||||
}
|
}
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
if (!props.target) return
|
if (!props.target) return
|
||||||
|
if (
|
||||||
|
!canLoadAuditInvestigation(props.target, userStore.info.user_type, userStore.isSuperAdmin)
|
||||||
|
) {
|
||||||
|
clear()
|
||||||
|
ElMessage.error('当前账号无权访问该审计调查')
|
||||||
|
return
|
||||||
|
}
|
||||||
const sequence = ++loadSequence
|
const sequence = ++loadSequence
|
||||||
clear()
|
clear()
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -430,10 +914,143 @@
|
|||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.node-tags {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actor-event-summary {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actor-event-sections {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actor-resource-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actor-resource {
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--el-fill-color-lighter);
|
||||||
|
border: 1px solid var(--el-border-color-lighter);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actor-resource__title {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-json-sections {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.json-section {
|
.json-section {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.integration-hero {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 18px 20px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
var(--el-color-primary-light-9),
|
||||||
|
var(--el-fill-color-light)
|
||||||
|
);
|
||||||
|
border: 1px solid var(--el-color-primary-light-7);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.integration-hero__eyebrow,
|
||||||
|
.integration-hero p,
|
||||||
|
.integration-hero__result span,
|
||||||
|
.content-block > span {
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.integration-hero h3 {
|
||||||
|
margin: 6px 0;
|
||||||
|
font-size: 18px;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.integration-hero p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.integration-hero__result {
|
||||||
|
display: grid;
|
||||||
|
justify-items: end;
|
||||||
|
gap: 5px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.integration-hero__result strong {
|
||||||
|
font-size: 20px;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.integration-hero__result span {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.integration-detail-grid,
|
||||||
|
.integration-content-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.integration-panel {
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
background: var(--el-bg-color);
|
||||||
|
border: 1px solid var(--el-border-color-lighter);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.integration-panel h4 {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.integration-panel :deep(.el-descriptions__label) {
|
||||||
|
width: 76px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.integration-content {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.integration-content-grid {
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-block {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-block > span {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
.json-section h4 {
|
.json-section h4 {
|
||||||
margin: 0 0 8px;
|
margin: 0 0 8px;
|
||||||
}
|
}
|
||||||
@@ -448,6 +1065,24 @@
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (width <= 640px) {
|
||||||
|
.integration-hero,
|
||||||
|
.integration-detail-grid,
|
||||||
|
.integration-content-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.integration-hero {
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.integration-hero__result {
|
||||||
|
justify-items: start;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.pagination {
|
.pagination {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="hasLinks" class="investigation-links" aria-label="调查入口">
|
<div v-if="hasLinks" class="investigation-links" aria-label="调查入口">
|
||||||
<ElButton v-if="eventId" link type="primary" @click="openEvent">事件详情</ElButton>
|
|
||||||
<ElButton v-if="actorRef" link type="primary" @click="openActor"> 操作者行为时间线 </ElButton>
|
<ElButton v-if="actorRef" link type="primary" @click="openActor"> 操作者行为时间线 </ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
v-show="showResources"
|
v-show="showResources"
|
||||||
@@ -36,7 +35,7 @@
|
|||||||
业务关联链路
|
业务关联链路
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
v-for="item in stableIntegrationRefs"
|
v-for="item in visibleIntegrationRefs"
|
||||||
:key="item.integration_id"
|
:key="item.integration_id"
|
||||||
link
|
link
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -58,15 +57,16 @@
|
|||||||
import { openAuditInvestigation, openAuditResourceSearch } from './investigationController'
|
import { openAuditInvestigation, openAuditResourceSearch } from './investigationController'
|
||||||
import { auditResourceTypeLabels } from '@/utils/business/audit'
|
import { auditResourceTypeLabels } from '@/utils/business/audit'
|
||||||
import { resolveInvestigationReference } from '@/utils/business/auditNavigation'
|
import { resolveInvestigationReference } from '@/utils/business/auditNavigation'
|
||||||
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { AUDIT_PERMISSIONS } from '@/config/constants/audit'
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
refs?: AuditInvestigationRefs | null
|
refs?: AuditInvestigationRefs | null
|
||||||
currentEventId?: string
|
|
||||||
showEmpty?: boolean
|
showEmpty?: boolean
|
||||||
showResources?: boolean
|
showResources?: boolean
|
||||||
}>(),
|
}>(),
|
||||||
{ currentEventId: '', showEmpty: true, showResources: true }
|
{ showEmpty: true, showResources: true }
|
||||||
)
|
)
|
||||||
const searchableTypes = new Set<AuditSearchResourceType>([
|
const searchableTypes = new Set<AuditSearchResourceType>([
|
||||||
'iot_card',
|
'iot_card',
|
||||||
@@ -75,12 +75,7 @@
|
|||||||
'order',
|
'order',
|
||||||
'refund'
|
'refund'
|
||||||
])
|
])
|
||||||
const eventId = computed(
|
const { hasAuth } = useAuth()
|
||||||
() =>
|
|
||||||
resolveInvestigationReference(props.refs?.event_id, {
|
|
||||||
currentId: props.currentEventId
|
|
||||||
}) || ''
|
|
||||||
)
|
|
||||||
const requestId = computed(() => resolveInvestigationReference(props.refs?.request_id) || '')
|
const requestId = computed(() => resolveInvestigationReference(props.refs?.request_id) || '')
|
||||||
const correlationId = computed(
|
const correlationId = computed(
|
||||||
() => resolveInvestigationReference(props.refs?.correlation_id) || ''
|
() => resolveInvestigationReference(props.refs?.correlation_id) || ''
|
||||||
@@ -111,21 +106,20 @@
|
|||||||
const stableIntegrationRefs = computed(() =>
|
const stableIntegrationRefs = computed(() =>
|
||||||
(props.refs?.integration_refs || []).filter((item) => Boolean(item.integration_id))
|
(props.refs?.integration_refs || []).filter((item) => Boolean(item.integration_id))
|
||||||
)
|
)
|
||||||
|
const visibleIntegrationRefs = computed(() =>
|
||||||
|
hasAuth(AUDIT_PERMISSIONS.auditEventIntegrationDetail) ? stableIntegrationRefs.value : []
|
||||||
|
)
|
||||||
const hasLinks = computed(() =>
|
const hasLinks = computed(() =>
|
||||||
Boolean(
|
Boolean(
|
||||||
eventId.value ||
|
actorRef.value ||
|
||||||
actorRef.value ||
|
|
||||||
requestId.value ||
|
requestId.value ||
|
||||||
correlationId.value ||
|
correlationId.value ||
|
||||||
(props.showResources &&
|
(props.showResources &&
|
||||||
(stableResourceRefs.value.length || searchableResourceRefs.value.length)) ||
|
(stableResourceRefs.value.length || searchableResourceRefs.value.length)) ||
|
||||||
stableIntegrationRefs.value.length
|
visibleIntegrationRefs.value.length
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const openEvent = () => {
|
|
||||||
if (eventId.value) openAuditInvestigation({ mode: 'event', id: eventId.value })
|
|
||||||
}
|
|
||||||
const openIntegration = (id: string) => openAuditInvestigation({ mode: 'integration', id })
|
const openIntegration = (id: string) => openAuditInvestigation({ mode: 'integration', id })
|
||||||
const openTimeline = (mode: 'request' | 'correlation', id: string) =>
|
const openTimeline = (mode: 'request' | 'correlation', id: string) =>
|
||||||
openAuditInvestigation({ mode, id })
|
openAuditInvestigation({ mode, id })
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ export const AUDIT_PERMISSIONS = {
|
|||||||
centerPage: 'audit:center_view',
|
centerPage: 'audit:center_view',
|
||||||
eventList: 'audit:event_list',
|
eventList: 'audit:event_list',
|
||||||
eventDetail: 'audit:event_detail',
|
eventDetail: 'audit:event_detail',
|
||||||
|
auditEventResourceTimeline: 'audit:audit_event_resource_timeline',
|
||||||
|
auditEventIntegrationDetail: 'audit:audit_event_integration_detail',
|
||||||
|
integrationDetailRequestTimeline: 'audit:integration_detail_request_timeline',
|
||||||
|
integrationDetailCorrelationTimeline: 'audit:integration_detail_correlation_timeline',
|
||||||
|
integrationDetailResourceTimeline: 'audit:integration_detail_resource_timeline',
|
||||||
actorTimeline: 'audit:actor_timeline',
|
actorTimeline: 'audit:actor_timeline',
|
||||||
resourceSearch: 'audit:resource_search',
|
resourceSearch: 'audit:resource_search',
|
||||||
resourceTimeline: 'audit:resource_timeline',
|
resourceTimeline: 'audit:resource_timeline',
|
||||||
@@ -11,16 +16,44 @@ export const AUDIT_PERMISSIONS = {
|
|||||||
financeTimeline: 'audit:finance_timeline',
|
financeTimeline: 'audit:finance_timeline',
|
||||||
riskPage: 'audit:risk_view',
|
riskPage: 'audit:risk_view',
|
||||||
riskEvents: 'audit:risk_events',
|
riskEvents: 'audit:risk_events',
|
||||||
|
riskEventDetail: 'audit:risk_event_detail',
|
||||||
integrationPage: 'audit:integration_view',
|
integrationPage: 'audit:integration_view',
|
||||||
integrationDetail: 'audit:integration_detail',
|
integrationDetail: 'audit:integration_detail',
|
||||||
agentActivity: 'audit:agent_resource_activity',
|
agentActivity: 'audit:agent_resource_activity',
|
||||||
enterpriseActivity: 'audit:enterprise_resource_activity',
|
enterpriseActivity: 'audit:enterprise_resource_activity',
|
||||||
|
agentCardActivity: 'audit:agent_card_activity',
|
||||||
|
agentDeviceActivity: 'audit:agent_device_activity',
|
||||||
|
enterpriseCardActivity: 'audit:enterprise_card_activity',
|
||||||
|
enterpriseDeviceActivity: 'audit:enterprise_device_activity',
|
||||||
|
agentExchangeActivity: 'audit:agent_exchange_activity',
|
||||||
cardEntry: 'audit:card_entry',
|
cardEntry: 'audit:card_entry',
|
||||||
deviceEntry: 'audit:device_entry',
|
deviceEntry: 'audit:device_entry',
|
||||||
|
exchangeEntry: 'audit:exchange_entry',
|
||||||
|
exchangeOldAssetEntry: 'audit:exchange_old_asset_entry',
|
||||||
|
exchangeNewAssetEntry: 'audit:exchange_new_asset_entry',
|
||||||
|
agentAssetAllocationActivity: 'audit:agent_asset_allocation_activity',
|
||||||
|
assetAllocationEntry: 'audit:asset_allocation_entry',
|
||||||
|
assetAllocationAssetEntry: 'audit:asset_allocation_asset_entry',
|
||||||
|
assetInfoEntry: 'audit:asset_info_entry',
|
||||||
|
agentAssetInfoCardActivity: 'audit:agent_asset_info_card_activity',
|
||||||
|
agentAssetInfoDeviceActivity: 'audit:agent_asset_info_device_activity',
|
||||||
|
enterpriseAssetInfoCardActivity: 'audit:enterprise_asset_info_card_activity',
|
||||||
|
enterpriseAssetInfoDeviceActivity: 'audit:enterprise_asset_info_device_activity',
|
||||||
|
assetInfoBindingCardEntry: 'audit:asset_info_binding_card_entry',
|
||||||
|
agentAssetInfoBindingCardActivity: 'audit:agent_asset_info_binding_card_activity',
|
||||||
|
enterpriseAssetInfoBindingCardActivity: 'audit:enterprise_asset_info_binding_card_activity',
|
||||||
|
assetInfoWalletFinance: 'audit:asset_info_wallet_finance',
|
||||||
|
assetInfoWalletAsset: 'audit:asset_info_wallet_asset',
|
||||||
shopEntry: 'audit:shop_entry',
|
shopEntry: 'audit:shop_entry',
|
||||||
enterpriseEntry: 'audit:enterprise_entry',
|
enterpriseEntry: 'audit:enterprise_entry',
|
||||||
orderEntry: 'audit:order_entry',
|
orderEntry: 'audit:order_entry',
|
||||||
|
orderFinanceEntry: 'audit:order_finance_entry',
|
||||||
|
agentRechargeEntry: 'audit:agent_recharge_entry',
|
||||||
|
agentRechargeFinanceEntry: 'audit:agent_recharge_finance_entry',
|
||||||
|
agentRechargeApprovalEntry: 'audit:agent_recharge_approval_entry',
|
||||||
refundEntry: 'audit:refund_entry',
|
refundEntry: 'audit:refund_entry',
|
||||||
|
refundFinanceEntry: 'audit:refund_finance_entry',
|
||||||
|
refundApprovalEntry: 'audit:refund_approval_entry',
|
||||||
walletEntry: 'audit:wallet_entry'
|
walletEntry: 'audit:wallet_entry'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { loadingService } from '@/utils/ui'
|
|||||||
import { useCommon } from '@/composables/useCommon'
|
import { useCommon } from '@/composables/useCommon'
|
||||||
import { useWorktabStore } from '@/store/modules/worktab'
|
import { useWorktabStore } from '@/store/modules/worktab'
|
||||||
import { isInWhiteList, hasRoutePermission, isTokenValid, buildLoginRedirect } from './permission'
|
import { isInWhiteList, hasRoutePermission, isTokenValid, buildLoginRedirect } from './permission'
|
||||||
|
import { isPlatformAuditAccount } from '@/utils/business/auditAccess'
|
||||||
|
|
||||||
// 是否已注册动态路由
|
// 是否已注册动态路由
|
||||||
const isRouteRegistered = ref(false)
|
const isRouteRegistered = ref(false)
|
||||||
@@ -95,6 +96,14 @@ async function handleRouteGuard(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
to.path.startsWith('/audit') &&
|
||||||
|
!isPlatformAuditAccount(userStore.info.user_type, userStore.isSuperAdmin)
|
||||||
|
) {
|
||||||
|
next(RoutesAlias.Exception403 || '/exception/403')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 处理动态路由注册
|
// 处理动态路由注册
|
||||||
if (!isRouteRegistered.value && userStore.isLogin) {
|
if (!isRouteRegistered.value && userStore.isLogin) {
|
||||||
await handleDynamicRoutes(to, router, next)
|
await handleDynamicRoutes(to, router, next)
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export const useUserStore = defineStore(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否是超级管理员
|
// 检查是否是超级管理员
|
||||||
const isSuperAdmin = computed(() => info.value.user_type === 1)
|
const isSuperAdmin = computed(() => Number(info.value.user_type) === 1)
|
||||||
|
|
||||||
// 检查是否有某个权限
|
// 检查是否有某个权限
|
||||||
const hasPermission = (permission: string): boolean => {
|
const hasPermission = (permission: string): boolean => {
|
||||||
|
|||||||
14
src/utils/business/auditAccess.ts
Normal file
14
src/utils/business/auditAccess.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import type { AuditInvestigationTarget } from '@/components/business/audit/types'
|
||||||
|
|
||||||
|
export const isPlatformAuditAccount = (userType?: number | null, isSuperAdmin = false) =>
|
||||||
|
isSuperAdmin || [1, 2].includes(Number(userType))
|
||||||
|
|
||||||
|
export const canLoadAuditInvestigation = (
|
||||||
|
target: AuditInvestigationTarget,
|
||||||
|
userType?: number | null,
|
||||||
|
isSuperAdmin = false
|
||||||
|
) => {
|
||||||
|
if (target.mode === 'agent') return Number(userType) === 3
|
||||||
|
if (target.mode === 'enterprise') return Number(userType) === 4
|
||||||
|
return isPlatformAuditAccount(userType, isSuperAdmin)
|
||||||
|
}
|
||||||
@@ -337,11 +337,14 @@
|
|||||||
} from '@/config/constants'
|
} from '@/config/constants'
|
||||||
import { AUDIT_PERMISSIONS } from '@/config/constants/audit'
|
import { AUDIT_PERMISSIONS } from '@/config/constants/audit'
|
||||||
import { resolveAuditResourceTarget } from '@/utils/business/auditNavigation'
|
import { resolveAuditResourceTarget } from '@/utils/business/auditNavigation'
|
||||||
|
import { isPlatformAuditAccount } from '@/utils/business/auditAccess'
|
||||||
import { openAuditInvestigation } from '@/components/business/audit/investigationController'
|
import { openAuditInvestigation } from '@/components/business/audit/investigationController'
|
||||||
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
|
||||||
defineOptions({ name: 'Account' }) // 定义组件名称,用于 KeepAlive 缓存控制
|
defineOptions({ name: 'Account' }) // 定义组件名称,用于 KeepAlive 缓存控制
|
||||||
|
|
||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
|
const userStore = useUserStore()
|
||||||
const canModifyAccountStatus = hasAuth('account:modify_status')
|
const canModifyAccountStatus = hasAuth('account:modify_status')
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
@@ -673,8 +676,12 @@
|
|||||||
const getActions = (row: any) => {
|
const getActions = (row: any) => {
|
||||||
const actions: any[] = []
|
const actions: any[] = []
|
||||||
|
|
||||||
if (hasAuth(AUDIT_PERMISSIONS.resourceTimeline)) {
|
if (
|
||||||
|
isPlatformAuditAccount(userStore.info.user_type, userStore.isSuperAdmin) &&
|
||||||
|
hasAuth(AUDIT_PERMISSIONS.resourceTimeline)
|
||||||
|
) {
|
||||||
const auditTarget = resolveAuditResourceTarget({
|
const auditTarget = resolveAuditResourceTarget({
|
||||||
|
userType: userStore.info.user_type,
|
||||||
resourceType: 'account',
|
resourceType: 'account',
|
||||||
internalId: row.id
|
internalId: row.id
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -505,16 +505,19 @@
|
|||||||
const getActions = (row: EnterpriseItem) => {
|
const getActions = (row: EnterpriseItem) => {
|
||||||
const actions: any[] = []
|
const actions: any[] = []
|
||||||
|
|
||||||
if (hasAuth(AUDIT_PERMISSIONS.enterpriseEntry)) {
|
const userType = Number(userStore.getUserInfo.user_type)
|
||||||
|
const auditPermission =
|
||||||
|
userType === 3 ? AUDIT_PERMISSIONS.agentActivity : AUDIT_PERMISSIONS.enterpriseEntry
|
||||||
|
if (hasAuth(auditPermission)) {
|
||||||
const auditTarget = resolveAuditResourceTarget({
|
const auditTarget = resolveAuditResourceTarget({
|
||||||
userType: userStore.getUserInfo.user_type,
|
userType,
|
||||||
resourceType: 'enterprise',
|
resourceType: 'enterprise',
|
||||||
internalId: row.id,
|
internalId: row.id,
|
||||||
businessIdentifier: row.enterprise_code
|
businessIdentifier: row.enterprise_code
|
||||||
})
|
})
|
||||||
if (auditTarget)
|
if (auditTarget)
|
||||||
actions.push({
|
actions.push({
|
||||||
label: '审计记录',
|
label: userType === 3 ? '活动记录' : '审计记录',
|
||||||
handler: () => openAuditInvestigation(auditTarget),
|
handler: () => openAuditInvestigation(auditTarget),
|
||||||
type: 'primary'
|
type: 'primary'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -314,7 +314,7 @@
|
|||||||
{{ scope.row.gateway_extend || '-' }}
|
{{ scope.row.gateway_extend || '-' }}
|
||||||
</template>
|
</template>
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
<ElTableColumn label="操作" width="280" fixed="right">
|
<ElTableColumn label="操作" width="260" fixed="right">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<!-- 根据网络状态显示启用或停用按钮 -->
|
<!-- 根据网络状态显示启用或停用按钮 -->
|
||||||
<ElButton
|
<ElButton
|
||||||
@@ -357,15 +357,6 @@
|
|||||||
>
|
>
|
||||||
卡审计
|
卡审计
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
|
||||||
v-if="getBindingAuditTarget(scope.row)"
|
|
||||||
type="primary"
|
|
||||||
size="small"
|
|
||||||
link
|
|
||||||
@click="openBindingAudit(scope.row)"
|
|
||||||
>
|
|
||||||
绑定审计
|
|
||||||
</ElButton>
|
|
||||||
</template>
|
</template>
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
</ElTable>
|
</ElTable>
|
||||||
@@ -616,7 +607,6 @@
|
|||||||
|
|
||||||
// Props
|
// Props
|
||||||
interface BindingCard {
|
interface BindingCard {
|
||||||
id: number
|
|
||||||
card_id?: number
|
card_id?: number
|
||||||
iccid: string
|
iccid: string
|
||||||
msisdn?: string
|
msisdn?: string
|
||||||
@@ -747,14 +737,17 @@
|
|||||||
const isSubjectActivityView = computed(() => [3, 4].includes(Number(userStore.info.user_type)))
|
const isSubjectActivityView = computed(() => [3, 4].includes(Number(userStore.info.user_type)))
|
||||||
const auditTarget = computed(() => {
|
const auditTarget = computed(() => {
|
||||||
const userType = Number(userStore.info.user_type)
|
const userType = Number(userStore.info.user_type)
|
||||||
|
const isCard = props.cardInfo.asset_type === 'card'
|
||||||
const permission =
|
const permission =
|
||||||
userType === 3
|
userType === 3
|
||||||
? AUDIT_PERMISSIONS.agentActivity
|
? isCard
|
||||||
|
? AUDIT_PERMISSIONS.agentAssetInfoCardActivity
|
||||||
|
: AUDIT_PERMISSIONS.agentAssetInfoDeviceActivity
|
||||||
: userType === 4
|
: userType === 4
|
||||||
? AUDIT_PERMISSIONS.enterpriseActivity
|
? isCard
|
||||||
: props.cardInfo.asset_type === 'card'
|
? AUDIT_PERMISSIONS.enterpriseAssetInfoCardActivity
|
||||||
? AUDIT_PERMISSIONS.cardEntry
|
: AUDIT_PERMISSIONS.enterpriseAssetInfoDeviceActivity
|
||||||
: AUDIT_PERMISSIONS.deviceEntry
|
: AUDIT_PERMISSIONS.assetInfoEntry
|
||||||
if (!hasAuth(permission)) return null
|
if (!hasAuth(permission)) return null
|
||||||
return resolveAuditResourceTarget({
|
return resolveAuditResourceTarget({
|
||||||
userType,
|
userType,
|
||||||
@@ -771,10 +764,10 @@
|
|||||||
const userType = Number(userStore.info.user_type)
|
const userType = Number(userStore.info.user_type)
|
||||||
const permission =
|
const permission =
|
||||||
userType === 3
|
userType === 3
|
||||||
? AUDIT_PERMISSIONS.agentActivity
|
? AUDIT_PERMISSIONS.agentAssetInfoBindingCardActivity
|
||||||
: userType === 4
|
: userType === 4
|
||||||
? AUDIT_PERMISSIONS.enterpriseActivity
|
? AUDIT_PERMISSIONS.enterpriseAssetInfoBindingCardActivity
|
||||||
: AUDIT_PERMISSIONS.cardEntry
|
: AUDIT_PERMISSIONS.assetInfoBindingCardEntry
|
||||||
if (!hasAuth(permission)) return null
|
if (!hasAuth(permission)) return null
|
||||||
return resolveAuditResourceTarget({
|
return resolveAuditResourceTarget({
|
||||||
userType,
|
userType,
|
||||||
@@ -783,25 +776,10 @@
|
|||||||
businessIdentifier: card.iccid
|
businessIdentifier: card.iccid
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const getBindingAuditTarget = (card: BindingCard) => {
|
|
||||||
if (
|
|
||||||
![1, 2].includes(Number(userStore.info.user_type)) ||
|
|
||||||
!hasAuth(AUDIT_PERMISSIONS.resourceTimeline)
|
|
||||||
)
|
|
||||||
return null
|
|
||||||
return resolveAuditResourceTarget({
|
|
||||||
resourceType: 'device_sim_binding',
|
|
||||||
internalId: card.id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const openBindingCardAudit = (card: BindingCard) => {
|
const openBindingCardAudit = (card: BindingCard) => {
|
||||||
const target = getBindingCardAuditTarget(card)
|
const target = getBindingCardAuditTarget(card)
|
||||||
if (target) openAuditInvestigation(target)
|
if (target) openAuditInvestigation(target)
|
||||||
}
|
}
|
||||||
const openBindingAudit = (card: BindingCard) => {
|
|
||||||
const target = getBindingAuditTarget(card)
|
|
||||||
if (target) openAuditInvestigation(target)
|
|
||||||
}
|
|
||||||
const { width } = useWindowSize()
|
const { width } = useWindowSize()
|
||||||
|
|
||||||
const CARD_MONTH_USAGE_PERMISSION = 'asset_info:view_card_month_usage'
|
const CARD_MONTH_USAGE_PERMISSION = 'asset_info:view_card_month_usage'
|
||||||
|
|||||||
@@ -296,7 +296,7 @@
|
|||||||
const descriptionsColumn = computed(() => {
|
const descriptionsColumn = computed(() => {
|
||||||
if (width.value <= 640) return 1
|
if (width.value <= 640) return 1
|
||||||
if (width.value <= 1024) return 2
|
if (width.value <= 1024) return 2
|
||||||
return 4
|
return 3
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|||||||
@@ -45,15 +45,20 @@
|
|||||||
<ElButton type="primary" @click="handleQuery">查询</ElButton>
|
<ElButton type="primary" @click="handleQuery">查询</ElButton>
|
||||||
<ElButton @click="handleReset()">重置</ElButton>
|
<ElButton @click="handleReset()">重置</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
v-if="walletInfo?.wallet_id && hasAuth(AUDIT_PERMISSIONS.financeTimeline)"
|
v-if="
|
||||||
|
isPlatformAuditUser &&
|
||||||
|
walletInfo?.wallet_id &&
|
||||||
|
hasAuth(AUDIT_PERMISSIONS.assetInfoWalletFinance)
|
||||||
|
"
|
||||||
@click="openWalletAudit"
|
@click="openWalletAudit"
|
||||||
>资金链路</ElButton
|
>资金链路</ElButton
|
||||||
>
|
>
|
||||||
<ElButton
|
<ElButton
|
||||||
v-if="
|
v-if="
|
||||||
|
isPlatformAuditUser &&
|
||||||
walletInfo?.resource_type &&
|
walletInfo?.resource_type &&
|
||||||
walletInfo?.resource_id &&
|
walletInfo?.resource_id &&
|
||||||
hasAuth(AUDIT_PERMISSIONS.resourceTimeline)
|
hasAuth(AUDIT_PERMISSIONS.assetInfoWalletAsset)
|
||||||
"
|
"
|
||||||
@click="openAssetAudit"
|
@click="openAssetAudit"
|
||||||
>资产审计</ElButton
|
>资产审计</ElButton
|
||||||
@@ -182,7 +187,9 @@
|
|||||||
TransactionType
|
TransactionType
|
||||||
} from '@/types/api'
|
} from '@/types/api'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { useUserStore } from '@/store/modules/user'
|
||||||
import { AUDIT_PERMISSIONS } from '@/config/constants/audit'
|
import { AUDIT_PERMISSIONS } from '@/config/constants/audit'
|
||||||
|
import { isPlatformAuditAccount } from '@/utils/business/auditAccess'
|
||||||
import {
|
import {
|
||||||
resolveAuditResourceTarget,
|
resolveAuditResourceTarget,
|
||||||
resolveFinanceAuditTarget
|
resolveFinanceAuditTarget
|
||||||
@@ -204,6 +211,10 @@
|
|||||||
boundDeviceNo: ''
|
boundDeviceNo: ''
|
||||||
})
|
})
|
||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const isPlatformAuditUser = computed(() =>
|
||||||
|
isPlatformAuditAccount(userStore.info.user_type, userStore.isSuperAdmin)
|
||||||
|
)
|
||||||
|
|
||||||
const openWalletAudit = () => {
|
const openWalletAudit = () => {
|
||||||
const target = resolveFinanceAuditTarget('wallet_id', props.walletInfo?.wallet_id)
|
const target = resolveFinanceAuditTarget('wallet_id', props.walletInfo?.wallet_id)
|
||||||
|
|||||||
@@ -3161,9 +3161,9 @@
|
|||||||
const userType = Number(userStore.getUserInfo.user_type)
|
const userType = Number(userStore.getUserInfo.user_type)
|
||||||
const auditPermission =
|
const auditPermission =
|
||||||
userType === 3
|
userType === 3
|
||||||
? AUDIT_PERMISSIONS.agentActivity
|
? AUDIT_PERMISSIONS.agentDeviceActivity
|
||||||
: userType === 4
|
: userType === 4
|
||||||
? AUDIT_PERMISSIONS.enterpriseActivity
|
? AUDIT_PERMISSIONS.enterpriseDeviceActivity
|
||||||
: AUDIT_PERMISSIONS.deviceEntry
|
: AUDIT_PERMISSIONS.deviceEntry
|
||||||
if (hasAuth(auditPermission)) {
|
if (hasAuth(auditPermission)) {
|
||||||
const auditTarget = resolveAuditResourceTarget({
|
const auditTarget = resolveAuditResourceTarget({
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
{{ userType === 3 ? '活动记录' : '换货审计' }}
|
{{ userType === 3 ? '活动记录' : '换货审计' }}
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
v-if="oldAssetAuditTarget && hasAuth(AUDIT_PERMISSIONS.resourceTimeline)"
|
v-if="oldAssetAuditTarget && hasAuth(AUDIT_PERMISSIONS.exchangeOldAssetEntry)"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
@click="openAuditInvestigation(oldAssetAuditTarget)"
|
@click="openAuditInvestigation(oldAssetAuditTarget)"
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
旧资产审计
|
旧资产审计
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
v-if="newAssetAuditTarget && hasAuth(AUDIT_PERMISSIONS.resourceTimeline)"
|
v-if="newAssetAuditTarget && hasAuth(AUDIT_PERMISSIONS.exchangeNewAssetEntry)"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
@click="openAuditInvestigation(newAssetAuditTarget)"
|
@click="openAuditInvestigation(newAssetAuditTarget)"
|
||||||
@@ -77,10 +77,10 @@
|
|||||||
const exchangeId = ref<number>(0)
|
const exchangeId = ref<number>(0)
|
||||||
const userType = computed(() => Number(userStore.info.user_type))
|
const userType = computed(() => Number(userStore.info.user_type))
|
||||||
const exchangeAuditPermission = computed(() =>
|
const exchangeAuditPermission = computed(() =>
|
||||||
userType.value === 3 ? AUDIT_PERMISSIONS.agentActivity : AUDIT_PERMISSIONS.resourceTimeline
|
userType.value === 3 ? AUDIT_PERMISSIONS.agentExchangeActivity : AUDIT_PERMISSIONS.exchangeEntry
|
||||||
)
|
)
|
||||||
const exchangeAuditTarget = computed(() => {
|
const exchangeAuditTarget = computed(() => {
|
||||||
if (!exchangeDetail.value || userType.value === 4) return null
|
if (!exchangeDetail.value || ![1, 2, 3].includes(userType.value)) return null
|
||||||
return resolveAuditResourceTarget({
|
return resolveAuditResourceTarget({
|
||||||
userType: userType.value,
|
userType: userType.value,
|
||||||
resourceType: 'exchange_order',
|
resourceType: 'exchange_order',
|
||||||
|
|||||||
@@ -1555,8 +1555,8 @@
|
|||||||
const flowType = row.flow_type || 'shipping' // 历史数据兼容
|
const flowType = row.flow_type || 'shipping' // 历史数据兼容
|
||||||
const userType = Number(userStore.info.user_type)
|
const userType = Number(userStore.info.user_type)
|
||||||
const entryPermission =
|
const entryPermission =
|
||||||
userType === 3 ? AUDIT_PERMISSIONS.agentActivity : AUDIT_PERMISSIONS.resourceTimeline
|
userType === 3 ? AUDIT_PERMISSIONS.agentExchangeActivity : AUDIT_PERMISSIONS.exchangeEntry
|
||||||
if (userType !== 4 && hasAuth(entryPermission)) {
|
if ([1, 2, 3].includes(userType) && hasAuth(entryPermission)) {
|
||||||
const exchangeTarget = resolveAuditResourceTarget({
|
const exchangeTarget = resolveAuditResourceTarget({
|
||||||
userType,
|
userType,
|
||||||
resourceType: 'exchange_order',
|
resourceType: 'exchange_order',
|
||||||
@@ -1574,7 +1574,7 @@
|
|||||||
resourceType: row.old_asset_type,
|
resourceType: row.old_asset_type,
|
||||||
internalId: row.old_asset_id
|
internalId: row.old_asset_id
|
||||||
})
|
})
|
||||||
if (oldAssetTarget)
|
if (oldAssetTarget && hasAuth(AUDIT_PERMISSIONS.exchangeOldAssetEntry))
|
||||||
actions.push({
|
actions.push({
|
||||||
label: '旧资产审计',
|
label: '旧资产审计',
|
||||||
handler: () => openAuditInvestigation(oldAssetTarget),
|
handler: () => openAuditInvestigation(oldAssetTarget),
|
||||||
@@ -1584,7 +1584,7 @@
|
|||||||
resourceType: row.new_asset_type || '',
|
resourceType: row.new_asset_type || '',
|
||||||
internalId: row.new_asset_id
|
internalId: row.new_asset_id
|
||||||
})
|
})
|
||||||
if (newAssetTarget)
|
if (newAssetTarget && hasAuth(AUDIT_PERMISSIONS.exchangeNewAssetEntry))
|
||||||
actions.push({
|
actions.push({
|
||||||
label: '新资产审计',
|
label: '新资产审计',
|
||||||
handler: () => openAuditInvestigation(newAssetTarget),
|
handler: () => openAuditInvestigation(newAssetTarget),
|
||||||
|
|||||||
@@ -2315,9 +2315,9 @@
|
|||||||
const userType = Number(userStore.getUserInfo.user_type)
|
const userType = Number(userStore.getUserInfo.user_type)
|
||||||
const auditPermission =
|
const auditPermission =
|
||||||
userType === 3
|
userType === 3
|
||||||
? AUDIT_PERMISSIONS.agentActivity
|
? AUDIT_PERMISSIONS.agentCardActivity
|
||||||
: userType === 4
|
: userType === 4
|
||||||
? AUDIT_PERMISSIONS.enterpriseActivity
|
? AUDIT_PERMISSIONS.enterpriseCardActivity
|
||||||
: AUDIT_PERMISSIONS.cardEntry
|
: AUDIT_PERMISSIONS.cardEntry
|
||||||
if (hasAuth(auditPermission)) {
|
if (hasAuth(auditPermission)) {
|
||||||
const auditTarget = resolveAuditResourceTarget({
|
const auditTarget = resolveAuditResourceTarget({
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
{{ userType === 3 ? '活动记录' : '分配审计' }}
|
{{ userType === 3 ? '活动记录' : '分配审计' }}
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
v-if="assetAuditTarget && hasAuth(AUDIT_PERMISSIONS.resourceTimeline)"
|
v-if="assetAuditTarget && hasAuth(AUDIT_PERMISSIONS.assetAllocationAssetEntry)"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
@click="openAuditInvestigation(assetAuditTarget)"
|
@click="openAuditInvestigation(assetAuditTarget)"
|
||||||
@@ -67,10 +67,12 @@
|
|||||||
const detailData = ref<AssetAllocationRecord | null>(null)
|
const detailData = ref<AssetAllocationRecord | null>(null)
|
||||||
const userType = computed(() => Number(userStore.info.user_type))
|
const userType = computed(() => Number(userStore.info.user_type))
|
||||||
const recordAuditPermission = computed(() =>
|
const recordAuditPermission = computed(() =>
|
||||||
userType.value === 3 ? AUDIT_PERMISSIONS.agentActivity : AUDIT_PERMISSIONS.resourceTimeline
|
userType.value === 3
|
||||||
|
? AUDIT_PERMISSIONS.agentAssetAllocationActivity
|
||||||
|
: AUDIT_PERMISSIONS.assetAllocationEntry
|
||||||
)
|
)
|
||||||
const recordAuditTarget = computed(() => {
|
const recordAuditTarget = computed(() => {
|
||||||
if (!detailData.value || userType.value === 4) return null
|
if (!detailData.value || ![1, 2, 3].includes(userType.value)) return null
|
||||||
return resolveAuditResourceTarget({
|
return resolveAuditResourceTarget({
|
||||||
userType: userType.value,
|
userType: userType.value,
|
||||||
resourceType: 'asset_allocation_record',
|
resourceType: 'asset_allocation_record',
|
||||||
|
|||||||
@@ -232,8 +232,10 @@
|
|||||||
const actions: Array<{ label: string; handler: () => void; type: 'primary' }> = []
|
const actions: Array<{ label: string; handler: () => void; type: 'primary' }> = []
|
||||||
const userType = Number(userStore.info.user_type)
|
const userType = Number(userStore.info.user_type)
|
||||||
const entryPermission =
|
const entryPermission =
|
||||||
userType === 3 ? AUDIT_PERMISSIONS.agentActivity : AUDIT_PERMISSIONS.resourceTimeline
|
userType === 3
|
||||||
if (!hasAuth(entryPermission) || userType === 4) return actions
|
? AUDIT_PERMISSIONS.agentAssetAllocationActivity
|
||||||
|
: AUDIT_PERMISSIONS.assetAllocationEntry
|
||||||
|
if (!hasAuth(entryPermission) || ![1, 2, 3].includes(userType)) return actions
|
||||||
const recordTarget = resolveAuditResourceTarget({
|
const recordTarget = resolveAuditResourceTarget({
|
||||||
userType,
|
userType,
|
||||||
resourceType: 'asset_allocation_record',
|
resourceType: 'asset_allocation_record',
|
||||||
@@ -252,7 +254,7 @@
|
|||||||
resourceType: row.asset_type,
|
resourceType: row.asset_type,
|
||||||
internalId: row.asset_id
|
internalId: row.asset_id
|
||||||
})
|
})
|
||||||
if (assetTarget)
|
if (assetTarget && hasAuth(AUDIT_PERMISSIONS.assetAllocationAssetEntry))
|
||||||
actions.push({
|
actions.push({
|
||||||
label: '资产审计',
|
label: '资产审计',
|
||||||
handler: () => openAuditInvestigation(assetTarget),
|
handler: () => openAuditInvestigation(assetTarget),
|
||||||
|
|||||||
@@ -96,10 +96,7 @@
|
|||||||
</ElCard>
|
</ElCard>
|
||||||
<ElCard shadow="never" class="section-card"
|
<ElCard shadow="never" class="section-card"
|
||||||
><template #header>调查引用</template
|
><template #header>调查引用</template
|
||||||
><AuditInvestigationLinks
|
><AuditInvestigationLinks :refs="detail.investigation_refs" :show-resources="false"
|
||||||
:refs="detail.investigation_refs"
|
|
||||||
:current-event-id="detail.event_id"
|
|
||||||
:show-resources="false"
|
|
||||||
/></ElCard>
|
/></ElCard>
|
||||||
<ElCard shadow="never" class="section-card">
|
<ElCard shadow="never" class="section-card">
|
||||||
<template #header>相关资源与快照</template>
|
<template #header>相关资源与快照</template>
|
||||||
@@ -143,6 +140,7 @@
|
|||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<ElButton link type="primary" @click="openSnapshotDialog(row)">查看快照</ElButton>
|
<ElButton link type="primary" @click="openSnapshotDialog(row)">查看快照</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
|
v-if="hasAuth(AUDIT_PERMISSIONS.auditEventResourceTimeline)"
|
||||||
link
|
link
|
||||||
type="primary"
|
type="primary"
|
||||||
:disabled="!row.resource_id"
|
:disabled="!row.resource_id"
|
||||||
@@ -217,6 +215,8 @@
|
|||||||
} from '@/utils/business/audit'
|
} from '@/utils/business/audit'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { AUDIT_PERMISSIONS } from '@/config/constants/audit'
|
||||||
import AuditInvestigationLinks from '@/components/business/audit/AuditInvestigationLinks.vue'
|
import AuditInvestigationLinks from '@/components/business/audit/AuditInvestigationLinks.vue'
|
||||||
import { openAuditInvestigation } from '@/components/business/audit/investigationController'
|
import { openAuditInvestigation } from '@/components/business/audit/investigationController'
|
||||||
|
|
||||||
@@ -224,6 +224,7 @@
|
|||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
const { hasAuth } = useAuth()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const detail = ref<AuditEventDetail>()
|
const detail = ref<AuditEventDetail>()
|
||||||
const snapshotDialogVisible = ref(false)
|
const snapshotDialogVisible = ref(false)
|
||||||
|
|||||||
@@ -171,8 +171,7 @@
|
|||||||
{ label: '操作者', prop: 'actor_name' },
|
{ label: '操作者', prop: 'actor_name' },
|
||||||
{ label: '来源', prop: 'source' },
|
{ label: '来源', prop: 'source' },
|
||||||
{ label: '结果', prop: 'result' },
|
{ label: '结果', prop: 'result' },
|
||||||
{ label: '风险', prop: 'risk_level' },
|
{ label: '风险', prop: 'risk_level' }
|
||||||
{ label: '操作', prop: 'actions' }
|
|
||||||
]
|
]
|
||||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||||
{
|
{
|
||||||
@@ -185,7 +184,12 @@
|
|||||||
prop: 'action_name',
|
prop: 'action_name',
|
||||||
label: '动作',
|
label: '动作',
|
||||||
minWidth: 220,
|
minWidth: 220,
|
||||||
formatter: (row: AuditEventView) => row.action_name || '-'
|
formatter: (row: AuditEventView) =>
|
||||||
|
h(
|
||||||
|
ElButton,
|
||||||
|
{ type: 'primary', link: true, onClick: () => openDetail(row) },
|
||||||
|
() => row.action_name || '-'
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{ prop: 'summary', label: '摘要', minWidth: 240, showOverflowTooltip: true },
|
{ prop: 'summary', label: '摘要', minWidth: 240, showOverflowTooltip: true },
|
||||||
{
|
{
|
||||||
@@ -234,18 +238,6 @@
|
|||||||
{ type: auditRiskMeta[row.risk_level]?.type || 'info', effect: 'plain' },
|
{ type: auditRiskMeta[row.risk_level]?.type || 'info', effect: 'plain' },
|
||||||
() => auditRiskMeta[row.risk_level]?.label || row.risk_level
|
() => auditRiskMeta[row.risk_level]?.label || row.risk_level
|
||||||
)
|
)
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'actions',
|
|
||||||
label: '操作',
|
|
||||||
width: 110,
|
|
||||||
fixed: 'right',
|
|
||||||
formatter: (row: AuditEventView) =>
|
|
||||||
h(
|
|
||||||
ElButton,
|
|
||||||
{ type: 'primary', link: true, onClick: () => openDetail(row) },
|
|
||||||
() => '查看详情'
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
@@ -94,28 +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="requestReference"
|
v-if="canViewRequestTimeline"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
@click="openAuditInvestigation({ mode: 'request', id: requestReference })"
|
@click="openAuditInvestigation({ mode: 'request', id: requestReference || '' })"
|
||||||
>
|
>
|
||||||
请求链路
|
请求链路
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
v-if="correlationReference"
|
v-if="canViewCorrelationTimeline"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
@click="
|
@click="
|
||||||
openAuditInvestigation({
|
openAuditInvestigation({
|
||||||
mode: 'correlation',
|
mode: 'correlation',
|
||||||
id: correlationReference
|
id: correlationReference || ''
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
业务关联链路
|
业务关联链路
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
v-if="detail.resource?.type && resourceReference"
|
v-if="canViewResourceTimeline"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
@click="openResourceTimeline"
|
@click="openResourceTimeline"
|
||||||
@@ -257,11 +257,14 @@
|
|||||||
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'
|
import { resolveInvestigationReference } from '@/utils/business/auditNavigation'
|
||||||
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { AUDIT_PERMISSIONS } from '@/config/constants/audit'
|
||||||
|
|
||||||
defineOptions({ name: 'AuditIntegrationDetail' })
|
defineOptions({ name: 'AuditIntegrationDetail' })
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const { hasAuth } = useAuth()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const detail = ref<IntegrationDetailResponse>()
|
const detail = ref<IntegrationDetailResponse>()
|
||||||
|
|
||||||
@@ -284,11 +287,25 @@
|
|||||||
fidelity: detail.value?.fidelity.resource_id_available
|
fidelity: detail.value?.fidelity.resource_id_available
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
const canViewRequestTimeline = computed(
|
||||||
|
() =>
|
||||||
|
Boolean(requestReference.value) && hasAuth(AUDIT_PERMISSIONS.integrationDetailRequestTimeline)
|
||||||
|
)
|
||||||
|
const canViewCorrelationTimeline = computed(
|
||||||
|
() =>
|
||||||
|
Boolean(correlationReference.value) &&
|
||||||
|
hasAuth(AUDIT_PERMISSIONS.integrationDetailCorrelationTimeline)
|
||||||
|
)
|
||||||
|
const canViewResourceTimeline = computed(
|
||||||
|
() =>
|
||||||
|
Boolean(detail.value?.resource?.type && resourceReference.value) &&
|
||||||
|
hasAuth(AUDIT_PERMISSIONS.integrationDetailResourceTimeline)
|
||||||
|
)
|
||||||
const hasAssociations = computed(() =>
|
const hasAssociations = computed(() =>
|
||||||
Boolean(
|
Boolean(
|
||||||
requestReference.value ||
|
canViewRequestTimeline.value ||
|
||||||
correlationReference.value ||
|
canViewCorrelationTimeline.value ||
|
||||||
(detail.value?.resource?.type && resourceReference.value)
|
canViewResourceTimeline.value
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
const hasJsonContent = (value?: Record<string, unknown> | null) =>
|
const hasJsonContent = (value?: Record<string, unknown> | null) =>
|
||||||
|
|||||||
@@ -180,9 +180,16 @@
|
|||||||
><ElTableColumn label="方向" prop="direction_name" width="90" /><ElTableColumn
|
><ElTableColumn label="方向" prop="direction_name" width="90" /><ElTableColumn
|
||||||
label="操作"
|
label="操作"
|
||||||
min-width="190"
|
min-width="190"
|
||||||
><template #default="{ row }">{{
|
><template #default="{ row }"
|
||||||
row.operation_name || row.operation
|
><ElButton
|
||||||
}}</template></ElTableColumn
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="
|
||||||
|
router.push(`/audit/integrations/${encodeURIComponent(row.integration_id)}`)
|
||||||
|
"
|
||||||
|
>{{ row.operation_name || row.operation || '-' }}</ElButton
|
||||||
|
></template
|
||||||
|
></ElTableColumn
|
||||||
><ElTableColumn label="结果" width="120"
|
><ElTableColumn label="结果" width="120"
|
||||||
><template #default="{ row }"
|
><template #default="{ row }"
|
||||||
><ElTag :type="categoryMeta(row).type">{{
|
><ElTag :type="categoryMeta(row).type">{{
|
||||||
@@ -197,17 +204,6 @@
|
|||||||
><template #default="{ row }">{{
|
><template #default="{ row }">{{
|
||||||
row.resource?.key || row.resource?.id || '-'
|
row.resource?.key || row.resource?.id || '-'
|
||||||
}}</template></ElTableColumn
|
}}</template></ElTableColumn
|
||||||
><ElTableColumn label="操作" width="100" fixed="right"
|
|
||||||
><template #default="{ row }"
|
|
||||||
><ElButton
|
|
||||||
link
|
|
||||||
type="primary"
|
|
||||||
@click="
|
|
||||||
router.push(`/audit/integrations/${encodeURIComponent(row.integration_id)}`)
|
|
||||||
"
|
|
||||||
>查看详情</ElButton
|
|
||||||
></template
|
|
||||||
></ElTableColumn
|
|
||||||
></template
|
></template
|
||||||
></ArtTable
|
></ArtTable
|
||||||
></ElCard
|
></ElCard
|
||||||
|
|||||||
@@ -137,6 +137,7 @@
|
|||||||
><div class="risk-event-table">
|
><div class="risk-event-table">
|
||||||
<AuditEventTable
|
<AuditEventTable
|
||||||
:items="events"
|
:items="events"
|
||||||
|
:detail-enabled="hasAuth(AUDIT_PERMISSIONS.riskEventDetail)"
|
||||||
@detail="(row) => router.push(`/audit/events/${encodeURIComponent(row.event_id)}`)"
|
@detail="(row) => router.push(`/audit/events/${encodeURIComponent(row.event_id)}`)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -167,6 +168,8 @@
|
|||||||
WarningFilled
|
WarningFilled
|
||||||
} from '@element-plus/icons-vue'
|
} from '@element-plus/icons-vue'
|
||||||
import { AuditService } from '@/api/modules'
|
import { AuditService } from '@/api/modules'
|
||||||
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { AUDIT_PERMISSIONS } from '@/config/constants/audit'
|
||||||
import type {
|
import type {
|
||||||
AuditEventView,
|
AuditEventView,
|
||||||
AuditNamedCount,
|
AuditNamedCount,
|
||||||
@@ -178,6 +181,7 @@
|
|||||||
|
|
||||||
defineOptions({ name: 'AuditRisks' })
|
defineOptions({ name: 'AuditRisks' })
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const { hasAuth } = useAuth()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const headerColumns = ref([])
|
const headerColumns = ref([])
|
||||||
const dateRange = ref<string[]>([])
|
const dateRange = ref<string[]>([])
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
</ElButton>
|
</ElButton>
|
||||||
<h2 class="detail-title">{{ pageTitle }}</h2>
|
<h2 class="detail-title">{{ pageTitle }}</h2>
|
||||||
<ElButton
|
<ElButton
|
||||||
v-if="detailData && isPlatformUser && hasAuth(AUDIT_PERMISSIONS.resourceTimeline)"
|
v-if="detailData && isPlatformUser && hasAuth(AUDIT_PERMISSIONS.agentRechargeEntry)"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
@click="openRechargeAudit"
|
@click="openRechargeAudit"
|
||||||
@@ -19,7 +19,9 @@
|
|||||||
审计记录
|
审计记录
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
v-if="detailData && isPlatformUser && hasAuth(AUDIT_PERMISSIONS.financeTimeline)"
|
v-if="
|
||||||
|
detailData && isPlatformUser && hasAuth(AUDIT_PERMISSIONS.agentRechargeFinanceEntry)
|
||||||
|
"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
@click="openRechargeFinance"
|
@click="openRechargeFinance"
|
||||||
@@ -30,7 +32,7 @@
|
|||||||
v-if="
|
v-if="
|
||||||
detailData?.approval_instance_id &&
|
detailData?.approval_instance_id &&
|
||||||
isPlatformUser &&
|
isPlatformUser &&
|
||||||
hasAuth(AUDIT_PERMISSIONS.resourceTimeline)
|
hasAuth(AUDIT_PERMISSIONS.agentRechargeApprovalEntry)
|
||||||
"
|
"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
|
|||||||
@@ -777,7 +777,7 @@
|
|||||||
{
|
{
|
||||||
prop: 'payment_method',
|
prop: 'payment_method',
|
||||||
label: '支付方式',
|
label: '支付方式',
|
||||||
width: 120,
|
width: 140,
|
||||||
formatter: (row: AgentRecharge) => getPaymentMethodText(row.payment_method)
|
formatter: (row: AgentRecharge) => getPaymentMethodText(row.payment_method)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1316,7 +1316,7 @@
|
|||||||
onConfirmPayment: handleShowConfirmPay,
|
onConfirmPayment: handleShowConfirmPay,
|
||||||
onReject: handleShowReject
|
onReject: handleShowReject
|
||||||
})
|
})
|
||||||
if (isPlatformAccount.value && hasAuth(AUDIT_PERMISSIONS.resourceTimeline)) {
|
if (isPlatformAccount.value && hasAuth(AUDIT_PERMISSIONS.agentRechargeEntry)) {
|
||||||
const resourceTarget = resolveAuditResourceTarget({
|
const resourceTarget = resolveAuditResourceTarget({
|
||||||
resourceType: 'agent_recharge',
|
resourceType: 'agent_recharge',
|
||||||
internalId: row.id
|
internalId: row.id
|
||||||
@@ -1328,7 +1328,7 @@
|
|||||||
type: 'primary'
|
type: 'primary'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (isPlatformAccount.value && hasAuth(AUDIT_PERMISSIONS.financeTimeline)) {
|
if (isPlatformAccount.value && hasAuth(AUDIT_PERMISSIONS.agentRechargeFinanceEntry)) {
|
||||||
const financeTarget = resolveFinanceAuditTarget('recharge_id', row.id)
|
const financeTarget = resolveFinanceAuditTarget('recharge_id', row.id)
|
||||||
if (financeTarget)
|
if (financeTarget)
|
||||||
actions.unshift({
|
actions.unshift({
|
||||||
@@ -1337,7 +1337,7 @@
|
|||||||
type: 'primary'
|
type: 'primary'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (isPlatformAccount.value && hasAuth(AUDIT_PERMISSIONS.resourceTimeline)) {
|
if (isPlatformAccount.value && hasAuth(AUDIT_PERMISSIONS.agentRechargeApprovalEntry)) {
|
||||||
const approvalTarget = resolveAuditResourceTarget({
|
const approvalTarget = resolveAuditResourceTarget({
|
||||||
resourceType: 'approval_instance',
|
resourceType: 'approval_instance',
|
||||||
internalId: row.approval_instance_id
|
internalId: row.approval_instance_id
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
审计记录
|
审计记录
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
v-if="refund && isPlatformUser && hasAuth(AUDIT_PERMISSIONS.financeTimeline)"
|
v-if="refund && isPlatformUser && hasAuth(AUDIT_PERMISSIONS.refundFinanceEntry)"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
@click="openRefundFinance"
|
@click="openRefundFinance"
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
v-if="
|
v-if="
|
||||||
refund?.approval_instance_id &&
|
refund?.approval_instance_id &&
|
||||||
isPlatformUser &&
|
isPlatformUser &&
|
||||||
hasAuth(AUDIT_PERMISSIONS.resourceTimeline)
|
hasAuth(AUDIT_PERMISSIONS.refundApprovalEntry)
|
||||||
"
|
"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
|
|||||||
@@ -754,7 +754,7 @@
|
|||||||
handler: () => openAuditInvestigation(auditTarget),
|
handler: () => openAuditInvestigation(auditTarget),
|
||||||
type: 'primary'
|
type: 'primary'
|
||||||
})
|
})
|
||||||
if (hasAuth(AUDIT_PERMISSIONS.financeTimeline)) {
|
if (hasAuth(AUDIT_PERMISSIONS.refundFinanceEntry)) {
|
||||||
const financeTarget = resolveFinanceAuditTarget('refund_id', row.id)
|
const financeTarget = resolveFinanceAuditTarget('refund_id', row.id)
|
||||||
if (financeTarget)
|
if (financeTarget)
|
||||||
actions.push({
|
actions.push({
|
||||||
@@ -768,7 +768,7 @@
|
|||||||
resourceType: 'approval_instance',
|
resourceType: 'approval_instance',
|
||||||
internalId: row.approval_instance_id
|
internalId: row.approval_instance_id
|
||||||
})
|
})
|
||||||
if (approvalTarget)
|
if (approvalTarget && hasAuth(AUDIT_PERMISSIONS.refundApprovalEntry))
|
||||||
actions.push({
|
actions.push({
|
||||||
label: '审批审计',
|
label: '审批审计',
|
||||||
handler: () => openAuditInvestigation(approvalTarget),
|
handler: () => openAuditInvestigation(approvalTarget),
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
审计记录
|
审计记录
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
v-if="detailData && isPlatformUser && hasAuth(AUDIT_PERMISSIONS.financeTimeline)"
|
v-if="detailData && isPlatformUser && hasAuth(AUDIT_PERMISSIONS.orderFinanceEntry)"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
@click="openOrderFinance"
|
@click="openOrderFinance"
|
||||||
|
|||||||
@@ -1372,7 +1372,7 @@
|
|||||||
handler: () => openAuditInvestigation(auditTarget),
|
handler: () => openAuditInvestigation(auditTarget),
|
||||||
type: 'primary'
|
type: 'primary'
|
||||||
})
|
})
|
||||||
if (hasAuth(AUDIT_PERMISSIONS.financeTimeline)) {
|
if (hasAuth(AUDIT_PERMISSIONS.orderFinanceEntry)) {
|
||||||
const financeTarget = resolveFinanceAuditTarget('order_id', row.id)
|
const financeTarget = resolveFinanceAuditTarget('order_id', row.id)
|
||||||
if (financeTarget)
|
if (financeTarget)
|
||||||
actions.push({
|
actions.push({
|
||||||
|
|||||||
@@ -815,16 +815,19 @@
|
|||||||
const getActions = (row: ShopResponse) => {
|
const getActions = (row: ShopResponse) => {
|
||||||
const actions: any[] = []
|
const actions: any[] = []
|
||||||
|
|
||||||
if (hasAuth(AUDIT_PERMISSIONS.shopEntry)) {
|
const userType = Number(userStore.getUserInfo.user_type)
|
||||||
|
const auditPermission =
|
||||||
|
userType === 3 ? AUDIT_PERMISSIONS.agentActivity : AUDIT_PERMISSIONS.shopEntry
|
||||||
|
if (hasAuth(auditPermission)) {
|
||||||
const auditTarget = resolveAuditResourceTarget({
|
const auditTarget = resolveAuditResourceTarget({
|
||||||
userType: userStore.getUserInfo.user_type,
|
userType,
|
||||||
resourceType: 'shop',
|
resourceType: 'shop',
|
||||||
internalId: row.id,
|
internalId: row.id,
|
||||||
businessIdentifier: row.shop_code
|
businessIdentifier: row.shop_code
|
||||||
})
|
})
|
||||||
if (auditTarget)
|
if (auditTarget)
|
||||||
actions.push({
|
actions.push({
|
||||||
label: '审计记录',
|
label: userType === 3 ? '活动记录' : '审计记录',
|
||||||
handler: () => openAuditInvestigation(auditTarget),
|
handler: () => openAuditInvestigation(auditTarget),
|
||||||
type: 'primary'
|
type: 'primary'
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user