fix: update some files
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 5m44s
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 5m44s
This commit is contained in:
50
openspec/changes/add-historical-approval-resend/design.md
Normal file
50
openspec/changes/add-historical-approval-resend/design.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
退款和代理充值已经接入企微审批,但部分历史记录在审批流程上线前创建,列表中的 `approval_status` 为空。这些记录需要由运营人员手动触发一次审批补发,才能进入企微审批链路。
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
- Goals: 提供代理充值和退款的历史审批补发入口。
|
||||||
|
- Goals: 通过路径参数指定目标记录,并复用后端返回的完整记录与审批状态。
|
||||||
|
- Goals: 由明确的状态规则控制入口展示,后端做最终资格校验。
|
||||||
|
- Non-Goals: 不在前端实现审批提交、审批回调或审批引擎。
|
||||||
|
- Non-Goals: 不修改历史记录的业务字段、金额或支付/退款结果。
|
||||||
|
- Non-Goals: 不提供批量自动补发。
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
- Decision: 两个模块分别新增 `triggerApproval(id)` 服务方法,返回完整业务记录并保留审批字段。
|
||||||
|
- Rationale: 两个接口契约一致,成功后页面需要立即展示最新审批状态,完整记录可直接用于刷新。
|
||||||
|
|
||||||
|
- Decision: 代理充值仅在 `approval_status` 为空且 `status` 不属于已完成、已驳回、已关闭时显示补发入口;退款仅在 `status=待审批` 且 `approval_status` 为空时显示补发入口。
|
||||||
|
- Rationale: 与业务规则一致,避免对已进入审批或已终结的记录重复补发;后端仍做最终资格校验。
|
||||||
|
|
||||||
|
- Decision: 引入独立按钮权限 `agent_recharge:trigger_approval` 和 `refund:trigger_approval`。
|
||||||
|
- Rationale: 补发审批是财务相关敏感操作,需与现有确认支付、拒绝、重新申请权限区分。
|
||||||
|
|
||||||
|
- Decision: 补发审批与现有“确认支付/拒绝”和“重新申请”操作共存。
|
||||||
|
- Rationale: 它们承担不同职责;补发审批只是把历史记录推进企微审批,不改变后续人工确认或重新申请的流程。
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- Risk: 代理充值中 `status=已支付`、`已退款` 等非终态记录是否允许补发,需要后端最终确认。
|
||||||
|
- Mitigation: 前端按约定的审批状态与状态排除规则展示入口,后端对不允许的记录返回错误并稳定展示。
|
||||||
|
|
||||||
|
- Risk: 接口可能对部分状态返回拒绝。
|
||||||
|
- Mitigation: 前端处理后端错误信息,不将失败记录标记为已补发。
|
||||||
|
|
||||||
|
- Risk: 重复点击可能触发多次审批提交。
|
||||||
|
- Mitigation: 提交期间锁定按钮并禁用重复触发;后端应保证幂等或返回明确的已存在审批提示。
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
1. 确认两个 `trigger-approval` 接口的响应字段与现有 `AgentRecharge`、`Refund` 类型一致。
|
||||||
|
2. 新增服务方法和按钮权限。
|
||||||
|
3. 在列表接入补发审批入口及资格判断。
|
||||||
|
4. 联调补发成功、接口拒绝、权限缺失、审批状态为空与状态不符合条件等场景。
|
||||||
|
5. 验证与现有确认支付、拒绝、重新申请操作不冲突。
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
- 后端对可补发记录的最终状态校验以及幂等性需确认。
|
||||||
|
- 两个接口是否都需要独立权限码,还是复用现有审批/财务权限,需与后端权限配置对齐。
|
||||||
39
openspec/changes/add-historical-approval-resend/proposal.md
Normal file
39
openspec/changes/add-historical-approval-resend/proposal.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Change: 补发历史线下代理充值审批与补发历史退款审批
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
历史线下代理充值记录和历史退款申请在企微审批流程上线前创建,列表中这些记录的审批状态为空,运营人员无法为它们补发审批流程。需要为这两类业务提供“补发审批”入口,调用后端触发审批接口,将历史记录纳入企微审批。
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- 在 `AgentRechargeService` 新增 `triggerApproval(id)`,调用 `POST /api/admin/agent-recharges/{id}/trigger-approval`。
|
||||||
|
- 在 `RefundService` 新增 `triggerApproval(id)`,调用 `POST /api/admin/refunds/{id}/trigger-approval`。
|
||||||
|
- 在代理充值列表为 `approval_status` 为空且 `status` 不属于已完成、已驳回、已关闭的充值记录增加“补发审批”操作。
|
||||||
|
- 在退款列表为 `status=待审批` 且 `approval_status` 为空的退款申请增加“补发审批”操作。
|
||||||
|
- 引入权限 `agent_recharge:trigger_approval` 和 `refund:trigger_approval`,仅对有权限的平台账号展示操作。
|
||||||
|
- 补发成功后刷新列表并展示返回的最新审批状态;失败时展示后端错误信息。
|
||||||
|
- 不改变现有“确认支付”“拒绝”“重新申请”等操作的资格和职责。
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Affected specs:
|
||||||
|
- `agent-recharge`
|
||||||
|
- `refund-management`
|
||||||
|
- Affected code:
|
||||||
|
- `src/api/modules/agentRecharge.ts`
|
||||||
|
- `src/api/modules/refund.ts`
|
||||||
|
- `src/types/api/agentRecharge.ts`
|
||||||
|
- `src/types/api/refund.ts`
|
||||||
|
- `src/views/finance/agent-recharge/agentRechargeActions.ts`
|
||||||
|
- `src/views/finance/agent-recharge/index.vue`
|
||||||
|
- `src/views/finance/refund/index.vue`
|
||||||
|
- API contracts:
|
||||||
|
- `POST /api/admin/agent-recharges/{id}/trigger-approval`
|
||||||
|
- `POST /api/admin/refunds/{id}/trigger-approval`
|
||||||
|
- Dependencies:
|
||||||
|
- 后端按文档返回完整业务记录及当前审批状态。
|
||||||
|
- 后端负责校验记录是否可补发审批,前端仅控制入口展示并处理后端拒绝。
|
||||||
|
- Out of scope:
|
||||||
|
- 企微审批的发起、撤回、通过、驳回或删除动作本身。
|
||||||
|
- 修改历史记录的业务字段、金额或支付/退款结果。
|
||||||
|
- 自动判断并批量补发历史审批。
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Agent Recharge Historical Approval Resend API
|
||||||
|
|
||||||
|
The agent recharge service SHALL expose a historical approval resend operation through `POST /api/admin/agent-recharges/{id}/trigger-approval`.
|
||||||
|
|
||||||
|
#### Scenario: Resend approval for a historical offline recharge
|
||||||
|
|
||||||
|
- **GIVEN** 一个需要补发审批的历史线下代理充值记录 `id`
|
||||||
|
- **WHEN** 前端调用 `POST /api/admin/agent-recharges/{id}/trigger-approval`
|
||||||
|
- **THEN** 请求 MUST 在路径参数中携带该充值记录 `id`
|
||||||
|
- **AND** 成功响应 MUST 被解析为完整的 `AgentRecharge` 记录,并保留 `approval_provider`、`approval_instance_id`、`approval_status` 和 `approval_status_name` 字段
|
||||||
|
|
||||||
|
### Requirement: Agent Recharge Historical Approval Resend Entry
|
||||||
|
|
||||||
|
The agent recharge list page SHALL provide a `补发审批` action only for recharge records whose `approval_status` is empty and whose business `status` is not completed, rejected, or closed, and SHALL gate it by permission.
|
||||||
|
|
||||||
|
#### Scenario: Show resend action for an eligible recharge
|
||||||
|
|
||||||
|
- **GIVEN** 平台账号拥有 `agent_recharge:trigger_approval` 权限
|
||||||
|
- **AND** 充值记录的 `approval_status` 为空(`null` 或 `undefined`)
|
||||||
|
- **AND** 充值记录的 `status` 不属于已完成(3)、已驳回(6)、已关闭(4)
|
||||||
|
- **WHEN** 页面渲染代理充值列表
|
||||||
|
- **THEN** 页面 MUST 为该记录显示“补发审批”操作
|
||||||
|
|
||||||
|
#### Scenario: Hide resend action when approval status is present
|
||||||
|
|
||||||
|
- **GIVEN** 充值记录的 `approval_status` 不为空
|
||||||
|
- **WHEN** 页面渲染该记录
|
||||||
|
- **THEN** 页面 MUST NOT 显示“补发审批”操作
|
||||||
|
|
||||||
|
#### Scenario: Hide resend action for terminal recharge statuses
|
||||||
|
|
||||||
|
- **GIVEN** 充值记录的 `status` 为已完成(3)、已驳回(6)或已关闭(4)
|
||||||
|
- **WHEN** 页面渲染该记录
|
||||||
|
- **THEN** 页面 MUST NOT 显示“补发审批”操作
|
||||||
|
|
||||||
|
#### Scenario: Hide resend action without permission
|
||||||
|
|
||||||
|
- **GIVEN** 当前账号不拥有 `agent_recharge:trigger_approval` 权限
|
||||||
|
- **WHEN** 页面渲染代理充值记录
|
||||||
|
- **THEN** 页面 MUST NOT 显示“补发审批”操作
|
||||||
|
|
||||||
|
#### Scenario: Resend approval succeeds
|
||||||
|
|
||||||
|
- **GIVEN** 用户对符合条件的充值记录点击“补发审批”
|
||||||
|
- **WHEN** 接口返回 `code=0`
|
||||||
|
- **THEN** 页面 MUST 显示成功提示并刷新列表
|
||||||
|
- **AND** 刷新后的记录 MUST 展示接口返回的最新审批状态
|
||||||
|
|
||||||
|
#### Scenario: Resend approval fails
|
||||||
|
|
||||||
|
- **GIVEN** 接口返回非零 `code` 或请求失败
|
||||||
|
- **WHEN** 用户触发“补发审批”
|
||||||
|
- **THEN** 页面 MUST 展示后端返回的错误信息
|
||||||
|
- **AND** 页面 MUST NOT 将记录标记为已补发审批
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Refund Historical Approval Resend API
|
||||||
|
|
||||||
|
The refund service SHALL expose a historical approval resend operation through `POST /api/admin/refunds/{id}/trigger-approval`.
|
||||||
|
|
||||||
|
#### Scenario: Resend approval for a historical refund
|
||||||
|
|
||||||
|
- **GIVEN** 一个需要补发审批的历史退款申请 `id`
|
||||||
|
- **WHEN** 前端调用 `POST /api/admin/refunds/{id}/trigger-approval`
|
||||||
|
- **THEN** 请求 MUST 在路径参数中携带该退款申请 `id`
|
||||||
|
- **AND** 成功响应 MUST 被解析为完整的 `Refund` 记录,并保留 `approval_provider`、`approval_instance_id`、`approval_status` 和 `approval_status_name` 字段
|
||||||
|
|
||||||
|
### Requirement: Refund Historical Approval Resend Entry
|
||||||
|
|
||||||
|
The refund list page SHALL provide a `补发审批` action only for refund records whose `status` is pending approval and whose `approval_status` is empty, and SHALL gate it by permission.
|
||||||
|
|
||||||
|
#### Scenario: Show resend action for an eligible refund
|
||||||
|
|
||||||
|
- **GIVEN** 平台账号拥有 `refund:trigger_approval` 权限
|
||||||
|
- **AND** 退款申请的 `status` 为待审批(1)
|
||||||
|
- **AND** 退款申请的 `approval_status` 为空(`null` 或 `undefined`)
|
||||||
|
- **WHEN** 页面渲染退款列表
|
||||||
|
- **THEN** 页面 MUST 为该记录显示“补发审批”操作
|
||||||
|
|
||||||
|
#### Scenario: Hide resend action when refund status is not pending
|
||||||
|
|
||||||
|
- **GIVEN** 退款申请的 `status` 不为待审批(1)
|
||||||
|
- **WHEN** 页面渲染该记录
|
||||||
|
- **THEN** 页面 MUST NOT 显示“补发审批”操作
|
||||||
|
|
||||||
|
#### Scenario: Hide resend action when approval status is present
|
||||||
|
|
||||||
|
- **GIVEN** 退款申请的 `approval_status` 不为空
|
||||||
|
- **WHEN** 页面渲染该记录
|
||||||
|
- **THEN** 页面 MUST NOT 显示“补发审批”操作
|
||||||
|
|
||||||
|
#### Scenario: Hide resend action without permission
|
||||||
|
|
||||||
|
- **GIVEN** 当前账号不拥有 `refund:trigger_approval` 权限
|
||||||
|
- **WHEN** 页面渲染退款记录
|
||||||
|
- **THEN** 页面 MUST NOT 显示“补发审批”操作
|
||||||
|
|
||||||
|
#### Scenario: Resend approval succeeds
|
||||||
|
|
||||||
|
- **GIVEN** 用户对符合条件的退款申请点击“补发审批”
|
||||||
|
- **WHEN** 接口返回 `code=0`
|
||||||
|
- **THEN** 页面 MUST 显示成功提示并刷新列表
|
||||||
|
- **AND** 刷新后的记录 MUST 展示接口返回的最新审批状态
|
||||||
|
|
||||||
|
#### Scenario: Resend approval fails
|
||||||
|
|
||||||
|
- **GIVEN** 接口返回非零 `code` 或请求失败
|
||||||
|
- **WHEN** 用户触发“补发审批”
|
||||||
|
- **THEN** 页面 MUST 展示后端返回的错误信息
|
||||||
|
- **AND** 页面 MUST NOT 将记录标记为已补发审批
|
||||||
23
openspec/changes/add-historical-approval-resend/tasks.md
Normal file
23
openspec/changes/add-historical-approval-resend/tasks.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
## 1. 类型与 API 契约
|
||||||
|
|
||||||
|
- [x] 1.1 在 `src/api/modules/agentRecharge.ts` 新增 `triggerApproval(id)` 方法
|
||||||
|
- [x] 1.2 在 `src/api/modules/refund.ts` 新增 `triggerApproval(id)` 方法
|
||||||
|
- [x] 1.3 确认 `AgentRecharge` 和 `Refund` 类型包含 `approval_provider`、`approval_source`、`approval_instance_id`、`approval_status`、`approval_status_name`
|
||||||
|
|
||||||
|
## 2. 代理充值补发审批入口
|
||||||
|
|
||||||
|
- [x] 2.1 在 `agentRechargeActions.ts` 增加“补发审批”动作及资格判断
|
||||||
|
- [x] 2.2 在代理充值列表接入权限 `agent_recharge:trigger_approval` 与成功/失败处理
|
||||||
|
- [x] 2.3 补发成功后刷新列表并展示最新审批状态
|
||||||
|
|
||||||
|
## 3. 退款补发审批入口
|
||||||
|
|
||||||
|
- [x] 3.1 在退款列表 `getActions` 增加“补发审批”动作及资格判断
|
||||||
|
- [x] 3.2 接入权限 `refund:trigger_approval` 与成功/失败处理
|
||||||
|
- [x] 3.3 补发成功后刷新列表并展示最新审批状态
|
||||||
|
|
||||||
|
## 4. 校验与验证
|
||||||
|
|
||||||
|
- [x] 4.1 运行 `openspec validate add-historical-approval-resend --strict`
|
||||||
|
- [x] 4.2 运行 ESLint、类型检查并修复
|
||||||
|
- [ ] 4.3 手工验证有/无审批实例、线上/线下充值、权限开关等场景
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
顶部通知抽屉已经通过通知列表接口加载最近 10 条,但当前共享状态没有保存列表分页信息。分页应复用现有通知列表接口,避免新增接口或改变通知数据契约。
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
- Decision: 分页状态由通知 store 保存,包括当前页、每页数量和总条数;列表请求返回后整体替换 `recentNotifications`。
|
||||||
|
- Decision: 页码切换使用 `GET /api/admin/notifications?page=<page>&page_size=10`,加载期间沿用现有 loading 状态。
|
||||||
|
- Decision: 分类切换将页码重置为 1,并重新加载列表;不使用滚动事件触发请求。
|
||||||
|
- Decision: 分页控件放在通知列表底部,列表区域继续保持固定高度,避免抽屉整体布局跳动。
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- 分类当前由抽屉对已加载页面进行过滤,分类页的总数仍由通知列表接口返回的总体 `total` 表示;本次不扩展后端分类分页契约。
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Change: 通知抽屉改为分页浏览
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
顶部通知抽屉目前只展示最近 10 条通知,用户无法通过页码查看更早的通知。通知列表应使用明确的分页操作,避免依赖下滑加载更多的交互。
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- 顶部通知抽屉增加页码分页控件,默认每页展示 10 条。
|
||||||
|
- 切换页码时调用通知列表接口并替换当前列表,不追加滚动加载。
|
||||||
|
- 切换通知分类时重置到第 1 页,并保持现有分类统计、已读和跳转行为。
|
||||||
|
- 保留 `/api/admin/notifications` 的 `page`、`page_size` 分页参数和后端返回的 `total`。
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Affected specs:
|
||||||
|
- `notification-center`
|
||||||
|
- Affected code:
|
||||||
|
- `src/components/core/layouts/art-notification/index.vue`
|
||||||
|
- `src/components/core/layouts/art-notification/style.scss`
|
||||||
|
- `src/store/modules/notification.ts`
|
||||||
|
- Out of scope:
|
||||||
|
- 不改变通知接口、通知分类统计和已读接口。
|
||||||
|
- 不接入下滑加载、无限滚动或新的实时推送机制。
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Global Notification Bell
|
||||||
|
|
||||||
|
The admin frontend SHALL provide a global notification bell in the top navigation near the settings and user avatar entries.
|
||||||
|
|
||||||
|
#### Scenario: Display unread count
|
||||||
|
|
||||||
|
- **GIVEN** 用户已登录后台
|
||||||
|
- **WHEN** 顶部导航加载未读通知数量
|
||||||
|
- **THEN** 铃铛 MUST call `GET /api/admin/notifications/unread-count`
|
||||||
|
- **AND** 数量 MUST display as `0`, `1` through `99`, or `99+`
|
||||||
|
|
||||||
|
#### Scenario: Open paginated notification drawer
|
||||||
|
|
||||||
|
- **WHEN** 用户点击顶部通知铃铛
|
||||||
|
- **THEN** 页面 MUST display notification items from `GET /api/admin/notifications` using `page=1` and `page_size=10`
|
||||||
|
- **AND** 抽屉 MUST provide 全部、审批、临期、同步/系统分类
|
||||||
|
- **AND** 抽屉 MUST provide page controls based on the response `total`
|
||||||
|
- **AND** changing page MUST replace the visible items instead of appending items from a scroll event
|
||||||
|
- **AND** changing category MUST reset the page to 1
|
||||||
|
- **AND** 抽屉 MUST provide an entry to `/notifications`
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
## 1. Notification State
|
||||||
|
|
||||||
|
- [x] 1.1 在通知 store 中保存当前页、每页数量和列表总数。
|
||||||
|
- [x] 1.2 支持按指定页请求通知,并用新结果替换当前列表。
|
||||||
|
|
||||||
|
## 2. Notification Drawer
|
||||||
|
|
||||||
|
- [x] 2.1 在通知列表底部增加页码分页控件,默认每页 10 条。
|
||||||
|
- [x] 2.2 切换页码时重新请求列表并回到列表顶部。
|
||||||
|
- [x] 2.3 切换分类时重置页码并保持现有分类、已读和跳转行为。
|
||||||
|
|
||||||
|
## 3. Verification
|
||||||
|
|
||||||
|
- [x] 3.1 验证分页请求携带正确的 `page` 和 `page_size`,且列表不会累加旧页数据。
|
||||||
|
- [x] 3.2 运行通知相关 lint、类型、格式、样式和编码检查;当前仓库暂无通知专项测试。
|
||||||
@@ -27,7 +27,7 @@ test('agent recharge rejection posts the rejection reason to the order rejection
|
|||||||
code: 0,
|
code: 0,
|
||||||
data: {
|
data: {
|
||||||
methods: ['wechat', 'alipay'],
|
methods: ['wechat', 'alipay'],
|
||||||
min_amount: 10,
|
min_amount: 10000,
|
||||||
max_amount: 100000000
|
max_amount: 100000000
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -88,4 +88,14 @@ export class AgentRechargeService extends BaseService {
|
|||||||
): Promise<BaseResponse<void>> {
|
): Promise<BaseResponse<void>> {
|
||||||
return this.post<BaseResponse<void>>(`/api/admin/agent-recharges/${id}/reject`, data)
|
return this.post<BaseResponse<void>>(`/api/admin/agent-recharges/${id}/reject`, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 补发历史线下代理充值审批
|
||||||
|
* @param id 充值记录ID
|
||||||
|
*/
|
||||||
|
static triggerApproval(id: number): Promise<BaseResponse<AgentRecharge>> {
|
||||||
|
return this.post<BaseResponse<AgentRecharge>>(
|
||||||
|
`/api/admin/agent-recharges/${id}/trigger-approval`
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,4 +45,12 @@ export class RefundService extends BaseService {
|
|||||||
static resubmitRefund(id: number, data: ResubmitRefundRequest): Promise<BaseResponse<void>> {
|
static resubmitRefund(id: number, data: ResubmitRefundRequest): Promise<BaseResponse<void>> {
|
||||||
return this.post<BaseResponse<void>>(`/api/admin/refunds/${id}/resubmit`, data)
|
return this.post<BaseResponse<void>>(`/api/admin/refunds/${id}/resubmit`, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 补发历史退款审批
|
||||||
|
* @param id 退款申请ID
|
||||||
|
*/
|
||||||
|
static triggerApproval(id: number): Promise<BaseResponse<Refund>> {
|
||||||
|
return this.post<BaseResponse<Refund>>(`/api/admin/refunds/${id}/trigger-approval`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,14 +12,14 @@
|
|||||||
:key="tab.value"
|
:key="tab.value"
|
||||||
type="button"
|
type="button"
|
||||||
:class="{ active: activeCategory === tab.value }"
|
:class="{ active: activeCategory === tab.value }"
|
||||||
@click="activeCategory = tab.value"
|
@click="handleCategoryChange(tab.value)"
|
||||||
>
|
>
|
||||||
{{ tab.label }}<span v-if="tab.count"> ({{ tab.count }})</span>
|
{{ tab.label }}<span v-if="tab.count"> ({{ tab.count }})</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div v-loading="notificationStore.loading" class="scroll">
|
<div ref="notificationListRef" v-loading="notificationStore.loading" class="scroll">
|
||||||
<button
|
<button
|
||||||
v-for="item in filteredItems"
|
v-for="item in filteredItems"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@@ -43,6 +43,21 @@
|
|||||||
<p>暂无通知</p>
|
<p>暂无通知</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="notificationStore.notificationTotal > notificationStore.notificationPageSize"
|
||||||
|
class="pagination"
|
||||||
|
>
|
||||||
|
<ElPagination
|
||||||
|
:current-page="notificationStore.notificationPage"
|
||||||
|
:page-size="notificationStore.notificationPageSize"
|
||||||
|
:total="notificationStore.notificationTotal"
|
||||||
|
:pager-count="5"
|
||||||
|
background
|
||||||
|
layout="prev, pager, next"
|
||||||
|
small
|
||||||
|
@current-change="handlePageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -64,6 +79,7 @@
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const notificationStore = useNotificationStore()
|
const notificationStore = useNotificationStore()
|
||||||
const activeCategory = ref<'all' | NotificationCategory>('all')
|
const activeCategory = ref<'all' | NotificationCategory>('all')
|
||||||
|
const notificationListRef = ref<HTMLElement | null>(null)
|
||||||
const visible = computed(() => props.modelValue)
|
const visible = computed(() => props.modelValue)
|
||||||
|
|
||||||
const categoryMatches = (item: NotificationItem, category: string) => {
|
const categoryMatches = (item: NotificationItem, category: string) => {
|
||||||
@@ -96,6 +112,24 @@
|
|||||||
|
|
||||||
const close = () => emit('update:modelValue', false)
|
const close = () => emit('update:modelValue', false)
|
||||||
|
|
||||||
|
const loadNotificationPage = async (page: number) => {
|
||||||
|
try {
|
||||||
|
await notificationStore.loadNotifications(page)
|
||||||
|
notificationListRef.value?.scrollTo({ top: 0 })
|
||||||
|
} catch (error: any) {
|
||||||
|
ElMessage.error(error?.message || '获取通知失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCategoryChange = (category: 'all' | NotificationCategory) => {
|
||||||
|
activeCategory.value = category
|
||||||
|
void loadNotificationPage(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePageChange = (page: number) => {
|
||||||
|
void loadNotificationPage(page)
|
||||||
|
}
|
||||||
|
|
||||||
const handleMarkAllRead = async () => {
|
const handleMarkAllRead = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await notificationStore.markAllRead()
|
const response = await notificationStore.markAllRead()
|
||||||
|
|||||||
@@ -68,6 +68,18 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 12px 8px;
|
||||||
|
overflow-x: auto;
|
||||||
|
border-top: 1px solid var(--art-border-color);
|
||||||
|
|
||||||
|
:deep(.el-pagination) {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.notification-item {
|
.notification-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { NotificationService } from '@/api/modules/notification'
|
import { NotificationService } from '@/api/modules/notification'
|
||||||
import type { NotificationItem, NotificationUnreadSummary } from '@/types/api'
|
import type {
|
||||||
|
BaseResponse,
|
||||||
|
NotificationItem,
|
||||||
|
NotificationListResponse,
|
||||||
|
NotificationUnreadSummary
|
||||||
|
} from '@/types/api'
|
||||||
|
|
||||||
const emptySummary = (): NotificationUnreadSummary => ({
|
const emptySummary = (): NotificationUnreadSummary => ({
|
||||||
approval: 0,
|
approval: 0,
|
||||||
@@ -16,8 +21,22 @@ export const useNotificationStore = defineStore('notificationStore', () => {
|
|||||||
const displayCount = ref('0')
|
const displayCount = ref('0')
|
||||||
const summary = ref<NotificationUnreadSummary>(emptySummary())
|
const summary = ref<NotificationUnreadSummary>(emptySummary())
|
||||||
const recentNotifications = ref<NotificationItem[]>([])
|
const recentNotifications = ref<NotificationItem[]>([])
|
||||||
|
const notificationPage = ref(1)
|
||||||
|
const notificationPageSize = 10
|
||||||
|
const notificationTotal = ref(0)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const applyNotificationList = (
|
||||||
|
response: BaseResponse<NotificationListResponse>,
|
||||||
|
fallbackPage: number
|
||||||
|
) => {
|
||||||
|
if (response.code !== 0 || !response.data) return
|
||||||
|
|
||||||
|
recentNotifications.value = response.data.items
|
||||||
|
notificationPage.value = response.data.page || fallbackPage
|
||||||
|
notificationTotal.value = Math.max(0, response.data.total || 0)
|
||||||
|
}
|
||||||
|
|
||||||
const refreshUnreadCount = async () => {
|
const refreshUnreadCount = async () => {
|
||||||
const response = await NotificationService.getUnreadCount()
|
const response = await NotificationService.getUnreadCount()
|
||||||
if (response.code === 0 && response.data) {
|
if (response.code === 0 && response.data) {
|
||||||
@@ -27,19 +46,33 @@ export const useNotificationStore = defineStore('notificationStore', () => {
|
|||||||
return unreadCount.value
|
return unreadCount.value
|
||||||
}
|
}
|
||||||
|
|
||||||
const refreshSummary = async () => {
|
const loadNotifications = async (page = notificationPage.value) => {
|
||||||
|
const targetPage = Math.max(1, page)
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const response = await NotificationService.getNotifications({
|
||||||
|
page: targetPage,
|
||||||
|
page_size: notificationPageSize
|
||||||
|
})
|
||||||
|
applyNotificationList(response, targetPage)
|
||||||
|
return response
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshSummary = async (page = 1) => {
|
||||||
|
const targetPage = Math.max(1, page)
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const [summaryResponse, listResponse] = await Promise.all([
|
const [summaryResponse, listResponse] = await Promise.all([
|
||||||
NotificationService.getUnreadSummary(),
|
NotificationService.getUnreadSummary(),
|
||||||
NotificationService.getNotifications({ page: 1, page_size: 10 })
|
NotificationService.getNotifications({ page: targetPage, page_size: notificationPageSize })
|
||||||
])
|
])
|
||||||
if (summaryResponse.code === 0 && summaryResponse.data) {
|
if (summaryResponse.code === 0 && summaryResponse.data) {
|
||||||
summary.value = summaryResponse.data
|
summary.value = summaryResponse.data
|
||||||
}
|
}
|
||||||
if (listResponse.code === 0 && listResponse.data) {
|
applyNotificationList(listResponse, targetPage)
|
||||||
recentNotifications.value = listResponse.data.items.slice(0, 10)
|
|
||||||
}
|
|
||||||
await refreshUnreadCount()
|
await refreshUnreadCount()
|
||||||
return summary.value
|
return summary.value
|
||||||
} finally {
|
} finally {
|
||||||
@@ -50,7 +83,7 @@ export const useNotificationStore = defineStore('notificationStore', () => {
|
|||||||
const markRead = async (id: number) => {
|
const markRead = async (id: number) => {
|
||||||
const response = await NotificationService.markRead(id)
|
const response = await NotificationService.markRead(id)
|
||||||
if (response.code === 0) {
|
if (response.code === 0) {
|
||||||
await refreshSummary()
|
await refreshSummary(notificationPage.value)
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
@@ -58,7 +91,7 @@ export const useNotificationStore = defineStore('notificationStore', () => {
|
|||||||
const markAllRead = async (category?: string) => {
|
const markAllRead = async (category?: string) => {
|
||||||
const response = await NotificationService.markAllRead(category ? { category } : {})
|
const response = await NotificationService.markAllRead(category ? { category } : {})
|
||||||
if (response.code === 0) {
|
if (response.code === 0) {
|
||||||
await refreshSummary()
|
await refreshSummary(notificationPage.value)
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
@@ -68,8 +101,12 @@ export const useNotificationStore = defineStore('notificationStore', () => {
|
|||||||
displayCount,
|
displayCount,
|
||||||
summary,
|
summary,
|
||||||
recentNotifications,
|
recentNotifications,
|
||||||
|
notificationPage,
|
||||||
|
notificationPageSize,
|
||||||
|
notificationTotal,
|
||||||
loading,
|
loading,
|
||||||
refreshUnreadCount,
|
refreshUnreadCount,
|
||||||
|
loadNotifications,
|
||||||
refreshSummary,
|
refreshSummary,
|
||||||
markRead,
|
markRead,
|
||||||
markAllRead
|
markAllRead
|
||||||
|
|||||||
@@ -397,11 +397,11 @@
|
|||||||
>
|
>
|
||||||
<ElDatePicker
|
<ElDatePicker
|
||||||
v-model="seriesBindingForm.created_at_range"
|
v-model="seriesBindingForm.created_at_range"
|
||||||
type="daterange"
|
type="datetimerange"
|
||||||
range-separator="至"
|
range-separator="至"
|
||||||
start-placeholder="开始时间"
|
start-placeholder="开始时间"
|
||||||
end-placeholder="结束时间"
|
end-placeholder="结束时间"
|
||||||
value-format="YYYY-MM-DD"
|
value-format="YYYY-MM-DDTHH:mm:ssZ"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
@@ -1537,13 +1537,13 @@
|
|||||||
{
|
{
|
||||||
label: '起止时间',
|
label: '起止时间',
|
||||||
prop: 'dateRange',
|
prop: 'dateRange',
|
||||||
type: 'date',
|
type: 'datetimerange',
|
||||||
config: {
|
config: {
|
||||||
type: 'daterange',
|
type: 'datetimerange',
|
||||||
rangeSeparator: '至',
|
rangeSeparator: '至',
|
||||||
startPlaceholder: '开始时间',
|
startPlaceholder: '开始时间',
|
||||||
endPlaceholder: '结束时间',
|
endPlaceholder: '结束时间',
|
||||||
valueFormat: 'YYYY-MM-DD'
|
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -10,30 +10,6 @@
|
|||||||
返回
|
返回
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<h2 class="detail-title">换货单详情</h2>
|
<h2 class="detail-title">换货单详情</h2>
|
||||||
<ElButton
|
|
||||||
v-if="exchangeAuditTarget && hasAuth(exchangeAuditPermission)"
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
@click="openAuditInvestigation(exchangeAuditTarget)"
|
|
||||||
>
|
|
||||||
{{ userType === 3 ? '活动记录' : '换货审计' }}
|
|
||||||
</ElButton>
|
|
||||||
<ElButton
|
|
||||||
v-if="oldAssetAuditTarget && hasAuth(AUDIT_PERMISSIONS.exchangeOldAssetEntry)"
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
@click="openAuditInvestigation(oldAssetAuditTarget)"
|
|
||||||
>
|
|
||||||
旧资产审计
|
|
||||||
</ElButton>
|
|
||||||
<ElButton
|
|
||||||
v-if="newAssetAuditTarget && hasAuth(AUDIT_PERMISSIONS.exchangeNewAssetEntry)"
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
@click="openAuditInvestigation(newAssetAuditTarget)"
|
|
||||||
>
|
|
||||||
新资产审计
|
|
||||||
</ElButton>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 详情内容 -->
|
<!-- 详情内容 -->
|
||||||
@@ -59,51 +35,15 @@
|
|||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import DetailPage from '@/components/common/DetailPage.vue'
|
import DetailPage from '@/components/common/DetailPage.vue'
|
||||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
|
||||||
import { useUserStore } from '@/store/modules/user'
|
|
||||||
import { AUDIT_PERMISSIONS } from '@/config/constants/audit'
|
|
||||||
import { resolveAuditResourceTarget } from '@/utils/business/auditNavigation'
|
|
||||||
import { openAuditInvestigation } from '@/components/business/audit/investigationController'
|
|
||||||
|
|
||||||
defineOptions({ name: 'ExchangeDetail' })
|
defineOptions({ name: 'ExchangeDetail' })
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { hasAuth } = useAuth()
|
|
||||||
const userStore = useUserStore()
|
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const exchangeDetail = ref<ExchangeResponse | null>(null)
|
const exchangeDetail = ref<ExchangeResponse | null>(null)
|
||||||
const exchangeId = ref<number>(0)
|
const exchangeId = ref<number>(0)
|
||||||
const userType = computed(() => Number(userStore.info.user_type))
|
|
||||||
const exchangeAuditPermission = computed(() =>
|
|
||||||
userType.value === 3 ? AUDIT_PERMISSIONS.agentExchangeActivity : AUDIT_PERMISSIONS.exchangeEntry
|
|
||||||
)
|
|
||||||
const exchangeAuditTarget = computed(() => {
|
|
||||||
if (!exchangeDetail.value || ![1, 2, 3].includes(userType.value)) return null
|
|
||||||
return resolveAuditResourceTarget({
|
|
||||||
userType: userType.value,
|
|
||||||
resourceType: 'exchange_order',
|
|
||||||
internalId: exchangeDetail.value.id,
|
|
||||||
businessIdentifier: exchangeDetail.value.exchange_no
|
|
||||||
})
|
|
||||||
})
|
|
||||||
const oldAssetAuditTarget = computed(() => {
|
|
||||||
if (!exchangeDetail.value || ![1, 2].includes(userType.value)) return null
|
|
||||||
return resolveAuditResourceTarget({
|
|
||||||
userType: userType.value,
|
|
||||||
resourceType: exchangeDetail.value.old_asset_type,
|
|
||||||
internalId: exchangeDetail.value.old_asset_id
|
|
||||||
})
|
|
||||||
})
|
|
||||||
const newAssetAuditTarget = computed(() => {
|
|
||||||
if (!exchangeDetail.value || ![1, 2].includes(userType.value)) return null
|
|
||||||
return resolveAuditResourceTarget({
|
|
||||||
userType: userType.value,
|
|
||||||
resourceType: exchangeDetail.value.new_asset_type || '',
|
|
||||||
internalId: exchangeDetail.value.new_asset_id
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const formatExchangeShopName = (shopName?: string | null, shopId?: number | null) => {
|
const formatExchangeShopName = (shopName?: string | null, shopId?: number | null) => {
|
||||||
if (shopName) return shopName
|
if (shopName) return shopName
|
||||||
|
|||||||
@@ -851,15 +851,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '创建时间',
|
label: '起止时间',
|
||||||
prop: 'created_at_range',
|
prop: 'created_at_range',
|
||||||
type: 'date',
|
type: 'datetimerange',
|
||||||
config: {
|
config: {
|
||||||
type: 'daterange',
|
type: 'datetimerange',
|
||||||
rangeSeparator: '至',
|
rangeSeparator: '至',
|
||||||
startPlaceholder: '开始日期',
|
startPlaceholder: '开始日期',
|
||||||
endPlaceholder: '结束日期',
|
endPlaceholder: '结束日期',
|
||||||
valueFormat: 'YYYY-MM-DD'
|
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -80,7 +80,7 @@
|
|||||||
total_count: 0,
|
total_count: 0,
|
||||||
window_days: 15
|
window_days: 15
|
||||||
})
|
})
|
||||||
const searchForm = reactive<ExpiringAssetQueryParams>({
|
const searchForm = reactive<ExpiringAssetQueryParams & { expires_range: string[] }>({
|
||||||
asset_type: undefined,
|
asset_type: undefined,
|
||||||
keyword: '',
|
keyword: '',
|
||||||
shop_id: undefined,
|
shop_id: undefined,
|
||||||
@@ -88,7 +88,8 @@
|
|||||||
days_min: undefined,
|
days_min: undefined,
|
||||||
days_max: undefined,
|
days_max: undefined,
|
||||||
expires_from: undefined,
|
expires_from: undefined,
|
||||||
expires_to: undefined
|
expires_to: undefined,
|
||||||
|
expires_range: []
|
||||||
})
|
})
|
||||||
const pagination = reactive({ currentPage: 1, pageSize: 20, total: 0 })
|
const pagination = reactive({ currentPage: 1, pageSize: 20, total: 0 })
|
||||||
|
|
||||||
@@ -166,16 +167,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '预计到期起始',
|
label: '预计到期起止',
|
||||||
prop: 'expires_from',
|
prop: 'expires_range',
|
||||||
type: 'date',
|
type: 'datetimerange',
|
||||||
config: { valueFormat: 'YYYY-MM-DD', clearable: true }
|
config: {
|
||||||
},
|
type: 'datetimerange',
|
||||||
{
|
rangeSeparator: '至',
|
||||||
label: '预计到期结束',
|
startPlaceholder: '开始时间',
|
||||||
prop: 'expires_to',
|
endPlaceholder: '结束时间',
|
||||||
type: 'date',
|
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ',
|
||||||
config: { valueFormat: 'YYYY-MM-DD', clearable: true }
|
clearable: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -314,8 +316,9 @@
|
|||||||
const daysMax = toInteger(searchForm.days_max)
|
const daysMax = toInteger(searchForm.days_max)
|
||||||
if (daysMin !== undefined) params.days_min = daysMin
|
if (daysMin !== undefined) params.days_min = daysMin
|
||||||
if (daysMax !== undefined) params.days_max = daysMax
|
if (daysMax !== undefined) params.days_max = daysMax
|
||||||
if (searchForm.expires_from) params.expires_from = searchForm.expires_from
|
const [expiresFrom, expiresTo] = searchForm.expires_range || []
|
||||||
if (searchForm.expires_to) params.expires_to = searchForm.expires_to
|
if (expiresFrom) params.expires_from = expiresFrom
|
||||||
|
if (expiresTo) params.expires_to = expiresTo
|
||||||
|
|
||||||
const response = await AssetService.getExpiringAssets(params)
|
const response = await AssetService.getExpiringAssets(params)
|
||||||
if (response.code === 0 && response.data) {
|
if (response.code === 0 && response.data) {
|
||||||
@@ -347,7 +350,8 @@
|
|||||||
days_min: undefined,
|
days_min: undefined,
|
||||||
days_max: undefined,
|
days_max: undefined,
|
||||||
expires_from: undefined,
|
expires_from: undefined,
|
||||||
expires_to: undefined
|
expires_to: undefined,
|
||||||
|
expires_range: []
|
||||||
})
|
})
|
||||||
pagination.currentPage = 1
|
pagination.currentPage = 1
|
||||||
void loadAssets()
|
void loadAssets()
|
||||||
|
|||||||
@@ -100,15 +100,15 @@
|
|||||||
options: () => statusOptions
|
options: () => statusOptions
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '创建时间',
|
label: '起止时间',
|
||||||
prop: 'dateRange',
|
prop: 'dateRange',
|
||||||
type: 'date',
|
type: 'datetimerange',
|
||||||
config: {
|
config: {
|
||||||
type: 'daterange',
|
type: 'datetimerange',
|
||||||
rangeSeparator: '至',
|
rangeSeparator: '至',
|
||||||
startPlaceholder: '开始时间',
|
startPlaceholder: '开始时间',
|
||||||
endPlaceholder: '结束时间',
|
endPlaceholder: '结束时间',
|
||||||
valueFormat: 'YYYY-MM-DD'
|
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -10,22 +10,6 @@
|
|||||||
返回
|
返回
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<h2 class="detail-title">资产分配详情</h2>
|
<h2 class="detail-title">资产分配详情</h2>
|
||||||
<ElButton
|
|
||||||
v-if="recordAuditTarget && hasAuth(recordAuditPermission)"
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
@click="openAuditInvestigation(recordAuditTarget)"
|
|
||||||
>
|
|
||||||
{{ userType === 3 ? '活动记录' : '分配审计' }}
|
|
||||||
</ElButton>
|
|
||||||
<ElButton
|
|
||||||
v-if="assetAuditTarget && hasAuth(AUDIT_PERMISSIONS.assetAllocationAssetEntry)"
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
@click="openAuditInvestigation(assetAuditTarget)"
|
|
||||||
>
|
|
||||||
资产审计
|
|
||||||
</ElButton>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 详情内容 -->
|
<!-- 详情内容 -->
|
||||||
@@ -41,7 +25,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { ElCard, ElButton, ElIcon, ElMessage } from 'element-plus'
|
import { ElCard, ElButton, ElIcon, ElMessage } from 'element-plus'
|
||||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
||||||
@@ -50,44 +34,14 @@
|
|||||||
import { CardService } from '@/api/modules'
|
import { CardService } from '@/api/modules'
|
||||||
import type { AssetAllocationRecord } from '@/types/api/card'
|
import type { AssetAllocationRecord } from '@/types/api/card'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
|
||||||
import { useUserStore } from '@/store/modules/user'
|
|
||||||
import { AUDIT_PERMISSIONS } from '@/config/constants/audit'
|
|
||||||
import { resolveAuditResourceTarget } from '@/utils/business/auditNavigation'
|
|
||||||
import { openAuditInvestigation } from '@/components/business/audit/investigationController'
|
|
||||||
|
|
||||||
defineOptions({ name: 'AssetAssignDetail' })
|
defineOptions({ name: 'AssetAssignDetail' })
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { hasAuth } = useAuth()
|
|
||||||
const userStore = useUserStore()
|
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const detailData = ref<AssetAllocationRecord | null>(null)
|
const detailData = ref<AssetAllocationRecord | null>(null)
|
||||||
const userType = computed(() => Number(userStore.info.user_type))
|
|
||||||
const recordAuditPermission = computed(() =>
|
|
||||||
userType.value === 3
|
|
||||||
? AUDIT_PERMISSIONS.agentAssetAllocationActivity
|
|
||||||
: AUDIT_PERMISSIONS.assetAllocationEntry
|
|
||||||
)
|
|
||||||
const recordAuditTarget = computed(() => {
|
|
||||||
if (!detailData.value || ![1, 2, 3].includes(userType.value)) return null
|
|
||||||
return resolveAuditResourceTarget({
|
|
||||||
userType: userType.value,
|
|
||||||
resourceType: 'asset_allocation_record',
|
|
||||||
internalId: detailData.value.id,
|
|
||||||
businessIdentifier: detailData.value.allocation_no
|
|
||||||
})
|
|
||||||
})
|
|
||||||
const assetAuditTarget = computed(() => {
|
|
||||||
if (!detailData.value || ![1, 2].includes(userType.value)) return null
|
|
||||||
return resolveAuditResourceTarget({
|
|
||||||
userType: userType.value,
|
|
||||||
resourceType: detailData.value.asset_type,
|
|
||||||
internalId: detailData.value.asset_id
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// 详情页配置
|
// 详情页配置
|
||||||
const detailSections: DetailSection[] = [
|
const detailSections: DetailSection[] = [
|
||||||
|
|||||||
@@ -172,16 +172,16 @@
|
|||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '创建时间',
|
label: '起止时间',
|
||||||
prop: 'dateRange',
|
prop: 'dateRange',
|
||||||
type: 'date',
|
type: 'datetimerange',
|
||||||
config: {
|
config: {
|
||||||
type: 'daterange',
|
type: 'datetimerange',
|
||||||
clearable: true,
|
clearable: true,
|
||||||
rangeSeparator: '至',
|
rangeSeparator: '至',
|
||||||
startPlaceholder: '开始日期',
|
startPlaceholder: '开始日期',
|
||||||
endPlaceholder: '结束日期',
|
endPlaceholder: '结束日期',
|
||||||
valueFormat: 'YYYY-MM-DD'
|
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -200,12 +200,12 @@
|
|||||||
{
|
{
|
||||||
label: '授权时间',
|
label: '授权时间',
|
||||||
prop: 'dateRange',
|
prop: 'dateRange',
|
||||||
type: 'daterange',
|
type: 'datetimerange',
|
||||||
config: {
|
config: {
|
||||||
type: 'daterange',
|
type: 'datetimerange',
|
||||||
startPlaceholder: '开始日期',
|
startPlaceholder: '开始日期',
|
||||||
endPlaceholder: '结束日期',
|
endPlaceholder: '结束日期',
|
||||||
valueFormat: 'YYYY-MM-DD'
|
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -213,9 +213,9 @@
|
|||||||
{
|
{
|
||||||
label: '创建时间',
|
label: '创建时间',
|
||||||
prop: 'dateRange',
|
prop: 'dateRange',
|
||||||
type: 'daterange',
|
type: 'datetimerange',
|
||||||
config: {
|
config: {
|
||||||
type: 'daterange',
|
type: 'datetimerange',
|
||||||
startPlaceholder: '开始时间',
|
startPlaceholder: '开始时间',
|
||||||
endPlaceholder: '结束时间',
|
endPlaceholder: '结束时间',
|
||||||
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ'
|
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ'
|
||||||
|
|||||||
@@ -329,9 +329,9 @@
|
|||||||
{
|
{
|
||||||
label: '创建时间',
|
label: '创建时间',
|
||||||
prop: 'dateRange',
|
prop: 'dateRange',
|
||||||
type: 'daterange',
|
type: 'datetimerange',
|
||||||
config: {
|
config: {
|
||||||
type: 'daterange',
|
type: 'datetimerange',
|
||||||
startPlaceholder: '开始时间',
|
startPlaceholder: '开始时间',
|
||||||
endPlaceholder: '结束时间',
|
endPlaceholder: '结束时间',
|
||||||
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ'
|
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ'
|
||||||
|
|||||||
@@ -52,8 +52,15 @@
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const items = ref<AuditEventView[]>([])
|
const items = ref<AuditEventView[]>([])
|
||||||
const pagination = reactive({ page: 1, pageSize: 20, total: 0 })
|
const pagination = reactive({ page: 1, pageSize: 20, total: 0 })
|
||||||
|
function createDefaultDateRange() {
|
||||||
|
const end = new Date()
|
||||||
|
const start = new Date(end.getTime() - 24 * 60 * 60 * 1000)
|
||||||
|
const format = (date: Date) => date.toISOString().replace(/\.\d{3}Z$/, 'Z')
|
||||||
|
return [format(start), format(end)]
|
||||||
|
}
|
||||||
|
|
||||||
const initialSearchState = {
|
const initialSearchState = {
|
||||||
dateRange: [] as string[],
|
dateRange: createDefaultDateRange(),
|
||||||
category: undefined,
|
category: undefined,
|
||||||
result: undefined,
|
result: undefined,
|
||||||
risk: undefined,
|
risk: undefined,
|
||||||
@@ -107,17 +114,25 @@
|
|||||||
{ label: '店铺', value: 'shop' },
|
{ label: '店铺', value: 'shop' },
|
||||||
{ label: '个人客户', value: 'personal_customer' }
|
{ label: '个人客户', value: 'personal_customer' }
|
||||||
]
|
]
|
||||||
|
const handleDateRangeChange = (value: unknown) => {
|
||||||
|
if (!Array.isArray(value) || value.length !== 2) {
|
||||||
|
searchForm.dateRange = createDefaultDateRange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const searchFormItems: SearchFormItem[] = [
|
const searchFormItems: SearchFormItem[] = [
|
||||||
{
|
{
|
||||||
label: '起止时间',
|
label: '起止时间',
|
||||||
prop: 'dateRange',
|
prop: 'dateRange',
|
||||||
type: 'date',
|
type: 'datetimerange',
|
||||||
|
onChange: ({ val }) => handleDateRangeChange(val),
|
||||||
config: {
|
config: {
|
||||||
type: 'daterange',
|
type: 'datetimerange',
|
||||||
rangeSeparator: '至',
|
rangeSeparator: '至',
|
||||||
startPlaceholder: '开始时间',
|
startPlaceholder: '开始时间',
|
||||||
endPlaceholder: '结束时间',
|
endPlaceholder: '结束时间',
|
||||||
valueFormat: 'YYYY-MM-DD'
|
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ',
|
||||||
|
clearable: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -241,18 +256,11 @@
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
const toAuditDate = (value?: string, endExclusive = false) => {
|
|
||||||
if (!value) return undefined
|
|
||||||
const [year, month, day] = value.split('-').map(Number)
|
|
||||||
const date = new Date(year, month - 1, day + (endExclusive ? 1 : 0))
|
|
||||||
return date.toISOString()
|
|
||||||
}
|
|
||||||
|
|
||||||
const buildQuery = (): AuditEventQuery => ({
|
const buildQuery = (): AuditEventQuery => ({
|
||||||
page: pagination.page,
|
page: pagination.page,
|
||||||
page_size: pagination.pageSize,
|
page_size: pagination.pageSize,
|
||||||
created_from: toAuditDate(searchForm.dateRange?.[0]),
|
created_from: searchForm.dateRange?.[0],
|
||||||
created_to: toAuditDate(searchForm.dateRange?.[1], true),
|
created_to: searchForm.dateRange?.[1],
|
||||||
category: searchForm.category,
|
category: searchForm.category,
|
||||||
result: searchForm.result,
|
result: searchForm.result,
|
||||||
risk: searchForm.risk,
|
risk: searchForm.risk,
|
||||||
@@ -277,7 +285,7 @@
|
|||||||
load()
|
load()
|
||||||
}
|
}
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
Object.assign(searchForm, { ...initialSearchState, dateRange: [] })
|
Object.assign(searchForm, { ...initialSearchState, dateRange: createDefaultDateRange() })
|
||||||
pagination.page = 1
|
pagination.page = 1
|
||||||
load()
|
load()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
value-format="YYYY-MM-DDTHH:mm:ssZ"
|
value-format="YYYY-MM-DDTHH:mm:ssZ"
|
||||||
start-placeholder="开始时间"
|
start-placeholder="开始时间"
|
||||||
end-placeholder="结束时间"
|
end-placeholder="结束时间"
|
||||||
|
:clearable="false"
|
||||||
|
@change="handleDateRangeChange"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
@@ -19,10 +21,10 @@
|
|||||||
<ElFormItem>
|
<ElFormItem>
|
||||||
<ElSelect v-model="query.provider" clearable placeholder="请选择提供方">
|
<ElSelect v-model="query.provider" clearable placeholder="请选择提供方">
|
||||||
<ElOption
|
<ElOption
|
||||||
v-for="item in overview?.providers || []"
|
v-for="item in providerOptions"
|
||||||
:key="item.code"
|
:key="item.value"
|
||||||
:label="item.name"
|
:label="item.label"
|
||||||
:value="item.code"
|
:value="item.value"
|
||||||
/>
|
/>
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
@@ -31,10 +33,10 @@
|
|||||||
<ElFormItem>
|
<ElFormItem>
|
||||||
<ElSelect v-model="query.direction" clearable placeholder="请选择方向">
|
<ElSelect v-model="query.direction" clearable placeholder="请选择方向">
|
||||||
<ElOption
|
<ElOption
|
||||||
v-for="item in overview?.directions || []"
|
v-for="item in directionOptions"
|
||||||
:key="item.code"
|
:key="item.value"
|
||||||
:label="item.name"
|
:label="item.label"
|
||||||
:value="item.code"
|
:value="item.value"
|
||||||
/>
|
/>
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
@@ -43,10 +45,10 @@
|
|||||||
<ElFormItem>
|
<ElFormItem>
|
||||||
<ElSelect v-model="query.result" clearable placeholder="请选择结果">
|
<ElSelect v-model="query.result" clearable placeholder="请选择结果">
|
||||||
<ElOption
|
<ElOption
|
||||||
v-for="item in overview?.results || []"
|
v-for="item in resultOptions"
|
||||||
:key="item.code"
|
:key="item.value"
|
||||||
:label="item.name"
|
:label="item.label"
|
||||||
:value="item.code"
|
:value="item.value"
|
||||||
/>
|
/>
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
@@ -231,9 +233,12 @@
|
|||||||
import { AuditService } from '@/api/modules'
|
import { AuditService } from '@/api/modules'
|
||||||
import type {
|
import type {
|
||||||
AuditNamedCount,
|
AuditNamedCount,
|
||||||
|
IntegrationDirection,
|
||||||
IntegrationListItem,
|
IntegrationListItem,
|
||||||
IntegrationOverview,
|
IntegrationOverview,
|
||||||
IntegrationQuery
|
IntegrationProvider,
|
||||||
|
IntegrationQuery,
|
||||||
|
IntegrationResult
|
||||||
} from '@/types/api'
|
} from '@/types/api'
|
||||||
import { auditResultDisplay, integrationCategoryMeta } from '@/utils/business/audit'
|
import { auditResultDisplay, integrationCategoryMeta } from '@/utils/business/audit'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
@@ -243,11 +248,39 @@
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const headerColumns = ref([])
|
const headerColumns = ref([])
|
||||||
const dateRange = ref<string[]>([])
|
const dateRange = ref<string[]>(createDefaultDateRange())
|
||||||
const query = reactive<IntegrationQuery>({ page: 1, page_size: 20 })
|
const query = reactive<IntegrationQuery>({ page: 1, page_size: 20 })
|
||||||
const overview = ref<IntegrationOverview>()
|
const overview = ref<IntegrationOverview>()
|
||||||
const items = ref<IntegrationListItem[]>([])
|
const items = ref<IntegrationListItem[]>([])
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
|
const providerOptions: Array<{ label: string; value: IntegrationProvider }> = [
|
||||||
|
{ label: '中国电信', value: 'ctcc' },
|
||||||
|
{ label: '中国移动', value: 'cmcc' },
|
||||||
|
{ label: '中国联通', value: 'cucc' },
|
||||||
|
{ label: '微信支付', value: 'wechat_pay' },
|
||||||
|
{ label: '支付宝', value: 'alipay' },
|
||||||
|
{ label: '富友', value: 'fuiou' },
|
||||||
|
{ label: '企业微信', value: 'wecom' },
|
||||||
|
{ label: '设备网关', value: 'gateway' }
|
||||||
|
]
|
||||||
|
const directionOptions: Array<{ label: string; value: IntegrationDirection }> = [
|
||||||
|
{ label: '入站', value: 'inbound' },
|
||||||
|
{ label: '出站', value: 'outbound' }
|
||||||
|
]
|
||||||
|
const resultOptions: Array<{ label: string; value: IntegrationResult }> = [
|
||||||
|
{ label: '待处理', value: 'pending' },
|
||||||
|
{ label: '成功', value: 'success' },
|
||||||
|
{ label: '失败', value: 'failed' },
|
||||||
|
{ label: '结果未知', value: 'unknown' },
|
||||||
|
{ label: '未找到', value: 'not_found' },
|
||||||
|
{ label: '无效载荷', value: 'invalid_payload' },
|
||||||
|
{ label: '冲突', value: 'conflict' },
|
||||||
|
{ label: '已忽略', value: 'ignored' },
|
||||||
|
{ label: '已合并', value: 'merged' },
|
||||||
|
{ label: '已限频', value: 'rate_limited' },
|
||||||
|
{ label: '已提前完成', value: 'completed' },
|
||||||
|
{ label: '已取消', value: 'cancelled' }
|
||||||
|
]
|
||||||
const aggregateChartData = (values: AuditNamedCount[]) => {
|
const aggregateChartData = (values: AuditNamedCount[]) => {
|
||||||
const totals = new Map<string, AuditNamedCount>()
|
const totals = new Map<string, AuditNamedCount>()
|
||||||
values.forEach((item) => {
|
values.forEach((item) => {
|
||||||
@@ -261,9 +294,21 @@
|
|||||||
const providerChartData = computed(() => aggregateChartData(overview.value?.providers || []))
|
const providerChartData = computed(() => aggregateChartData(overview.value?.providers || []))
|
||||||
const directionChartData = computed(() => aggregateChartData(overview.value?.directions || []))
|
const directionChartData = computed(() => aggregateChartData(overview.value?.directions || []))
|
||||||
const resultChartData = computed(() => aggregateChartData(overview.value?.results || []))
|
const resultChartData = computed(() => aggregateChartData(overview.value?.results || []))
|
||||||
|
function createDefaultDateRange() {
|
||||||
|
const end = new Date()
|
||||||
|
const start = new Date(end.getTime() - 24 * 60 * 60 * 1000)
|
||||||
|
const format = (date: Date) => date.toISOString().replace(/\.\d{3}Z$/, 'Z')
|
||||||
|
return [format(start), format(end)]
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDateRangeChange = (value: string[] | null) => {
|
||||||
|
if (!value || value.length !== 2) dateRange.value = createDefaultDateRange()
|
||||||
|
}
|
||||||
|
|
||||||
const syncDates = () => {
|
const syncDates = () => {
|
||||||
query.created_from = dateRange.value?.[0] || undefined
|
if (dateRange.value.length !== 2) dateRange.value = createDefaultDateRange()
|
||||||
query.created_to = dateRange.value?.[1] || undefined
|
query.created_from = dateRange.value[0]
|
||||||
|
query.created_to = dateRange.value[1]
|
||||||
}
|
}
|
||||||
const loadList = async () => {
|
const loadList = async () => {
|
||||||
syncDates()
|
syncDates()
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
start-placeholder="开始时间"
|
start-placeholder="开始时间"
|
||||||
end-placeholder="结束时间"
|
end-placeholder="结束时间"
|
||||||
value-format="YYYY-MM-DDTHH:mm:ssZ"
|
value-format="YYYY-MM-DDTHH:mm:ssZ"
|
||||||
|
:clearable="false"
|
||||||
|
@change="handleDateRangeChange"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
@@ -184,7 +186,7 @@
|
|||||||
const { hasAuth } = useAuth()
|
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[]>(createDefaultDateRange())
|
||||||
const overview = ref<AuditRiskOverview>()
|
const overview = ref<AuditRiskOverview>()
|
||||||
const events = ref<AuditEventView[]>([])
|
const events = ref<AuditEventView[]>([])
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
@@ -202,9 +204,21 @@
|
|||||||
const riskChartData = computed(() => aggregateChartData(overview.value?.risks || []))
|
const riskChartData = computed(() => aggregateChartData(overview.value?.risks || []))
|
||||||
const resultChartData = computed(() => aggregateChartData(overview.value?.results || []))
|
const resultChartData = computed(() => aggregateChartData(overview.value?.results || []))
|
||||||
const sourceChartData = computed(() => aggregateChartData(overview.value?.sources || []))
|
const sourceChartData = computed(() => aggregateChartData(overview.value?.sources || []))
|
||||||
|
function createDefaultDateRange() {
|
||||||
|
const end = new Date()
|
||||||
|
const start = new Date(end.getTime() - 24 * 60 * 60 * 1000)
|
||||||
|
const format = (date: Date) => date.toISOString().replace(/\.\d{3}Z$/, 'Z')
|
||||||
|
return [format(start), format(end)]
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDateRangeChange = (value: string[] | null) => {
|
||||||
|
if (!value || value.length !== 2) dateRange.value = createDefaultDateRange()
|
||||||
|
}
|
||||||
|
|
||||||
const syncDates = () => {
|
const syncDates = () => {
|
||||||
query.created_from = dateRange.value?.[0] || undefined
|
if (dateRange.value.length !== 2) dateRange.value = createDefaultDateRange()
|
||||||
query.created_to = dateRange.value?.[1] || undefined
|
query.created_from = dateRange.value[0]
|
||||||
|
query.created_to = dateRange.value[1]
|
||||||
if (
|
if (
|
||||||
dateRange.value?.length === 2 &&
|
dateRange.value?.length === 2 &&
|
||||||
new Date(dateRange.value[1]).getTime() - new Date(dateRange.value[0]).getTime() >
|
new Date(dateRange.value[1]).getTime() - new Date(dateRange.value[0]).getTime() >
|
||||||
|
|||||||
@@ -249,11 +249,11 @@
|
|||||||
<ElFormItem label="交易日期">
|
<ElFormItem label="交易日期">
|
||||||
<ElDatePicker
|
<ElDatePicker
|
||||||
v-model="mainWalletSearchForm.date_range"
|
v-model="mainWalletSearchForm.date_range"
|
||||||
type="daterange"
|
type="datetimerange"
|
||||||
range-separator="至"
|
range-separator="至"
|
||||||
start-placeholder="开始日期"
|
start-placeholder="开始日期"
|
||||||
end-placeholder="结束日期"
|
end-placeholder="结束日期"
|
||||||
value-format="YYYY-MM-DD"
|
value-format="YYYY-MM-DDTHH:mm:ssZ"
|
||||||
unlink-panels
|
unlink-panels
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
|||||||
@@ -400,11 +400,11 @@
|
|||||||
{
|
{
|
||||||
label: '起止时间',
|
label: '起止时间',
|
||||||
prop: 'dateRange',
|
prop: 'dateRange',
|
||||||
type: 'date',
|
type: 'datetimerange',
|
||||||
config: {
|
config: {
|
||||||
type: 'daterange',
|
type: 'datetimerange',
|
||||||
clearable: true,
|
clearable: true,
|
||||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ',
|
||||||
startPlaceholder: '开始日期',
|
startPlaceholder: '开始日期',
|
||||||
endPlaceholder: '结束日期'
|
endPlaceholder: '结束日期'
|
||||||
},
|
},
|
||||||
@@ -479,16 +479,19 @@
|
|||||||
{
|
{
|
||||||
prop: 'iccid',
|
prop: 'iccid',
|
||||||
label: 'ICCID',
|
label: 'ICCID',
|
||||||
|
width: 200,
|
||||||
formatter: (row: ShopCommissionRecordItem) => row.iccid || '-'
|
formatter: (row: ShopCommissionRecordItem) => row.iccid || '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'virtual_no',
|
prop: 'virtual_no',
|
||||||
label: '虚拟号',
|
label: '虚拟号',
|
||||||
|
width: 180,
|
||||||
formatter: (row: ShopCommissionRecordItem) => row.virtual_no || '-'
|
formatter: (row: ShopCommissionRecordItem) => row.virtual_no || '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'order_no',
|
prop: 'order_no',
|
||||||
label: '订单号',
|
label: '订单号',
|
||||||
|
width: 220,
|
||||||
formatter: (row: ShopCommissionRecordItem) => row.order_no || '-'
|
formatter: (row: ShopCommissionRecordItem) => row.order_no || '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -608,11 +611,11 @@
|
|||||||
{
|
{
|
||||||
label: '起止时间',
|
label: '起止时间',
|
||||||
prop: 'dateRange',
|
prop: 'dateRange',
|
||||||
type: 'date',
|
type: 'datetimerange',
|
||||||
config: {
|
config: {
|
||||||
type: 'daterange',
|
type: 'datetimerange',
|
||||||
clearable: true,
|
clearable: true,
|
||||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ',
|
||||||
startPlaceholder: '开始日期',
|
startPlaceholder: '开始日期',
|
||||||
endPlaceholder: '结束日期'
|
endPlaceholder: '结束日期'
|
||||||
},
|
},
|
||||||
@@ -656,7 +659,7 @@
|
|||||||
{
|
{
|
||||||
prop: 'withdrawal_no',
|
prop: 'withdrawal_no',
|
||||||
label: '提现单号',
|
label: '提现单号',
|
||||||
minWidth: 160
|
minWidth: 180
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'amount',
|
prop: 'amount',
|
||||||
@@ -700,7 +703,8 @@
|
|||||||
{
|
{
|
||||||
prop: 'reject_reason',
|
prop: 'reject_reason',
|
||||||
label: '拒绝原因',
|
label: '拒绝原因',
|
||||||
minWidth: 120,
|
minWidth: 230,
|
||||||
|
showOverflowTooltip: true,
|
||||||
formatter: (row: WithdrawalRequestItem) => row.reject_reason || '-'
|
formatter: (row: WithdrawalRequestItem) => row.reject_reason || '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -165,13 +165,13 @@
|
|||||||
{
|
{
|
||||||
label: '起止时间',
|
label: '起止时间',
|
||||||
prop: 'dateRange',
|
prop: 'dateRange',
|
||||||
type: 'date',
|
type: 'datetimerange',
|
||||||
config: {
|
config: {
|
||||||
type: 'daterange',
|
type: 'datetimerange',
|
||||||
rangeSeparator: '至',
|
rangeSeparator: '至',
|
||||||
startPlaceholder: '开始日期',
|
startPlaceholder: '开始日期',
|
||||||
endPlaceholder: '结束日期',
|
endPlaceholder: '结束日期',
|
||||||
valueFormat: 'YYYY-MM-DD'
|
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ interface BuildAgentRechargeActionsOptions {
|
|||||||
onViewPaymentVoucher: (row: AgentRecharge) => void
|
onViewPaymentVoucher: (row: AgentRecharge) => void
|
||||||
onConfirmPayment: (row: AgentRecharge) => void
|
onConfirmPayment: (row: AgentRecharge) => void
|
||||||
onReject: (row: AgentRecharge) => void
|
onReject: (row: AgentRecharge) => void
|
||||||
|
onTriggerApproval: (row: AgentRecharge) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const buildAgentRechargeActions = (
|
export const buildAgentRechargeActions = (
|
||||||
@@ -24,6 +25,14 @@ export const buildAgentRechargeActions = (
|
|||||||
row.approval_source === 'wecom' ||
|
row.approval_source === 'wecom' ||
|
||||||
row.approval_source === 'legacy' ||
|
row.approval_source === 'legacy' ||
|
||||||
(row.approval_instance_id !== undefined && row.approval_instance_id !== null)
|
(row.approval_instance_id !== undefined && row.approval_instance_id !== null)
|
||||||
|
const approvalStatusEmpty = row.approval_status === undefined || row.approval_status === null
|
||||||
|
const canTriggerApproval =
|
||||||
|
approvalStatusEmpty &&
|
||||||
|
![
|
||||||
|
AgentRechargeStatus.COMPLETED,
|
||||||
|
AgentRechargeStatus.REJECTED,
|
||||||
|
AgentRechargeStatus.CLOSED
|
||||||
|
].includes(row.status)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
row.payment_method === 'offline' &&
|
row.payment_method === 'offline' &&
|
||||||
@@ -31,7 +40,7 @@ export const buildAgentRechargeActions = (
|
|||||||
options.hasAuth('agent_recharge:view_payment_voucher')
|
options.hasAuth('agent_recharge:view_payment_voucher')
|
||||||
) {
|
) {
|
||||||
actions.push({
|
actions.push({
|
||||||
label: '查看支付凭证',
|
label: '支付凭证',
|
||||||
handler: () => options.onViewPaymentVoucher(row),
|
handler: () => options.onViewPaymentVoucher(row),
|
||||||
type: 'primary'
|
type: 'primary'
|
||||||
})
|
})
|
||||||
@@ -50,6 +59,14 @@ export const buildAgentRechargeActions = (
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (canTriggerApproval && options.hasAuth('agent_recharge:trigger_approval')) {
|
||||||
|
actions.push({
|
||||||
|
label: '补发审批',
|
||||||
|
handler: () => options.onTriggerApproval(row),
|
||||||
|
type: 'primary'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!hasApprovalRecord &&
|
!hasApprovalRecord &&
|
||||||
row.status === AgentRechargeStatus.PENDING &&
|
row.status === AgentRechargeStatus.PENDING &&
|
||||||
|
|||||||
@@ -10,36 +10,6 @@
|
|||||||
返回
|
返回
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<h2 class="detail-title">{{ pageTitle }}</h2>
|
<h2 class="detail-title">{{ pageTitle }}</h2>
|
||||||
<ElButton
|
|
||||||
v-if="detailData && isPlatformUser && hasAuth(AUDIT_PERMISSIONS.agentRechargeEntry)"
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
@click="openRechargeAudit"
|
|
||||||
>
|
|
||||||
审计记录
|
|
||||||
</ElButton>
|
|
||||||
<ElButton
|
|
||||||
v-if="
|
|
||||||
detailData && isPlatformUser && hasAuth(AUDIT_PERMISSIONS.agentRechargeFinanceEntry)
|
|
||||||
"
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
@click="openRechargeFinance"
|
|
||||||
>
|
|
||||||
资金链路
|
|
||||||
</ElButton>
|
|
||||||
<ElButton
|
|
||||||
v-if="
|
|
||||||
detailData?.approval_instance_id &&
|
|
||||||
isPlatformUser &&
|
|
||||||
hasAuth(AUDIT_PERMISSIONS.agentRechargeApprovalEntry)
|
|
||||||
"
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
@click="openApprovalAudit"
|
|
||||||
>
|
|
||||||
审批审计
|
|
||||||
</ElButton>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 详情内容 -->
|
<!-- 详情内容 -->
|
||||||
@@ -71,45 +41,19 @@
|
|||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import { hasVoucherKeys, toVoucherKeyList } from '@/utils/business'
|
import { hasVoucherKeys, toVoucherKeyList } from '@/utils/business'
|
||||||
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
|
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
|
||||||
import { formatRejectionReason } from './agentRechargeDisplay'
|
|
||||||
import { useAuth } from '@/composables/useAuth'
|
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
import { AUDIT_PERMISSIONS } from '@/config/constants/audit'
|
import { formatRejectionReason } from './agentRechargeDisplay'
|
||||||
import {
|
|
||||||
resolveAuditResourceTarget,
|
|
||||||
resolveFinanceAuditTarget
|
|
||||||
} from '@/utils/business/auditNavigation'
|
|
||||||
import { openAuditInvestigation } from '@/components/business/audit/investigationController'
|
|
||||||
|
|
||||||
defineOptions({ name: 'AgentRechargeDetail' })
|
defineOptions({ name: 'AgentRechargeDetail' })
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { hasAuth } = useAuth()
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
const isRestrictedCustomerRole = computed(() => [3, 4].includes(Number(userStore.info.user_type)))
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const detailData = ref<AgentRecharge | null>(null)
|
const detailData = ref<AgentRecharge | null>(null)
|
||||||
const paymentVoucherFileKeys = ref<string[]>([])
|
const paymentVoucherFileKeys = ref<string[]>([])
|
||||||
const isPlatformUser = computed(() => [1, 2].includes(Number(userStore.info.user_type)))
|
|
||||||
const openRechargeAudit = () => {
|
|
||||||
const target = resolveAuditResourceTarget({
|
|
||||||
resourceType: 'agent_recharge',
|
|
||||||
internalId: detailData.value?.id
|
|
||||||
})
|
|
||||||
if (target) openAuditInvestigation(target)
|
|
||||||
}
|
|
||||||
const openRechargeFinance = () => {
|
|
||||||
const target = resolveFinanceAuditTarget('recharge_id', detailData.value?.id)
|
|
||||||
if (target) openAuditInvestigation(target)
|
|
||||||
}
|
|
||||||
const openApprovalAudit = () => {
|
|
||||||
const target = resolveAuditResourceTarget({
|
|
||||||
resourceType: 'approval_instance',
|
|
||||||
internalId: detailData.value?.approval_instance_id
|
|
||||||
})
|
|
||||||
if (target) openAuditInvestigation(target)
|
|
||||||
}
|
|
||||||
|
|
||||||
const pageTitle = computed(() => `充值订单详情`)
|
const pageTitle = computed(() => `充值订单详情`)
|
||||||
|
|
||||||
@@ -171,7 +115,15 @@
|
|||||||
{ label: '充值单号', prop: 'recharge_no' },
|
{ label: '充值单号', prop: 'recharge_no' },
|
||||||
{ label: '店铺名称', prop: 'shop_name' },
|
{ label: '店铺名称', prop: 'shop_name' },
|
||||||
{ label: '充值来源', formatter: (_, data) => getRechargeSourceText(data) },
|
{ label: '充值来源', formatter: (_, data) => getRechargeSourceText(data) },
|
||||||
{ label: '提交人', prop: 'submitter_name', formatter: (value) => value || '-' },
|
...(!isRestrictedCustomerRole.value
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
label: '提交人',
|
||||||
|
prop: 'submitter_name',
|
||||||
|
formatter: (value: string | null | undefined) => value || '-'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []),
|
||||||
{
|
{
|
||||||
label: '充值金额',
|
label: '充值金额',
|
||||||
formatter: (_, data) => formatCurrency(data.amount)
|
formatter: (_, data) => formatCurrency(data.amount)
|
||||||
@@ -181,15 +133,19 @@
|
|||||||
render: (data) =>
|
render: (data) =>
|
||||||
h(ElTag, { type: getStatusType(data.status) }, () => data.status_name || '-')
|
h(ElTag, { type: getStatusType(data.status) }, () => data.status_name || '-')
|
||||||
},
|
},
|
||||||
|
...(!isRestrictedCustomerRole.value
|
||||||
|
? [
|
||||||
{
|
{
|
||||||
label: '驳回原因',
|
label: '驳回原因',
|
||||||
prop: 'rejection_reason',
|
prop: 'rejection_reason',
|
||||||
formatter: (value) => formatRejectionReason(value),
|
formatter: (value: string | null | undefined) => formatRejectionReason(value),
|
||||||
fullWidth: true
|
fullWidth: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
: [])
|
||||||
|
]
|
||||||
},
|
},
|
||||||
...(isOfflineRecharge
|
...(isOfflineRecharge && !isRestrictedCustomerRole.value
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
title: '企微审批信息',
|
title: '企微审批信息',
|
||||||
@@ -212,15 +168,19 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
|
...(!isRestrictedCustomerRole.value
|
||||||
|
? [
|
||||||
{
|
{
|
||||||
title: '业务处理结果',
|
title: '业务处理结果',
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
label: '处理状态',
|
label: '处理状态',
|
||||||
formatter: (_, data) => data.processing_status_name || '-'
|
formatter: (_: unknown, data: AgentRecharge) => data.processing_status_name || '-'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
|
]
|
||||||
|
: []),
|
||||||
{
|
{
|
||||||
title: '支付信息',
|
title: '支付信息',
|
||||||
fields: [
|
fields: [
|
||||||
@@ -269,13 +229,17 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
|
...(!isRestrictedCustomerRole.value
|
||||||
|
? [
|
||||||
{
|
{
|
||||||
label: '运营备注',
|
label: '运营备注',
|
||||||
prop: 'remark',
|
prop: 'remark',
|
||||||
formatter: (value) => value || '-',
|
formatter: (value: string | null | undefined) => value || '-',
|
||||||
fullWidth: true
|
fullWidth: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
: [])
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '时间信息',
|
title: '时间信息',
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<ArtSearchBar
|
<ArtSearchBar
|
||||||
v-model:filter="searchForm"
|
v-model:filter="searchForm"
|
||||||
:items="searchFormItems"
|
:items="searchFormItems"
|
||||||
:show-expand="false"
|
show-expand
|
||||||
@reset="handleReset"
|
@reset="handleReset"
|
||||||
@search="handleSearch"
|
@search="handleSearch"
|
||||||
></ArtSearchBar>
|
></ArtSearchBar>
|
||||||
@@ -300,6 +300,7 @@
|
|||||||
import { AgentRechargeService, CommissionService, ShopService } from '@/api/modules'
|
import { AgentRechargeService, CommissionService, ShopService } from '@/api/modules'
|
||||||
import {
|
import {
|
||||||
ElMessage,
|
ElMessage,
|
||||||
|
ElMessageBox,
|
||||||
ElTag,
|
ElTag,
|
||||||
ElButton,
|
ElButton,
|
||||||
ElCascader,
|
ElCascader,
|
||||||
@@ -356,6 +357,18 @@
|
|||||||
|
|
||||||
const isAgentAccount = computed(() => Number(userStore.info.user_type) === 3)
|
const isAgentAccount = computed(() => Number(userStore.info.user_type) === 3)
|
||||||
const isPlatformAccount = computed(() => [1, 2].includes(Number(userStore.info.user_type)))
|
const isPlatformAccount = computed(() => [1, 2].includes(Number(userStore.info.user_type)))
|
||||||
|
const isRestrictedCustomerRole = computed(() => [3, 4].includes(Number(userStore.info.user_type)))
|
||||||
|
const hiddenInternalColumnProps = new Set([
|
||||||
|
'approval_provider',
|
||||||
|
'submitter_name',
|
||||||
|
'approval_status',
|
||||||
|
'current_approver_summary',
|
||||||
|
'processing_status_name',
|
||||||
|
'remark',
|
||||||
|
'rejection_reason'
|
||||||
|
])
|
||||||
|
const shouldShowInternalColumn = (prop?: string) =>
|
||||||
|
!isRestrictedCustomerRole.value || !hiddenInternalColumnProps.has(prop || '')
|
||||||
const canViewRecharge = computed(() => [1, 2, 3].includes(Number(userStore.info.user_type)))
|
const canViewRecharge = computed(() => [1, 2, 3].includes(Number(userStore.info.user_type)))
|
||||||
const canCreateRecharge = computed(() => isAgentAccount.value || isPlatformAccount.value)
|
const canCreateRecharge = computed(() => isAgentAccount.value || isPlatformAccount.value)
|
||||||
const createMode = ref<'online' | 'offline'>(isAgentAccount.value ? 'online' : 'offline')
|
const createMode = ref<'online' | 'offline'>(isAgentAccount.value ? 'online' : 'offline')
|
||||||
@@ -369,6 +382,7 @@
|
|||||||
const voucherUploading = ref(false)
|
const voucherUploading = ref(false)
|
||||||
const confirmPayLoading = ref(false)
|
const confirmPayLoading = ref(false)
|
||||||
const rejectLoading = ref(false)
|
const rejectLoading = ref(false)
|
||||||
|
const triggerApprovalLoading = ref(false)
|
||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
const createDialogVisible = ref(false)
|
const createDialogVisible = ref(false)
|
||||||
const exportDialogVisible = ref(false)
|
const exportDialogVisible = ref(false)
|
||||||
@@ -385,7 +399,7 @@
|
|||||||
const qrContent = ref('')
|
const qrContent = ref('')
|
||||||
const onlineRequestId = ref<string | null>(null)
|
const onlineRequestId = ref<string | null>(null)
|
||||||
const paymentStatusTimer = ref<ReturnType<typeof setInterval> | null>(null)
|
const paymentStatusTimer = ref<ReturnType<typeof setInterval> | null>(null)
|
||||||
const ONLINE_MIN_RECHARGE_AMOUNT_FEN = 10
|
const ONLINE_MIN_RECHARGE_AMOUNT_FEN = 10000
|
||||||
const ONLINE_MAX_RECHARGE_AMOUNT_FEN = 100000000
|
const ONLINE_MAX_RECHARGE_AMOUNT_FEN = 100000000
|
||||||
const paymentMethodsBounds = reactive({
|
const paymentMethodsBounds = reactive({
|
||||||
min_amount: ONLINE_MIN_RECHARGE_AMOUNT_FEN,
|
min_amount: ONLINE_MIN_RECHARGE_AMOUNT_FEN,
|
||||||
@@ -493,13 +507,13 @@
|
|||||||
{
|
{
|
||||||
label: '起止时间',
|
label: '起止时间',
|
||||||
prop: 'dateRange',
|
prop: 'dateRange',
|
||||||
type: 'date',
|
type: 'datetimerange',
|
||||||
config: {
|
config: {
|
||||||
type: 'daterange',
|
type: 'datetimerange',
|
||||||
rangeSeparator: '至',
|
rangeSeparator: '至',
|
||||||
startPlaceholder: '开始日期',
|
startPlaceholder: '开始日期',
|
||||||
endPlaceholder: '结束日期',
|
endPlaceholder: '结束日期',
|
||||||
valueFormat: 'YYYY-MM-DD'
|
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -534,7 +548,7 @@
|
|||||||
{ label: '支付时间', prop: 'paid_at' },
|
{ label: '支付时间', prop: 'paid_at' },
|
||||||
{ label: '完成时间', prop: 'completed_at' },
|
{ label: '完成时间', prop: 'completed_at' },
|
||||||
{ label: '更新时间', prop: 'updated_at' }
|
{ label: '更新时间', prop: 'updated_at' }
|
||||||
]
|
].filter(({ prop }) => shouldShowInternalColumn(prop))
|
||||||
|
|
||||||
const createFormRef = ref<FormInstance>()
|
const createFormRef = ref<FormInstance>()
|
||||||
const confirmPayFormRef = ref<FormInstance>()
|
const confirmPayFormRef = ref<FormInstance>()
|
||||||
@@ -694,7 +708,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 动态列配置
|
// 动态列配置
|
||||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
const { columnChecks, columns } = useCheckedColumns(() =>
|
||||||
|
[
|
||||||
{
|
{
|
||||||
prop: 'recharge_no',
|
prop: 'recharge_no',
|
||||||
label: '充值单号',
|
label: '充值单号',
|
||||||
@@ -818,7 +833,8 @@
|
|||||||
prop: 'completed_at',
|
prop: 'completed_at',
|
||||||
label: '完成时间',
|
label: '完成时间',
|
||||||
width: 180,
|
width: 180,
|
||||||
formatter: (row: AgentRecharge) => (row.completed_at ? formatDateTime(row.completed_at) : '-')
|
formatter: (row: AgentRecharge) =>
|
||||||
|
row.completed_at ? formatDateTime(row.completed_at) : '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'updated_at',
|
prop: 'updated_at',
|
||||||
@@ -826,7 +842,8 @@
|
|||||||
width: 180,
|
width: 180,
|
||||||
formatter: (row: AgentRecharge) => formatDateTime(row.updated_at)
|
formatter: (row: AgentRecharge) => formatDateTime(row.updated_at)
|
||||||
}
|
}
|
||||||
])
|
].filter(({ prop }) => shouldShowInternalColumn(prop))
|
||||||
|
)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getTableData()
|
getTableData()
|
||||||
@@ -1294,6 +1311,37 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 补发历史线下代理充值审批
|
||||||
|
const handleTriggerApproval = (row: AgentRecharge) => {
|
||||||
|
if (triggerApprovalLoading.value) return
|
||||||
|
|
||||||
|
ElMessageBox.confirm(`确定要为充值单号 ${row.recharge_no} 补发企微审批吗?`, '补发审批', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
triggerApprovalLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await AgentRechargeService.triggerApproval(row.id)
|
||||||
|
if (res.code !== 0) {
|
||||||
|
ElMessage.error(res.msg || '补发审批失败')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ElMessage.success('补发审批成功')
|
||||||
|
await getTableData()
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
ElMessage.error('补发审批失败')
|
||||||
|
} finally {
|
||||||
|
triggerApprovalLoading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// 用户取消
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 处理名称点击
|
// 处理名称点击
|
||||||
const handleNameClick = (row: AgentRecharge) => {
|
const handleNameClick = (row: AgentRecharge) => {
|
||||||
if (hasAuth('agent_recharge:detail_page')) {
|
if (hasAuth('agent_recharge:detail_page')) {
|
||||||
@@ -1317,7 +1365,8 @@
|
|||||||
hasAuth,
|
hasAuth,
|
||||||
onViewPaymentVoucher: handleViewPaymentVoucher,
|
onViewPaymentVoucher: handleViewPaymentVoucher,
|
||||||
onConfirmPayment: handleShowConfirmPay,
|
onConfirmPayment: handleShowConfirmPay,
|
||||||
onReject: handleShowReject
|
onReject: handleShowReject,
|
||||||
|
onTriggerApproval: handleTriggerApproval
|
||||||
})
|
})
|
||||||
if (isPlatformAccount.value && hasAuth(AUDIT_PERMISSIONS.agentRechargeEntry)) {
|
if (isPlatformAccount.value && hasAuth(AUDIT_PERMISSIONS.agentRechargeEntry)) {
|
||||||
const resourceTarget = resolveAuditResourceTarget({
|
const resourceTarget = resolveAuditResourceTarget({
|
||||||
@@ -1352,6 +1401,13 @@
|
|||||||
type: 'primary'
|
type: 'primary'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const paymentVoucherIndex = actions.findIndex((action) => action.label === '支付凭证')
|
||||||
|
if (paymentVoucherIndex > 0) {
|
||||||
|
const [paymentVoucherAction] = actions.splice(paymentVoucherIndex, 1)
|
||||||
|
actions.unshift(paymentVoucherAction)
|
||||||
|
}
|
||||||
|
|
||||||
return actions
|
return actions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,34 +10,6 @@
|
|||||||
返回
|
返回
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<h2 class="detail-title">{{ pageTitle }}</h2>
|
<h2 class="detail-title">{{ pageTitle }}</h2>
|
||||||
<ElButton
|
|
||||||
v-if="refund && isPlatformUser && hasAuth(AUDIT_PERMISSIONS.refundEntry)"
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
@click="openRefundAudit"
|
|
||||||
>
|
|
||||||
审计记录
|
|
||||||
</ElButton>
|
|
||||||
<ElButton
|
|
||||||
v-if="refund && isPlatformUser && hasAuth(AUDIT_PERMISSIONS.refundFinanceEntry)"
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
@click="openRefundFinance"
|
|
||||||
>
|
|
||||||
资金链路
|
|
||||||
</ElButton>
|
|
||||||
<ElButton
|
|
||||||
v-if="
|
|
||||||
refund?.approval_instance_id &&
|
|
||||||
isPlatformUser &&
|
|
||||||
hasAuth(AUDIT_PERMISSIONS.refundApprovalEntry)
|
|
||||||
"
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
@click="openApprovalAudit"
|
|
||||||
>
|
|
||||||
审批审计
|
|
||||||
</ElButton>
|
|
||||||
<ElButton
|
<ElButton
|
||||||
v-if="refund && canResubmit(refund) && hasAuth('refund:resubmit')"
|
v-if="refund && canResubmit(refund) && hasAuth('refund:resubmit')"
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -148,12 +120,6 @@
|
|||||||
import VoucherUpload from '@/components/business/VoucherUpload.vue'
|
import VoucherUpload from '@/components/business/VoucherUpload.vue'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
import { AUDIT_PERMISSIONS } from '@/config/constants/audit'
|
|
||||||
import {
|
|
||||||
resolveAuditResourceTarget,
|
|
||||||
resolveFinanceAuditTarget
|
|
||||||
} from '@/utils/business/auditNavigation'
|
|
||||||
import { openAuditInvestigation } from '@/components/business/audit/investigationController'
|
|
||||||
|
|
||||||
defineOptions({ name: 'RefundDetail' })
|
defineOptions({ name: 'RefundDetail' })
|
||||||
|
|
||||||
@@ -161,29 +127,11 @@
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
const isRestrictedCustomerRole = computed(() => [3, 4].includes(Number(userStore.info.user_type)))
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const refund = ref<Refund | null>(null)
|
const refund = ref<Refund | null>(null)
|
||||||
const refundVoucherFileKeys = ref<string[]>([])
|
const refundVoucherFileKeys = ref<string[]>([])
|
||||||
const isPlatformUser = computed(() => [1, 2].includes(Number(userStore.info.user_type)))
|
|
||||||
const openRefundAudit = () => {
|
|
||||||
const target = resolveAuditResourceTarget({
|
|
||||||
resourceType: 'refund',
|
|
||||||
internalId: refund.value?.id
|
|
||||||
})
|
|
||||||
if (target) openAuditInvestigation(target)
|
|
||||||
}
|
|
||||||
const openRefundFinance = () => {
|
|
||||||
const target = resolveFinanceAuditTarget('refund_id', refund.value?.id)
|
|
||||||
if (target) openAuditInvestigation(target)
|
|
||||||
}
|
|
||||||
const openApprovalAudit = () => {
|
|
||||||
const target = resolveAuditResourceTarget({
|
|
||||||
resourceType: 'approval_instance',
|
|
||||||
internalId: refund.value?.approval_instance_id
|
|
||||||
})
|
|
||||||
if (target) openAuditInvestigation(target)
|
|
||||||
}
|
|
||||||
|
|
||||||
const pageTitle = computed(() => `退款详情`)
|
const pageTitle = computed(() => `退款详情`)
|
||||||
|
|
||||||
@@ -291,7 +239,15 @@
|
|||||||
{ label: '资产标识符', prop: 'asset_identifier' },
|
{ label: '资产标识符', prop: 'asset_identifier' },
|
||||||
{ label: '资产类型', prop: 'asset_type' },
|
{ label: '资产类型', prop: 'asset_type' },
|
||||||
{ label: '退款状态', prop: 'status_name', formatter: (value) => value || '-' },
|
{ label: '退款状态', prop: 'status_name', formatter: (value) => value || '-' },
|
||||||
{ label: '提交人', prop: 'submitter_name', formatter: (value) => value || '-' },
|
...(!isRestrictedCustomerRole.value
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
label: '提交人',
|
||||||
|
prop: 'submitter_name',
|
||||||
|
formatter: (value: string | null | undefined) => value || '-'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []),
|
||||||
{
|
{
|
||||||
label: '申请退款金额',
|
label: '申请退款金额',
|
||||||
formatter: (_, data) => formatCurrency(data.requested_refund_amount)
|
formatter: (_, data) => formatCurrency(data.requested_refund_amount)
|
||||||
@@ -304,24 +260,28 @@
|
|||||||
label: '实收金额',
|
label: '实收金额',
|
||||||
formatter: (_, data) => formatCurrency(data.actual_received_amount)
|
formatter: (_, data) => formatCurrency(data.actual_received_amount)
|
||||||
},
|
},
|
||||||
|
...(!isRestrictedCustomerRole.value
|
||||||
|
? [
|
||||||
{
|
{
|
||||||
label: '退款原因',
|
label: '退款原因',
|
||||||
prop: 'refund_reason',
|
prop: 'refund_reason',
|
||||||
formatter: (value) => value || '-',
|
formatter: (value: string | null | undefined) => value || '-',
|
||||||
fullWidth: true
|
fullWidth: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '拒绝原因',
|
label: '拒绝原因',
|
||||||
prop: 'reject_reason',
|
prop: 'reject_reason',
|
||||||
formatter: (value) => value || '-',
|
formatter: (value: string | null | undefined) => value || '-',
|
||||||
fullWidth: true
|
fullWidth: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '备注',
|
label: '备注',
|
||||||
prop: 'remark',
|
prop: 'remark',
|
||||||
formatter: (value) => value || '-',
|
formatter: (value: string | null | undefined) => value || '-',
|
||||||
fullWidth: true
|
fullWidth: true
|
||||||
},
|
}
|
||||||
|
]
|
||||||
|
: []),
|
||||||
{
|
{
|
||||||
label: '退款凭证',
|
label: '退款凭证',
|
||||||
fullWidth: true,
|
fullWidth: true,
|
||||||
@@ -360,7 +320,14 @@
|
|||||||
{
|
{
|
||||||
title: '企微审批信息',
|
title: '企微审批信息',
|
||||||
fields: [
|
fields: [
|
||||||
{ label: '审批渠道', formatter: (_, data) => getApprovalProviderText(data) },
|
...(!isRestrictedCustomerRole.value
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
label: '审批渠道',
|
||||||
|
formatter: (_: unknown, data: Refund) => getApprovalProviderText(data)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []),
|
||||||
{
|
{
|
||||||
label: '审批来源',
|
label: '审批来源',
|
||||||
formatter: (_, data) => data.approval?.source || data.approval_source || '-'
|
formatter: (_, data) => data.approval?.source || data.approval_source || '-'
|
||||||
@@ -370,10 +337,14 @@
|
|||||||
label: '审批状态',
|
label: '审批状态',
|
||||||
formatter: (_, data) => getRefundApprovalStatusText(data)
|
formatter: (_, data) => getRefundApprovalStatusText(data)
|
||||||
},
|
},
|
||||||
|
...(!isRestrictedCustomerRole.value
|
||||||
|
? [
|
||||||
{
|
{
|
||||||
label: '当前审批人摘要',
|
label: '当前审批人摘要',
|
||||||
formatter: (_, data) => data.current_approver_summary || '-'
|
formatter: (_: unknown, data: Refund) => data.current_approver_summary || '-'
|
||||||
},
|
}
|
||||||
|
]
|
||||||
|
: []),
|
||||||
{ label: '模板版本', prop: 'approval.template_version' },
|
{ label: '模板版本', prop: 'approval.template_version' },
|
||||||
{
|
{
|
||||||
label: '申请人',
|
label: '申请人',
|
||||||
@@ -417,10 +388,15 @@
|
|||||||
{
|
{
|
||||||
title: '业务处理结果',
|
title: '业务处理结果',
|
||||||
fields: [
|
fields: [
|
||||||
|
...(!isRestrictedCustomerRole.value
|
||||||
|
? [
|
||||||
{
|
{
|
||||||
label: '处理状态',
|
label: '处理状态',
|
||||||
formatter: (_, data) => data.processing_status_name || data.processing_status || '-'
|
formatter: (_: unknown, data: Refund) =>
|
||||||
},
|
data.processing_status_name || data.processing_status || '-'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []),
|
||||||
{
|
{
|
||||||
label: '失败摘要',
|
label: '失败摘要',
|
||||||
formatter: (_, data) => data.processing_failure_summary || data.error_summary || '-',
|
formatter: (_, data) => data.processing_failure_summary || data.error_summary || '-',
|
||||||
|
|||||||
@@ -143,17 +143,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h } from 'vue'
|
import { computed, h } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { RefundService, ShopService, OrderService } from '@/api/modules'
|
import { RefundService, ShopService, OrderService } from '@/api/modules'
|
||||||
import { ElMessage, ElTag, ElButton } from 'element-plus'
|
import { ElMessage, ElMessageBox, ElTag, ElButton } from 'element-plus'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
import type {
|
import {
|
||||||
Refund,
|
|
||||||
RefundQueryParams,
|
|
||||||
RefundStatus,
|
RefundStatus,
|
||||||
ResubmitRefundRequest,
|
type Refund,
|
||||||
RefundAttachment
|
type RefundQueryParams,
|
||||||
|
type ResubmitRefundRequest,
|
||||||
|
type RefundAttachment
|
||||||
} from '@/types/api'
|
} from '@/types/api'
|
||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
@@ -183,10 +183,23 @@
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
|
const isRestrictedCustomerRole = computed(() => [3, 4].includes(Number(userStore.info.user_type)))
|
||||||
|
const hiddenInternalColumnProps = new Set([
|
||||||
|
'approval_provider',
|
||||||
|
'submitter_name',
|
||||||
|
'current_approver_summary',
|
||||||
|
'processing_status_name',
|
||||||
|
'refund_reason',
|
||||||
|
'reject_reason',
|
||||||
|
'remark'
|
||||||
|
])
|
||||||
|
const shouldShowInternalColumn = (prop?: string) =>
|
||||||
|
!isRestrictedCustomerRole.value || !hiddenInternalColumnProps.has(prop || '')
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const resubmitLoading = ref(false)
|
const resubmitLoading = ref(false)
|
||||||
const resubmitVoucherUploading = ref(false)
|
const resubmitVoucherUploading = ref(false)
|
||||||
|
const triggerApprovalLoading = ref(false)
|
||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
const resubmitUploadRef = ref<InstanceType<typeof VoucherUpload>>()
|
const resubmitUploadRef = ref<InstanceType<typeof VoucherUpload>>()
|
||||||
const createDialogVisible = ref(false)
|
const createDialogVisible = ref(false)
|
||||||
@@ -303,7 +316,7 @@
|
|||||||
{ label: '创建时间', prop: 'created_at' },
|
{ label: '创建时间', prop: 'created_at' },
|
||||||
{ label: '审批时间', prop: 'processed_at' },
|
{ label: '审批时间', prop: 'processed_at' },
|
||||||
{ label: '更新时间', prop: 'updated_at' }
|
{ label: '更新时间', prop: 'updated_at' }
|
||||||
]
|
].filter(({ prop }) => shouldShowInternalColumn(prop))
|
||||||
|
|
||||||
const resubmitFormRef = ref<FormInstance>()
|
const resubmitFormRef = ref<FormInstance>()
|
||||||
|
|
||||||
@@ -344,7 +357,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 动态列配置
|
// 动态列配置
|
||||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
const { columnChecks, columns } = useCheckedColumns(() =>
|
||||||
|
[
|
||||||
{
|
{
|
||||||
prop: 'refund_no',
|
prop: 'refund_no',
|
||||||
label: '退款单号',
|
label: '退款单号',
|
||||||
@@ -512,7 +526,8 @@
|
|||||||
width: 180,
|
width: 180,
|
||||||
formatter: (row: Refund) => formatDateTime(row.updated_at)
|
formatter: (row: Refund) => formatDateTime(row.updated_at)
|
||||||
}
|
}
|
||||||
])
|
].filter(({ prop }) => shouldShowInternalColumn(prop))
|
||||||
|
)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getTableData()
|
getTableData()
|
||||||
@@ -735,6 +750,42 @@
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const canTriggerApproval = (row: Refund) => {
|
||||||
|
const approvalStatusEmpty = row.approval_status === undefined || row.approval_status === null
|
||||||
|
return row.status === RefundStatus.PENDING && approvalStatusEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
// 补发历史退款审批
|
||||||
|
const handleTriggerApproval = (row: Refund) => {
|
||||||
|
if (triggerApprovalLoading.value) return
|
||||||
|
|
||||||
|
ElMessageBox.confirm(`确定要为退款单号 ${row.refund_no} 补发企微审批吗?`, '补发审批', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
triggerApprovalLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await RefundService.triggerApproval(row.id)
|
||||||
|
if (res.code !== 0) {
|
||||||
|
ElMessage.error(res.msg || '补发审批失败')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ElMessage.success('补发审批成功')
|
||||||
|
await getTableData()
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
ElMessage.error('补发审批失败')
|
||||||
|
} finally {
|
||||||
|
triggerApprovalLoading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// 用户取消
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 获取操作按钮
|
// 获取操作按钮
|
||||||
const getActions = (row: Refund) => {
|
const getActions = (row: Refund) => {
|
||||||
const actions: any[] = []
|
const actions: any[] = []
|
||||||
@@ -743,6 +794,15 @@
|
|||||||
[1, 2].includes(userStore.getUserInfo.user_type || 0) &&
|
[1, 2].includes(userStore.getUserInfo.user_type || 0) &&
|
||||||
hasAuth(AUDIT_PERMISSIONS.refundEntry)
|
hasAuth(AUDIT_PERMISSIONS.refundEntry)
|
||||||
) {
|
) {
|
||||||
|
const voucherKeys = getRefundAttachmentKeys(row)
|
||||||
|
if (voucherKeys.length && hasAuth('refund:view_voucher')) {
|
||||||
|
actions.push({
|
||||||
|
label: '退款凭证',
|
||||||
|
handler: () => handleViewRefundVoucher(row),
|
||||||
|
type: 'primary'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const auditTarget = resolveAuditResourceTarget({
|
const auditTarget = resolveAuditResourceTarget({
|
||||||
userType: userStore.getUserInfo.user_type,
|
userType: userStore.getUserInfo.user_type,
|
||||||
resourceType: 'refund',
|
resourceType: 'refund',
|
||||||
@@ -754,6 +814,7 @@
|
|||||||
handler: () => openAuditInvestigation(auditTarget),
|
handler: () => openAuditInvestigation(auditTarget),
|
||||||
type: 'primary'
|
type: 'primary'
|
||||||
})
|
})
|
||||||
|
|
||||||
if (hasAuth(AUDIT_PERMISSIONS.refundFinanceEntry)) {
|
if (hasAuth(AUDIT_PERMISSIONS.refundFinanceEntry)) {
|
||||||
const financeTarget = resolveFinanceAuditTarget('refund_id', row.id)
|
const financeTarget = resolveFinanceAuditTarget('refund_id', row.id)
|
||||||
if (financeTarget)
|
if (financeTarget)
|
||||||
@@ -775,7 +836,18 @@
|
|||||||
type: 'primary'
|
type: 'primary'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const voucherKeys = getRefundAttachmentKeys(row)
|
|
||||||
|
if (
|
||||||
|
[1, 2].includes(userStore.getUserInfo.user_type || 0) &&
|
||||||
|
canTriggerApproval(row) &&
|
||||||
|
hasAuth('refund:trigger_approval')
|
||||||
|
) {
|
||||||
|
actions.push({
|
||||||
|
label: '补发审批',
|
||||||
|
handler: () => handleTriggerApproval(row),
|
||||||
|
type: 'primary'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (canResubmit(row) && hasAuth('refund:resubmit')) {
|
if (canResubmit(row) && hasAuth('refund:resubmit')) {
|
||||||
actions.push({
|
actions.push({
|
||||||
@@ -785,14 +857,6 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (voucherKeys.length && hasAuth('refund:view_voucher')) {
|
|
||||||
actions.push({
|
|
||||||
label: '查看退款凭证',
|
|
||||||
handler: () => handleViewRefundVoucher(row),
|
|
||||||
type: 'primary'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return actions
|
return actions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,22 +10,6 @@
|
|||||||
返回
|
返回
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<h2 class="detail-title">订单详情</h2>
|
<h2 class="detail-title">订单详情</h2>
|
||||||
<ElButton
|
|
||||||
v-if="detailData && isPlatformUser && hasAuth(AUDIT_PERMISSIONS.orderEntry)"
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
@click="openOrderAudit"
|
|
||||||
>
|
|
||||||
审计记录
|
|
||||||
</ElButton>
|
|
||||||
<ElButton
|
|
||||||
v-if="detailData && isPlatformUser && hasAuth(AUDIT_PERMISSIONS.orderFinanceEntry)"
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
@click="openOrderFinance"
|
|
||||||
>
|
|
||||||
资金链路
|
|
||||||
</ElButton>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 详情内容 -->
|
<!-- 详情内容 -->
|
||||||
@@ -98,12 +82,6 @@
|
|||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import { hasVoucherKeys, toVoucherKeyList } from '@/utils/business'
|
import { hasVoucherKeys, toVoucherKeyList } from '@/utils/business'
|
||||||
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
|
import PaymentVoucherDialog from '@/components/business/PaymentVoucherDialog.vue'
|
||||||
import { AUDIT_PERMISSIONS } from '@/config/constants/audit'
|
|
||||||
import {
|
|
||||||
resolveAuditResourceTarget,
|
|
||||||
resolveFinanceAuditTarget
|
|
||||||
} from '@/utils/business/auditNavigation'
|
|
||||||
import { openAuditInvestigation } from '@/components/business/audit/investigationController'
|
|
||||||
|
|
||||||
defineOptions({ name: 'OrderDetail' })
|
defineOptions({ name: 'OrderDetail' })
|
||||||
|
|
||||||
@@ -115,18 +93,6 @@
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const detailData = ref<Order | null>(null)
|
const detailData = ref<Order | null>(null)
|
||||||
const paymentVoucherFileKeys = ref<string[]>([])
|
const paymentVoucherFileKeys = ref<string[]>([])
|
||||||
const isPlatformUser = computed(() => [1, 2].includes(Number(userStore.info.user_type)))
|
|
||||||
const openOrderAudit = () => {
|
|
||||||
const target = resolveAuditResourceTarget({
|
|
||||||
resourceType: 'order',
|
|
||||||
internalId: detailData.value?.id
|
|
||||||
})
|
|
||||||
if (target) openAuditInvestigation(target)
|
|
||||||
}
|
|
||||||
const openOrderFinance = () => {
|
|
||||||
const target = resolveFinanceAuditTarget('order_id', detailData.value?.id)
|
|
||||||
if (target) openAuditInvestigation(target)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化货币 - 将分转换为元
|
// 格式化货币 - 将分转换为元
|
||||||
const formatCurrency = (amount: number): string => {
|
const formatCurrency = (amount: number): string => {
|
||||||
|
|||||||
@@ -483,15 +483,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '开始至结束',
|
label: '起止时间',
|
||||||
prop: 'dateRange',
|
prop: 'dateRange',
|
||||||
type: 'date',
|
type: 'datetimerange',
|
||||||
config: {
|
config: {
|
||||||
type: 'daterange',
|
type: 'datetimerange',
|
||||||
rangeSeparator: '至',
|
rangeSeparator: '至',
|
||||||
startPlaceholder: '开始日期',
|
startPlaceholder: '开始日期',
|
||||||
endPlaceholder: '结束日期',
|
endPlaceholder: '结束日期',
|
||||||
valueFormat: 'YYYY-MM-DD'
|
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user