vue3-element-plus-app-chu 0.0.24 → 0.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/package.json +1 -1
- package/src/router/index.ts +7 -0
- package/src/router/menus.ts +1 -0
- package/src/view/dcps/README.md +12 -8
- package/src/view/dcps/auth/index.vue +8 -1
- package/src/view/dcps/auth/typs.ts +1 -1
- package/src/view/dcps/estimation/index.vue +2 -2
- package/src/view/dcps/flow-query/api.ts +12 -0
- package/src/view/dcps/flow-query/index.vue +249 -0
- package/src/view/dcps/flow-query/typs.ts +38 -0
- package/src/view/dcps/forecast/api.ts +2 -1
- package/src/view/dcps/forecast/index.vue +79 -37
- package/src/view/dcps/forecast/typs.ts +2 -4
- package/src/view/dcps/funding/injection/api.ts +3 -1
- package/src/view/dcps/funding/injection/index.vue +35 -7
- package/src/view/dcps/funding/injection/typs.ts +3 -2
- package/src/view/dcps/write-off/api.ts +4 -3
- package/src/view/dcps/write-off/index.vue +50 -20
- package/src/view/dcps/write-off/typs.ts +10 -6
package/README.md
CHANGED
|
@@ -23,15 +23,19 @@ pnpm dev:backend # 只启动后端(backend/ 目录,tsx watch)
|
|
|
23
23
|
默认入口为数字人民币头寸预报(`/forecast`),侧边菜单可切换至其他数字人民币页面。
|
|
24
24
|
|
|
25
25
|
- 列表页只保留筛选卡片和表格卡片;筛选条件压在一行,窗口过窄时才换行。
|
|
26
|
-
-
|
|
26
|
+
- 新增、修改、详情与列表共用 `/forecast` 入口,以当前页面状态切换完整视图,页面顶部提供“返回列表”按钮;返回后列表的筛选和分页状态不变。
|
|
27
27
|
- 支持交易日期、客户、交易对手、机构、金额范围及业务代码筛选,分页、详情、新增、修改和批量删除。
|
|
28
|
-
-
|
|
28
|
+
- 金额、账号及流水号使用字符串处理。业务类型使用文档明确的中文选项,其余未提供字典的代码保留接口原值。
|
|
29
29
|
- 导入、导出按钮保留在列表工具栏,文件由后端接口生成和解析,页面不再在前端拼装 CSV。
|
|
30
|
-
- 模块目录与授权页面保持一致:`api.ts`
|
|
30
|
+
- 模块目录与授权页面保持一致:`api.ts` 放接口定义,`typs.ts` 放类型,`index.vue` 包含列表、表单、详情和页面内工具函数。默认交易日期通过共用的 `src/view/dcps/runtime.ts` 获取,接口定义只维护在各自模块下,不再集中到 `src/api`。
|
|
31
31
|
- 新增、修改、详情页面每行四列展示,窗口变窄后依次降为三列、两列、一列。
|
|
32
32
|
|
|
33
33
|
本地后端已接入 MongoDB 业务模拟,预报增删改查、核销、注资余额、匡算历史和参数均可持久化。JSON 响应统一为 `{ code, data, message }`,TAM 的 `head/body` 位于 `data` 中,前端公共请求层自动解包。服务端导入协议尚未实现时明确返回 `501`。完整运行说明见 [后端 README](backend/README.md)。
|
|
34
34
|
|
|
35
|
+
数字人民币相关接口已按 2026-10-26 最新 Swagger 同步:新增预报的业务类型必填并使用 `201/202/203` 字典,移除 `predName`;核销预报方向使用 `1/2`,待核销查询包含状态 `1/3`,补充交易对手账号;流水增加业务类型筛选并改用 `tranStatus`;注资明细支持起止交易日期;授权页展示业务类型中文名称。匡算明细同步展示部分核销状态。部分核销的继续处理金额语义尚未定义,当前支持查询展示,操作仍沿用原有未核销/已核销流程。字段及本地实现说明见 [数字人民币接口摘录](docs/digital-rmb-apis.md)。
|
|
36
|
+
|
|
37
|
+
2026-10-27 新增 `/TAM/0001/140504002` 实时流水查询。独立页面 `/flow-query` 改为调用该接口,按 `txnLists` 展示平台流水、交易金额、时间、收付款人、钱包及机构信息,支持分页和刷新。该接口的业务请求体为空,页面不再提交旧核销流水的日期、姓名、金额等筛选;核销页面继续使用 `/140502002`。新接口未声明的业务类型、方向和状态字典保留原值。
|
|
38
|
+
|
|
35
39
|
## 城银清头寸参数接口
|
|
36
40
|
|
|
37
41
|
参数管理接口已支持持久化增删改查、关键词筛选和分页。数币页面位于 `src/view/dcps/parameters/`,使用 `moduleCode: '5'`;页面详情直接取表格行数据,不再请求详情接口。同一后端也支持城银清模块 `'3'`。参数名称全局唯一,修改和删除按名称定位;接口字段和示例见 [城银清头寸参数接口说明](docs/city-clearing-parameters-apis.md),Scalar 文档入口为后端 `/docs`。
|
package/package.json
CHANGED
package/src/router/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import DigitalRmbForecast from '@/view/dcps/forecast/index.vue'
|
|
|
6
6
|
import DigitalRmbInjection from '@/view/dcps/funding/injection/index.vue'
|
|
7
7
|
import DigitalRmbWriteOff from '@/view/dcps/write-off/index.vue'
|
|
8
8
|
import DigitalRmbPositionEstimation from '@/view/dcps/estimation/index.vue'
|
|
9
|
+
import DigitalRmbFlowQuery from '@/view/dcps/flow-query/index.vue'
|
|
9
10
|
import DigitalRmbParameters from '@/view/dcps/parameters/index.vue'
|
|
10
11
|
|
|
11
12
|
declare module 'vue-router' {
|
|
@@ -57,6 +58,12 @@ const routes: RouteRecordRaw[] = [
|
|
|
57
58
|
},
|
|
58
59
|
],
|
|
59
60
|
},
|
|
61
|
+
{
|
|
62
|
+
path: 'flow-query',
|
|
63
|
+
name: 'flow-query',
|
|
64
|
+
component: DigitalRmbFlowQuery,
|
|
65
|
+
meta: { menu: 'flow-query', title: '数字人民币流水查询' },
|
|
66
|
+
},
|
|
60
67
|
{
|
|
61
68
|
path: 'auth',
|
|
62
69
|
name: 'auth',
|
package/src/router/menus.ts
CHANGED
|
@@ -23,6 +23,7 @@ export const layoutMenus: LayoutMenu[] = [
|
|
|
23
23
|
{ name: 'balance', title: '数字人民币账户行余额查询申请', compactTitle: '余额查询' },
|
|
24
24
|
],
|
|
25
25
|
},
|
|
26
|
+
{ name: 'flow-query', title: '数字人民币流水查询', compactTitle: '流水查询' },
|
|
26
27
|
{ name: 'auth', title: '数字人民币授权', compactTitle: '授权' },
|
|
27
28
|
{ name: 'write-off', title: '数字人民币核销', compactTitle: '核销' },
|
|
28
29
|
{ name: 'parameters', title: '数字人民币参数管理', compactTitle: '参数管理' },
|
package/src/view/dcps/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`src/view/dcps` 下每个业务页面统一保持三个文件,不再拆分子组件、工具文件:
|
|
4
4
|
|
|
5
|
-
跨页面共用的运行时配置统一放在 `src/view/dcps/runtime.ts
|
|
5
|
+
跨页面共用的运行时配置统一放在 `src/view/dcps/runtime.ts`,通过具名对象 `dcpsRuntime` 导出,包含 `date`(共用业务日期,格式为 `YYYYMMDD`)、`approverId`(工号)和 `approverName`(姓名)。日期在模块加载时按上海时区生成一次,预报、匡算、核销及授权查询的默认日期均使用同一个 `date`;授权日期范围的开始和结束默认都取该值。页面通过 `import { dcpsRuntime } from '../runtime'`(分组子页面为 `../../runtime`)引入,不再单独导出日期工具或模块配置。
|
|
6
6
|
|
|
7
7
|
一级菜单为分组时,页面放在该分组目录下。例如“注资/预注资管理”的两个页面分别位于 `funding/injection/` 和 `funding/balance/`,各自保留下面三个文件。
|
|
8
8
|
|
|
@@ -14,6 +14,12 @@
|
|
|
14
14
|
|
|
15
15
|
公共样式变量仍放在 `common.css`,由 `src/main.ts` 统一引入。
|
|
16
16
|
|
|
17
|
+
## 当前接口契约
|
|
18
|
+
|
|
19
|
+
数字人民币接口及递归引用模型以 `docs/digital-rmb.openapi.json` 的 2026-10-27 附件为准。预报新增移除 `predName`,`busiType` 为必填,字典为 `201` 兑回、`202` 兑出、`203` 付现。核销预报查询使用方向 `1/2`,待核销传 `verifyStatusList: ['1', '3']`,已核销传 `['2']`;预报管理和流水方向保持各自原协议。核销页流水查询增加 `busiType`,返回 `tranStatus`(`S/P/F`),核销状态支持 `3` 部分核销。注资明细支持 `txnDtStart`、`txnDtEnd` 单边或双边日期筛选。完整字段及本地业务限制见 `docs/digital-rmb-apis.md`。
|
|
20
|
+
|
|
21
|
+
独立流水查询页面 `/flow-query` 使用新增的 `140504002`:业务请求体为空,公共请求头分页;读取 `txnLists` 和数字类型的 `totalCount`、`totalPage`,显示新模型的 16 个字符串字段。页面的 `api.ts`、`typs.ts` 独立维护该契约,不再复用核销页的 `140502002` 模型。业务类型、收支方向及交易状态未提供字典,按原值显示。
|
|
22
|
+
|
|
17
23
|
## 命名约定
|
|
18
24
|
|
|
19
25
|
- 所有页面组件的 `defineOptions({ name })` 统一以 `Dcps` 开头(PascalCase)。
|
|
@@ -29,13 +35,11 @@
|
|
|
29
35
|
| 路由路径 | 路由 name | 页面文件 | 页面形态 | 页面标题(meta.title) |
|
|
30
36
|
| --- | --- | --- | --- | --- |
|
|
31
37
|
| `/forecast` | `forecast` | `forecast/index.vue` | 列表 | 数字人民币头寸预报 |
|
|
32
|
-
| `/forecast/create` | `forecast-create` | `forecast/index.vue` | 新增(`mode: 'create'`) | 新增数字人民币头寸预报 |
|
|
33
|
-
| `/forecast/:predRefNo/edit` | `forecast-edit` | `forecast/index.vue` | 修改(`mode: 'edit'`) | 修改数字人民币头寸预报 |
|
|
34
|
-
| `/forecast/:predRefNo` | `forecast-detail` | `forecast/index.vue` | 详情(`mode: 'detail'`) | 数字人民币头寸预报详情 |
|
|
35
38
|
| `/estimation` | `estimation` | `estimation/index.vue` | 页面 | 数字人民币头寸匡算 |
|
|
36
39
|
| `/funding` | `funding` | — | 重定向到 `injection` | 注资/预注资管理 |
|
|
37
40
|
| `/funding/injection` | `injection` | `funding/injection/index.vue` | 页面 | 数字人民币注资明细查询 |
|
|
38
41
|
| `/funding/balance` | `balance` | `funding/balance/index.vue` | 页面 | 数字人民币账户行余额查询申请 |
|
|
42
|
+
| `/flow-query` | `flow-query` | `flow-query/index.vue` | 页面 | 数字人民币流水查询 |
|
|
39
43
|
| `/auth` | `auth` | `auth/index.vue` | 页面 | 数字人民币授权 |
|
|
40
44
|
| `/write-off` | `write-off` | `write-off/index.vue` | 页面 | 数字人民币核销 |
|
|
41
45
|
| `/parameters` | `parameters` | `parameters/index.vue` | 列表、详情、新增和修改弹窗 | 数字人民币参数管理 |
|
|
@@ -44,13 +48,13 @@
|
|
|
44
48
|
|
|
45
49
|
补充说明:
|
|
46
50
|
|
|
47
|
-
- 头寸预报的列表、新增、修改、详情共用 `forecast/index.vue
|
|
48
|
-
- `forecast/index.vue` 顶部使用一个普通 `<script>`
|
|
51
|
+
- 头寸预报的列表、新增、修改、详情共用 `/forecast` 与 `forecast/index.vue`,通过当前组件的视图状态切换;修改和详情使用所选记录的 `predRefNo` 请求数据,返回列表保留筛选和分页。
|
|
52
|
+
- `forecast/index.vue` 顶部使用一个普通 `<script>` 块保存列表记录缓存,打开修改或详情前暂存列表行,用于补齐详情接口未返回的字段。
|
|
49
53
|
- 侧边菜单配置在 `src/router/menus.ts`,其中 `name` 必须与对应路由的 `name` 保持一致;分组菜单通过 `children` 配置二级页面,页面的 `meta.menu` 指向自己的菜单项。
|
|
50
54
|
- “注资/预注资管理”对应 `funding` 分组,包含注资明细查询和账户行余额查询申请两个页面;访问子页面时自动展开父菜单。旧地址 `/injection`、`/balance` 保留为对应页面的别名。
|
|
51
|
-
- 数字人民币参数管理为末尾的独立菜单,页面文件为 `parameters/index.vue`、`parameters/api.ts`、`parameters/typs.ts`,接口与类型均独立定义在该目录,不再依赖或保留 `city-clearing/` 目录。页面复用“城银清算头寸管理-头寸参数”的列表、新增、修改、删除四个接口,详情直接取表格行数据,不再请求详情接口;`api.ts` 参照授权页面直接向 `@chic/http` 平铺传参,由 Axios 公共封装拆分为服务端所需的 `body`、`head` 等字段,不手动包装信封。页面查询调用只传 `keywords`、数字类型的 `pageIndex` 和 `pageSize`,`moduleCode: '5'` 由 API 内部固定添加,分页数字由封装层转换为字符串;新增传六个字符串表单字段,模块代码也由 API 固定添加。本次用户确认以 `paraName` 定位修改和删除:修改时锁定原参数名称、参数标志只读;删除向 API 传名称数组,由 API 转为平铺的 `sysParaList`。页面类型使用 `DigitalRmbParameter` 等独立类型,原始 Swagger 契约及 `docs/city-clearing-parameters*`
|
|
55
|
+
- 数字人民币参数管理为末尾的独立菜单,页面文件为 `parameters/index.vue`、`parameters/api.ts`、`parameters/typs.ts`,接口与类型均独立定义在该目录,不再依赖或保留 `city-clearing/` 目录。页面复用“城银清算头寸管理-头寸参数”的列表、新增、修改、删除四个接口,详情直接取表格行数据,不再请求详情接口;`api.ts` 参照授权页面直接向 `@chic/http` 平铺传参,由 Axios 公共封装拆分为服务端所需的 `body`、`head` 等字段,不手动包装信封。页面查询调用只传 `keywords`、数字类型的 `pageIndex` 和 `pageSize`,`moduleCode: '5'` 由 API 内部固定添加,分页数字由封装层转换为字符串;新增传六个字符串表单字段,模块代码也由 API 固定添加。本次用户确认以 `paraName` 定位修改和删除:修改时锁定原参数名称、参数标志只读;删除向 API 传名称数组,由 API 转为平铺的 `sysParaList`。页面类型使用 `DigitalRmbParameter` 等独立类型,原始 Swagger 契约及 `docs/city-clearing-parameters*` 文档文件名保留。当前本地后端将业务模拟参数持久化至 MongoDB。
|
|
52
56
|
- 数字人民币参数管理以数字人民币授权页面为布局参考,统一筛选区、填满剩余高度的表格以及包含摘要和分组字段的详情弹窗样式。
|
|
53
|
-
- `src/layout/index.vue` 使用 `<KeepAlive include="DcpsForecastPage">`
|
|
57
|
+
- `src/layout/index.vue` 使用 `<KeepAlive include="DcpsForecastPage">` 缓存头寸预报页面;新增、修改、详情在同一实例内切换,返回列表时自动重新查询。
|
|
54
58
|
|
|
55
59
|
## 新增页面检查清单
|
|
56
60
|
|
|
@@ -34,6 +34,12 @@ const filterFields = [
|
|
|
34
34
|
] as const
|
|
35
35
|
// 任务状态字典:01 待审批、02 已通过、03 已拒绝。
|
|
36
36
|
const statusLabels: Record<string, string> = { '01': '待审批', '02': '已通过', '03': '已拒绝' }
|
|
37
|
+
// 业务类型按更新后的接口字典展示,未登记的代码保留原值。
|
|
38
|
+
const businessTypeLabels: Record<string, string> = { '201': '兑回', '202': '兑出', '203': '付现' }
|
|
39
|
+
const formatBusinessType = (value?: string) => {
|
|
40
|
+
const code = value?.trim() || ''
|
|
41
|
+
return code ? businessTypeLabels[code] || code : '—'
|
|
42
|
+
}
|
|
37
43
|
// 审批按钮配置:value 为操作标识,label 为按钮文字,type 为按钮样式。
|
|
38
44
|
const decisionActions = [
|
|
39
45
|
{ value: 'reject', label: '拒绝', type: 'danger' },
|
|
@@ -221,7 +227,7 @@ const tableColumns: ColumnOption[] = [
|
|
|
221
227
|
{ key: 'sequence', type: 'globalIndex', label: '序号', width: 80, align: 'center' },
|
|
222
228
|
textColumn('platfSeqNo', '平台流水号', 190),
|
|
223
229
|
textColumn('id', '任务ID', 180),
|
|
224
|
-
textColumn('bizType', '业务类型', 140),
|
|
230
|
+
textColumn('bizType', '业务类型', 140, formatBusinessType),
|
|
225
231
|
textColumn('collePayerName', '付款人名称', 160),
|
|
226
232
|
textColumn('payeeName', '收款人名称', 160),
|
|
227
233
|
{
|
|
@@ -254,6 +260,7 @@ const detailValues = computed(() => {
|
|
|
254
260
|
|
|
255
261
|
return {
|
|
256
262
|
...task,
|
|
263
|
+
bizType: formatBusinessType(task.bizType),
|
|
257
264
|
txnAmt: formatAmount(task.txnAmt),
|
|
258
265
|
taskSts: formatStatus(task.taskSts),
|
|
259
266
|
txnTm: formatDateTime(task.txnTm),
|
|
@@ -20,7 +20,7 @@ export interface AuthorizationQueryParams extends AuthorizationFilters {
|
|
|
20
20
|
export interface AuthorizationTask {
|
|
21
21
|
id?: string // 任务 ID。
|
|
22
22
|
platfSeqNo?: string // 平台流水号,用于定位审批任务。
|
|
23
|
-
bizType?: string //
|
|
23
|
+
bizType?: string // 业务类型:201 兑回、202 兑出、203 付现;未知代码保留原值。
|
|
24
24
|
authoOrgName?: string // 授权机构名称。
|
|
25
25
|
authoOrgNo?: string // 授权机构编号。
|
|
26
26
|
collePayerName?: string // 付款人名称。
|
|
@@ -116,8 +116,8 @@ const calcGapAmount = (inAmt: string, outAmt: string, emptyAsZero = false): stri
|
|
|
116
116
|
const payDirecLabels: Record<string, string> = { I: '来账', O: '往账' }
|
|
117
117
|
/** 支付渠道:预报与流水的数字人民币分别为 8、5;其余渠道保留原文。 */
|
|
118
118
|
const payChannelLabels: Record<string, string> = { '5': '数字人民币', '8': '数字人民币' }
|
|
119
|
-
/**
|
|
120
|
-
const verifyStatusLabels: Record<string, string> = { '1': '未核销', '2': '已核销' }
|
|
119
|
+
/** 与核销页面统一展示核销状态;部分核销记录仍可能出现在匡算明细中。 */
|
|
120
|
+
const verifyStatusLabels: Record<string, string> = { '1': '未核销', '2': '已核销', '3': '部分核销' }
|
|
121
121
|
/** 业务类型字典:201 兑回、202 兑出、203 付现;与核销预报使用同一套枚举。 */
|
|
122
122
|
const busiTypeLabels: Record<string, string> = { '201': '兑回', '202': '兑出', '203': '付现' }
|
|
123
123
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import baseHttpClient from '@chic/http'
|
|
2
|
+
import type { FlowQueryParams } from './typs'
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
/**
|
|
6
|
+
* 分页查询数字人民币实时流水(140504002)。
|
|
7
|
+
* 接口业务 body 为空,不声明日期、姓名、金额或核销状态筛选。
|
|
8
|
+
* @param params 公共分页参数,由请求层放入 head 并转换为字符串。
|
|
9
|
+
*/
|
|
10
|
+
queryFlowApi: (params: FlowQueryParams) =>
|
|
11
|
+
baseHttpClient.post<any>('/TAM/0001/140504002', params),
|
|
12
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
|
3
|
+
import { ElMessageBox } from 'element-plus'
|
|
4
|
+
import API from './api'
|
|
5
|
+
import { ArtDataTable, type ColumnOption, type PaginationOptions, type TableSettings } from '@/components/table'
|
|
6
|
+
import type { FlowRow, Tam140504002OutBody } from './typs'
|
|
7
|
+
|
|
8
|
+
defineOptions({ name: 'DcpsFlowQueryPage' })
|
|
9
|
+
|
|
10
|
+
/** 列表查询结果:当前页记录、总记录数和可访问的最后一页。 */
|
|
11
|
+
interface FlowPage {
|
|
12
|
+
data: FlowRow[]
|
|
13
|
+
total: number
|
|
14
|
+
lastPage: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** 校验 TAM 公共响应头,返回实时流水业务数据。 */
|
|
18
|
+
const ensureSuccess = (response: any): Tam140504002OutBody | null => {
|
|
19
|
+
if (response?.head?.retCode !== '00000') {
|
|
20
|
+
throw new Error(response?.head?.retMsg?.trim() || '实时流水查询失败')
|
|
21
|
+
}
|
|
22
|
+
return response.body
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 计数字段为 integer,明确拒绝字符串、小数、负数和超过安全精度的数字。 */
|
|
26
|
+
const readCount = (value: unknown, label: string): number | undefined => {
|
|
27
|
+
if (value === undefined) return undefined
|
|
28
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {
|
|
29
|
+
throw new Error(`查询返回的${label}无效`)
|
|
30
|
+
}
|
|
31
|
+
return value
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 按 txnLists 读取列表,分页数据不完整或相互矛盾时展示明确错误。 */
|
|
35
|
+
const readPage = (body: Tam140504002OutBody | null, pageIndex: number, pageSize: number): FlowPage => {
|
|
36
|
+
if (!body || typeof body !== 'object') throw new Error('查询结果缺少实时流水数据')
|
|
37
|
+
if (body.txnLists !== undefined && !Array.isArray(body.txnLists)) {
|
|
38
|
+
throw new Error('查询返回的实时流水列表格式异常')
|
|
39
|
+
}
|
|
40
|
+
const data = body.txnLists ?? []
|
|
41
|
+
const declaredTotal = readCount(body.totalCount, '总记录数')
|
|
42
|
+
const declaredPages = readCount(body.totalPage, '总页数')
|
|
43
|
+
// 单页且未提供计数时可按实际条数展示,多页结果需要真实总数以保证分页准确。
|
|
44
|
+
if (declaredTotal === undefined && (pageIndex > 1 || (declaredPages !== undefined && declaredPages > 1))) {
|
|
45
|
+
throw new Error('查询结果缺少总记录数,无法读取分页数据')
|
|
46
|
+
}
|
|
47
|
+
const total = declaredTotal ?? data.length
|
|
48
|
+
const pages = Math.ceil(total / pageSize)
|
|
49
|
+
if (total < data.length || (body.txnLists === undefined && total > 0)) {
|
|
50
|
+
throw new Error('查询返回的实时流水列表与总记录数不一致')
|
|
51
|
+
}
|
|
52
|
+
// 无记录时允许服务端返回 0 或 1 页,页面均停留在第 1 页。
|
|
53
|
+
if (declaredPages !== undefined && declaredPages !== pages && !(total === 0 && declaredPages === 1)) {
|
|
54
|
+
throw new Error('查询返回的总页数与总记录数不一致')
|
|
55
|
+
}
|
|
56
|
+
return { data, total, lastPage: Math.max(1, pages) }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const getErrorMessage = (error: unknown): string => {
|
|
60
|
+
if (error instanceof Error && error.message.trim()) return error.message
|
|
61
|
+
if (typeof error === 'string' && error.trim()) return error
|
|
62
|
+
return '实时流水查询失败,请稍后重试'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** 仅用字符串处理金额,不做浮点转换或舍入。 */
|
|
66
|
+
const formatAmount = (value?: string): string => {
|
|
67
|
+
const original = value?.trim() || ''
|
|
68
|
+
if (!original) return '—'
|
|
69
|
+
let text = original
|
|
70
|
+
if (text.includes(',')) {
|
|
71
|
+
if (!/^[+-]?\d{1,3}(?:,\d{3})+(?:\.\d*)?$/.test(text)) return original
|
|
72
|
+
text = text.replace(/,/g, '')
|
|
73
|
+
}
|
|
74
|
+
const match = /^([+-]?)(\d*)(?:\.(\d*))?$/.exec(text)
|
|
75
|
+
if (!match || (!match[2] && !match[3])) return original
|
|
76
|
+
const integer = (match[2] || '0').replace(/^0+(?=\d)/, '')
|
|
77
|
+
const fraction = match[3] || ''
|
|
78
|
+
const negative = match[1] === '-' && /[1-9]/.test(integer + fraction)
|
|
79
|
+
return `${negative ? '-' : ''}${integer.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}.${fraction.padEnd(2, '0')}`
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const flowRows = ref<FlowRow[]>([])
|
|
83
|
+
const flowPagination = reactive({ current: 1, size: 15, total: 0 })
|
|
84
|
+
const flowPaginationOptions: PaginationOptions = { align: 'right', pageSizes: [15, 30, 50, 100], background: false }
|
|
85
|
+
const flowTableSettings = ref<TableSettings>({ size: 'small', stripe: true, border: true, headerBackground: true })
|
|
86
|
+
const flowLoading = ref(false)
|
|
87
|
+
const flowQueryError = ref('')
|
|
88
|
+
let disposed = false
|
|
89
|
+
let flowRequestId = 0 // 仅最新请求可以写入列表;离开页面时使未完成请求失效。
|
|
90
|
+
|
|
91
|
+
/** 所有代码字段保留接口原值,不套用核销接口的方向、业务类型或状态字典。 */
|
|
92
|
+
const flowTextColumn = (prop: keyof FlowRow, label: string, width: number, visible = true): ColumnOption<FlowRow> => ({
|
|
93
|
+
prop, label, width, visible, showOverflowTooltip: true,
|
|
94
|
+
formatter: (row) => row[prop]?.trim() || '—',
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
// 覆盖实时流水的 16 个字段;钱包和机构编号默认在列设置中按需开启。
|
|
98
|
+
const flowColumns: ColumnOption<FlowRow>[] = [
|
|
99
|
+
{ type: 'globalIndex', label: '序号', width: 60, align: 'center' },
|
|
100
|
+
flowTextColumn('consumerSeqNo', '平台流水号', 200),
|
|
101
|
+
flowTextColumn('txnTmStamp', '交易时间', 190),
|
|
102
|
+
flowTextColumn('txnDirection', '收支方向', 100),
|
|
103
|
+
flowTextColumn('dbtrNm', '付款人名称', 170),
|
|
104
|
+
flowTextColumn('cdtrNm', '收款人名称', 170),
|
|
105
|
+
{ prop: 'txnAmt', label: '交易金额', width: 170, align: 'right', useSlot: true, slotName: 'txnAmount' },
|
|
106
|
+
flowTextColumn('bizType', '业务类型', 100),
|
|
107
|
+
flowTextColumn('txnSts', '交易状态', 100),
|
|
108
|
+
flowTextColumn('payerFinOrgName', '付款机构名称', 170),
|
|
109
|
+
flowTextColumn('payeeFinOrgName', '收款机构名称', 170),
|
|
110
|
+
flowTextColumn('subBrchName', '落地支行名称', 170),
|
|
111
|
+
flowTextColumn('dbtrWltId', '付款钱包 ID', 190, false),
|
|
112
|
+
flowTextColumn('cdtrWltId', '收款钱包 ID', 190, false),
|
|
113
|
+
flowTextColumn('payerFinOrgNo', '付款机构编号', 170, false),
|
|
114
|
+
flowTextColumn('payeeFinOrgNo', '收款机构编号', 170, false),
|
|
115
|
+
flowTextColumn('subBrchBankNo', '落地支行行号', 170, false),
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
/** 公共 head 承载分页,140504002 的业务 body 不发送旧核销筛选条件。 */
|
|
119
|
+
const loadFlowList = async () => {
|
|
120
|
+
if (disposed) return
|
|
121
|
+
const currentId = ++flowRequestId
|
|
122
|
+
const pageSize = flowPagination.size
|
|
123
|
+
let pageIndex = flowPagination.current
|
|
124
|
+
flowLoading.value = true
|
|
125
|
+
flowQueryError.value = ''
|
|
126
|
+
try {
|
|
127
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
128
|
+
const response = await API.queryFlowApi({ pageIndex, pageSize })
|
|
129
|
+
if (disposed || currentId !== flowRequestId) return
|
|
130
|
+
const page = readPage(ensureSuccess(response), pageIndex, pageSize)
|
|
131
|
+
if (pageIndex > page.lastPage) {
|
|
132
|
+
if (attempt === 0) {
|
|
133
|
+
pageIndex = page.lastPage
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
136
|
+
throw new Error('实时流水列表已变化,请重新查询')
|
|
137
|
+
}
|
|
138
|
+
flowPagination.current = pageIndex
|
|
139
|
+
flowPagination.total = page.total
|
|
140
|
+
flowRows.value = page.data
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (disposed || currentId !== flowRequestId) return
|
|
145
|
+
flowRows.value = []
|
|
146
|
+
flowPagination.total = 0
|
|
147
|
+
flowQueryError.value = getErrorMessage(error)
|
|
148
|
+
} finally {
|
|
149
|
+
if (!disposed && currentId === flowRequestId) flowLoading.value = false
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** 查询从第一页开始;工具栏刷新和错误重试保留当前页。 */
|
|
154
|
+
const searchFlow = () => {
|
|
155
|
+
if (disposed || flowLoading.value) return
|
|
156
|
+
flowPagination.current = 1
|
|
157
|
+
void loadFlowList()
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const realtimeChecking = ref(false)
|
|
161
|
+
|
|
162
|
+
/** 「实时查询流水」仅调用 140504002 确认实时链路;返回成功后弹窗提示,不改动表格与分页。 */
|
|
163
|
+
const checkRealtimeFlow = async () => {
|
|
164
|
+
if (disposed || realtimeChecking.value) return
|
|
165
|
+
realtimeChecking.value = true
|
|
166
|
+
try {
|
|
167
|
+
const response = await API.queryFlowApi({ pageIndex: 1, pageSize: 1 })
|
|
168
|
+
if (disposed) return
|
|
169
|
+
if (response?.head?.retCode !== '00000') {
|
|
170
|
+
throw new Error(response?.head?.retMsg?.trim() || '实时查询流水失败')
|
|
171
|
+
}
|
|
172
|
+
ElMessageBox.alert('操作成功', '提示', { type: 'success', confirmButtonText: '确定' }).catch(() => {})
|
|
173
|
+
} catch (error) {
|
|
174
|
+
if (disposed) return
|
|
175
|
+
ElMessageBox.alert(getErrorMessage(error), '提示', { type: 'error', confirmButtonText: '确定' }).catch(() => {})
|
|
176
|
+
} finally {
|
|
177
|
+
if (!disposed) realtimeChecking.value = false
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const changeFlowPage = (current: number, size = flowPagination.size) => {
|
|
182
|
+
if (disposed || (current === flowPagination.current && size === flowPagination.size)) return
|
|
183
|
+
if (!Number.isSafeInteger(current) || current < 1 || !Number.isSafeInteger(size) || size < 1) return
|
|
184
|
+
flowPagination.current = current
|
|
185
|
+
flowPagination.size = size
|
|
186
|
+
void loadFlowList()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
onMounted(() => { void loadFlowList() })
|
|
190
|
+
onBeforeUnmount(() => {
|
|
191
|
+
disposed = true
|
|
192
|
+
flowRequestId += 1
|
|
193
|
+
})
|
|
194
|
+
</script>
|
|
195
|
+
|
|
196
|
+
<template>
|
|
197
|
+
<main class="dcps-page">
|
|
198
|
+
<el-alert v-if="flowQueryError" :title="flowQueryError" type="error" show-icon :closable="false">
|
|
199
|
+
<el-button link type="primary" :disabled="flowLoading" @click="void loadFlowList()">重新查询</el-button>
|
|
200
|
+
</el-alert>
|
|
201
|
+
|
|
202
|
+
<div class="table-region">
|
|
203
|
+
<ArtDataTable
|
|
204
|
+
v-model:settings="flowTableSettings"
|
|
205
|
+
class="flow-table"
|
|
206
|
+
fill
|
|
207
|
+
title="数字人民币实时流水"
|
|
208
|
+
toolbar-layout="refresh,size,fullscreen,columns,settings"
|
|
209
|
+
:data="flowRows"
|
|
210
|
+
:columns="flowColumns"
|
|
211
|
+
:loading="flowLoading"
|
|
212
|
+
:pagination="flowPagination"
|
|
213
|
+
:pagination-options="flowPaginationOptions"
|
|
214
|
+
row-key="consumerSeqNo"
|
|
215
|
+
:empty-text="flowQueryError ? '查询失败,请重新查询' : '暂无实时流水信息'"
|
|
216
|
+
@pagination:current-change="changeFlowPage"
|
|
217
|
+
@pagination:size-change="(size: number) => changeFlowPage(1, size)"
|
|
218
|
+
@refresh="void loadFlowList()"
|
|
219
|
+
>
|
|
220
|
+
<template #toolbar-left>
|
|
221
|
+
<el-button type="primary" :size="flowTableSettings.size" :loading="flowLoading" @click="searchFlow">查询</el-button>
|
|
222
|
+
<el-button type="primary" plain :size="flowTableSettings.size" :loading="realtimeChecking" @click="void checkRealtimeFlow()">实时查询流水</el-button>
|
|
223
|
+
</template>
|
|
224
|
+
<template #txnAmount="{ row }"><span class="amount-cell">{{ formatAmount(row.txnAmt) }}</span></template>
|
|
225
|
+
</ArtDataTable>
|
|
226
|
+
</div>
|
|
227
|
+
</main>
|
|
228
|
+
</template>
|
|
229
|
+
|
|
230
|
+
<style scoped>
|
|
231
|
+
.dcps-page {
|
|
232
|
+
box-sizing: border-box;
|
|
233
|
+
display: flex;
|
|
234
|
+
width: 100%;
|
|
235
|
+
max-width: 100%;
|
|
236
|
+
flex-direction: column;
|
|
237
|
+
gap: 5px;
|
|
238
|
+
height: calc(100vh - var(--dpcs-host-layout-height));
|
|
239
|
+
height: calc(100dvh - var(--dpcs-host-layout-height));
|
|
240
|
+
min-height: 0;
|
|
241
|
+
min-width: 0;
|
|
242
|
+
padding: 5px;
|
|
243
|
+
overflow: hidden;
|
|
244
|
+
background: var(--el-bg-color-page, #f7f8fa);
|
|
245
|
+
}
|
|
246
|
+
.table-region { flex: 1; min-width: 0; min-height: 0; }
|
|
247
|
+
.flow-table { padding: 10px; }
|
|
248
|
+
.amount-cell { color: var(--el-text-color-primary); font-variant-numeric: tabular-nums; }
|
|
249
|
+
</style>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** 数字人民币实时流水查询(140504002)的业务 body 为空。 */
|
|
2
|
+
export type Tam140504002InBody = Record<string, never>
|
|
3
|
+
|
|
4
|
+
/** 平铺的公共分页参数;请求层将其放入 head,业务 body 保持为空。 */
|
|
5
|
+
export interface FlowQueryParams {
|
|
6
|
+
pageIndex: number // 页码,从 1 开始。
|
|
7
|
+
pageSize: number // 每页条数。
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Tam140504002Out.Body:计数字段遵循接口 integer 类型。 */
|
|
11
|
+
export interface Tam140504002OutBody {
|
|
12
|
+
txnLists?: Tam140504002OutBodyTxnLists[] // 当前页的交易流水。
|
|
13
|
+
totalCount?: number // 总记录数。
|
|
14
|
+
totalPage?: number // 总页数。
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** 实时流水原始记录;金额、流水号、钱包 ID 和代码均保留字符串。 */
|
|
18
|
+
export interface Tam140504002OutBodyTxnLists {
|
|
19
|
+
bizType?: string // 业务类型,接口未提供枚举。
|
|
20
|
+
cdtrNm?: string // 收款人名称。
|
|
21
|
+
cdtrWltId?: string // 收款钱包 ID。
|
|
22
|
+
consumerSeqNo?: string // 平台流水号。
|
|
23
|
+
dbtrNm?: string // 付款人名称。
|
|
24
|
+
dbtrWltId?: string // 付款钱包 ID。
|
|
25
|
+
payeeFinOrgName?: string // 收款机构名称。
|
|
26
|
+
payeeFinOrgNo?: string // 收款机构编号。
|
|
27
|
+
payerFinOrgName?: string // 付款机构名称。
|
|
28
|
+
payerFinOrgNo?: string // 付款机构编号。
|
|
29
|
+
subBrchBankNo?: string // 落地支行行号。
|
|
30
|
+
subBrchName?: string // 落地支行名称。
|
|
31
|
+
txnAmt?: string // 交易金额。
|
|
32
|
+
txnDirection?: string // 收支方向,接口未提供枚举。
|
|
33
|
+
txnSts?: string // 交易状态,接口未提供枚举。
|
|
34
|
+
txnTmStamp?: string // 交易时间,保留接口原始时间文本。
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 独立实时流水页面的单行记录。 */
|
|
38
|
+
export type FlowRow = Tam140504002OutBodyTxnLists
|
|
@@ -12,7 +12,8 @@ export default {
|
|
|
12
12
|
queryListApi: (params: unknown) => baseHttpClient.post<any>("/TAM/0001/140501001", params),
|
|
13
13
|
/**
|
|
14
14
|
* 新增一条数字人民币头寸预报。
|
|
15
|
-
* @param params 新增参数:
|
|
15
|
+
* @param params 新增参数:busiType 必填(201 兑回、202 兑出、203 付现);
|
|
16
|
+
* tranDate 为交易日期,predDate 为预报日期,predAmt 为预报金额;
|
|
16
17
|
* 其余为客户、机构、渠道、用途、备注等业务字段,未填写的字段可不传。
|
|
17
18
|
*/
|
|
18
19
|
addApi: (params: unknown) => baseHttpClient.post<any>("/TAM/0001/120501001", params),
|
|
@@ -40,7 +40,7 @@ defineOptions({ name: 'DcpsForecastPage' })
|
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* 预报页面:列表、新增/修改、详情共用一个入口文件。
|
|
43
|
-
*
|
|
43
|
+
* 新增、修改和详情在当前入口切换视图,返回列表后保留筛选和分页。
|
|
44
44
|
*/
|
|
45
45
|
|
|
46
46
|
// ---------- 页面内工具函数 ----------
|
|
@@ -141,23 +141,24 @@ const createFormValues = (): ForecastFormValues => {
|
|
|
141
141
|
billDate: '', billNo: '', busiType: '', clientName: '', clientType: '',
|
|
142
142
|
competAcct: '', competName: '', custInfoSeq: '', groundBranch: '', groundBranchName: '',
|
|
143
143
|
linkMan: '', payChannel: '', payDirec: '', phoneNo: '', predAmt: '', predDate: dcpsRuntime.date,
|
|
144
|
-
|
|
144
|
+
predOrgName: '', purpose: '', remark: '', tranDate: dcpsRuntime.date,
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
/**
|
|
149
|
-
*
|
|
149
|
+
* 接口要求新增业务类型必填,同时保留现有预报业务所需的客户、机构、方向、日期与金额校验。
|
|
150
150
|
* @param values 表单值。
|
|
151
151
|
* @param mode create 为新增,edit 为修改。
|
|
152
152
|
* @returns 错误提示文本,空字符串表示校验通过。
|
|
153
153
|
*/
|
|
154
154
|
const validateForm = (values: ForecastFormValues, mode: 'create' | 'edit' = 'create'): string => {
|
|
155
|
-
const
|
|
156
|
-
['clientName', '客户名称'], ['payDirec', '收支方向'],
|
|
155
|
+
const businessRequiredFields = [
|
|
156
|
+
['clientName', '客户名称'], ['payDirec', '收支方向'],
|
|
157
157
|
['payChannel', '支付渠道'], ['groundBranch', '落地机构'],
|
|
158
158
|
] as const
|
|
159
159
|
if (mode === 'create') {
|
|
160
|
-
|
|
160
|
+
if (!values.busiType.trim()) return '请选择业务类型'
|
|
161
|
+
for (const [key, label] of businessRequiredFields) {
|
|
161
162
|
if (!values[key].trim()) return `请填写${label}`
|
|
162
163
|
}
|
|
163
164
|
}
|
|
@@ -213,13 +214,15 @@ const listTextFilters = [
|
|
|
213
214
|
['clientName', '客户名称'],
|
|
214
215
|
['competName', '交易对手名称'],
|
|
215
216
|
['payDirec', '收支方向'],
|
|
216
|
-
['busiType', '业务类型'],
|
|
217
217
|
['payChannel', '支付渠道'],
|
|
218
218
|
['groundBranch', '落地机构'],
|
|
219
219
|
['predStatus', '预报状态'],
|
|
220
220
|
['verifyStatus', '核销状态'],
|
|
221
221
|
['sortRule', '排序规则'],
|
|
222
222
|
] as const
|
|
223
|
+
/** 新接口明确给出的业务类型;未登记代码保留接口原值。 */
|
|
224
|
+
const busiTypeLabels: Record<string, string> = { '201': '兑回', '202': '兑出', '203': '付现' }
|
|
225
|
+
const busiTypeOptions = Object.entries(busiTypeLabels).map(([value, label]) => ({ value, label }))
|
|
223
226
|
const listRows = ref<ForecastRow[]>([])
|
|
224
227
|
const listSelectedRows = ref<ForecastRow[]>([])
|
|
225
228
|
const listPagination = reactive({ current: 1, size: 15, total: 0 })
|
|
@@ -233,12 +236,14 @@ const listBusy = computed(() => listMutation.value !== null)
|
|
|
233
236
|
const listTableRef = ref<InstanceType<typeof ArtDataTable> | null>(null)
|
|
234
237
|
let listRequestId = 0
|
|
235
238
|
|
|
236
|
-
// ----------
|
|
237
|
-
const editorDialogVisible = ref(false) //
|
|
238
|
-
const editorDialogMode = ref<'create' | 'edit'>('create') //
|
|
239
|
+
// ---------- 当前视图状态 ----------
|
|
240
|
+
const editorDialogVisible = ref(false) // 新增/修改视图是否显示。
|
|
241
|
+
const editorDialogMode = ref<'create' | 'edit'>('create') // 当前编辑模式。
|
|
239
242
|
const editorDialogPredRefNo = ref('') // 修改模式的预报流水号。
|
|
240
|
-
const detailDialogVisible = ref(false) //
|
|
243
|
+
const detailDialogVisible = ref(false) // 详情视图是否显示。
|
|
241
244
|
const detailDialogPredRefNo = ref('') // 详情模式的预报流水号。
|
|
245
|
+
const isEditor = computed(() => editorDialogVisible.value)
|
|
246
|
+
const isDetail = computed(() => detailDialogVisible.value)
|
|
242
247
|
|
|
243
248
|
const listHasReference = (row: ForecastRow) => Boolean(row.predRefNo?.trim())
|
|
244
249
|
const listTextColumn = (prop: keyof ForecastRow, label: string, width: number, visible = true): ColumnOption<ForecastRow> => ({
|
|
@@ -252,7 +257,7 @@ const listColumns: ColumnOption<ForecastRow>[] = [
|
|
|
252
257
|
{ ...listTextColumn('tranDate', '交易日期', 110), formatter: (row) => formatDate(row.tranDate) },
|
|
253
258
|
listTextColumn('clientName', '客户名称', 180),
|
|
254
259
|
listTextColumn('payDirec', '收支方向', 95),
|
|
255
|
-
listTextColumn('busiType', '业务类型', 110),
|
|
260
|
+
{ ...listTextColumn('busiType', '业务类型', 110), formatter: (row) => busiTypeLabels[row.busiType ?? ''] ?? row.busiType ?? '—' },
|
|
256
261
|
listTextColumn('payChannel', '支付渠道', 110),
|
|
257
262
|
{ prop: 'predAmt', label: '预报金额(元)', width: 180, align: 'right', useSlot: true, slotName: 'amount' },
|
|
258
263
|
{ prop: 'verifyAmt', label: '已核销金额(元)', width: 180, align: 'right', useSlot: true, slotName: 'verifiedAmount' },
|
|
@@ -339,7 +344,7 @@ const changeListPage = (current: number, size = listPagination.size) => {
|
|
|
339
344
|
void loadList()
|
|
340
345
|
}
|
|
341
346
|
|
|
342
|
-
//
|
|
347
|
+
// 行内操作切换当前视图,整行记录用于补齐详情接口没有返回的字段。
|
|
343
348
|
const openEdit = (row: ForecastRow) => {
|
|
344
349
|
if (listBusy.value || !listHasReference(row)) return
|
|
345
350
|
const predRefNo = row.predRefNo?.trim()
|
|
@@ -351,7 +356,7 @@ const openEdit = (row: ForecastRow) => {
|
|
|
351
356
|
}
|
|
352
357
|
const openDetail = (row: ForecastRow) => {
|
|
353
358
|
if (listBusy.value || !listHasReference(row)) return
|
|
354
|
-
const predRefNo = row.
|
|
359
|
+
const predRefNo = row.predRefNo?.trim()
|
|
355
360
|
if (!predRefNo) return
|
|
356
361
|
rememberForecastRow(row)
|
|
357
362
|
detailDialogPredRefNo.value = predRefNo
|
|
@@ -397,7 +402,7 @@ type FieldKey = Exclude<keyof ForecastFormValues, 'custRefSeq'>
|
|
|
397
402
|
type EditorField = {
|
|
398
403
|
key: FieldKey
|
|
399
404
|
label: string
|
|
400
|
-
kind?: 'date' | 'amount' | 'textarea'
|
|
405
|
+
kind?: 'date' | 'amount' | 'textarea' | 'businessType'
|
|
401
406
|
placeholder?: string
|
|
402
407
|
createOnly?: boolean
|
|
403
408
|
}
|
|
@@ -420,7 +425,7 @@ const fieldGroups: { title: string; fields: EditorField[] }[] = [
|
|
|
420
425
|
{ key: 'predDate', label: '预报日期', kind: 'date' },
|
|
421
426
|
{ key: 'predAmt', label: '预报金额(元)', kind: 'amount', placeholder: '请输入金额,最多两位小数' },
|
|
422
427
|
{ key: 'payDirec', label: '收支方向代码', placeholder: '请输入收支方向代码' },
|
|
423
|
-
{ key: 'busiType', label: '
|
|
428
|
+
{ key: 'busiType', label: '业务类型', kind: 'businessType' },
|
|
424
429
|
{ key: 'payChannel', label: '支付渠道代码', placeholder: '请输入支付渠道代码' },
|
|
425
430
|
{ key: 'clientName', label: '客户名称' },
|
|
426
431
|
{ key: 'clientType', label: '客户类型代码', placeholder: '请输入客户类型代码' },
|
|
@@ -441,7 +446,6 @@ const fieldGroups: { title: string; fields: EditorField[] }[] = [
|
|
|
441
446
|
{
|
|
442
447
|
title: '其他信息',
|
|
443
448
|
fields: [
|
|
444
|
-
{ key: 'predName', label: '预报人员姓名', createOnly: true },
|
|
445
449
|
{ key: 'predOrgName', label: '预报机构名称', createOnly: true },
|
|
446
450
|
{ key: 'billNo', label: '票号' },
|
|
447
451
|
{ key: 'billDate', label: '出票日期', kind: 'date' },
|
|
@@ -454,6 +458,7 @@ const fieldGroups: { title: string; fields: EditorField[] }[] = [
|
|
|
454
458
|
const form = reactive<ForecastFormValues>(createFormValues())
|
|
455
459
|
const formError = ref('')
|
|
456
460
|
const editorLoading = ref(false) // 读取修改前的原始数据。
|
|
461
|
+
let editorRequestId = 0 // 切换记录或关闭视图后,旧详情请求不再回填表单。
|
|
457
462
|
const submitting = ref(false) // 提交新增或修改。
|
|
458
463
|
const loadError = ref('')
|
|
459
464
|
const initialValues = ref<Partial<ForecastFormValues>>({})
|
|
@@ -511,33 +516,55 @@ const buildInitialValues = (detail: ForecastDetail): Partial<ForecastFormValues>
|
|
|
511
516
|
return initial
|
|
512
517
|
}
|
|
513
518
|
|
|
514
|
-
//
|
|
519
|
+
// 修改模式先读取原始数据;仅当前视图、当前记录的最新请求可以回填。
|
|
515
520
|
const loadEditorDetail = async () => {
|
|
516
|
-
if (editorMode.value !== 'edit') return
|
|
517
|
-
|
|
521
|
+
if (!editorDialogVisible.value || editorMode.value !== 'edit') return
|
|
522
|
+
const currentId = ++editorRequestId
|
|
523
|
+
const predRefNo = editorPredRefNo.value
|
|
524
|
+
const isCurrentRequest = () => !disposed
|
|
525
|
+
&& currentId === editorRequestId
|
|
526
|
+
&& editorDialogVisible.value
|
|
527
|
+
&& editorMode.value === 'edit'
|
|
528
|
+
&& editorPredRefNo.value === predRefNo
|
|
529
|
+
if (!predRefNo) {
|
|
530
|
+
editorLoading.value = false
|
|
518
531
|
loadError.value = '预报流水号缺失,无法修改'
|
|
519
532
|
return
|
|
520
533
|
}
|
|
521
534
|
editorLoading.value = true
|
|
522
535
|
loadError.value = ''
|
|
523
536
|
try {
|
|
524
|
-
const cachedRow = recallForecastRow(
|
|
525
|
-
const detailRow: ForecastRow = { ...cachedRow, predRefNo
|
|
526
|
-
|
|
527
|
-
if (
|
|
537
|
+
const cachedRow = recallForecastRow(predRefNo)
|
|
538
|
+
const detailRow: ForecastRow = { ...cachedRow, predRefNo }
|
|
539
|
+
const nextValues = buildInitialValues(await fetchDetail(detailRow))
|
|
540
|
+
if (!isCurrentRequest()) return
|
|
541
|
+
initialValues.value = nextValues
|
|
528
542
|
initializeForm()
|
|
529
543
|
} catch (error) {
|
|
530
|
-
if (
|
|
544
|
+
if (isCurrentRequest()) loadError.value = getErrorMessage(error)
|
|
531
545
|
} finally {
|
|
532
|
-
if (
|
|
546
|
+
if (isCurrentRequest()) editorLoading.value = false
|
|
533
547
|
}
|
|
534
548
|
}
|
|
535
549
|
|
|
536
|
-
//
|
|
550
|
+
// 切换状态时同步使旧请求失效;新增表单不受此前修改详情请求影响。
|
|
551
|
+
watch([editorDialogVisible, editorDialogPredRefNo, editorDialogMode], ([visible]) => {
|
|
552
|
+
editorRequestId += 1
|
|
553
|
+
editorLoading.value = false
|
|
554
|
+
if (!visible) return
|
|
555
|
+
initialValues.value = {}
|
|
556
|
+
loadError.value = ''
|
|
557
|
+
initializeForm()
|
|
558
|
+
if (editorMode.value === 'edit') void loadEditorDetail()
|
|
559
|
+
}, { flush: 'sync' })
|
|
537
560
|
|
|
538
561
|
watch(form, () => { formError.value = '' }, { deep: true })
|
|
539
562
|
|
|
540
|
-
const backToList = () =>
|
|
563
|
+
const backToList = () => {
|
|
564
|
+
editorDialogVisible.value = false
|
|
565
|
+
detailDialogVisible.value = false
|
|
566
|
+
void loadList()
|
|
567
|
+
}
|
|
541
568
|
|
|
542
569
|
// 日期控件清空时可能产生 null,提交前统一回到协议要求的字符串。
|
|
543
570
|
const submit = async () => {
|
|
@@ -555,7 +582,7 @@ const submit = async () => {
|
|
|
555
582
|
submitting.value = true
|
|
556
583
|
try {
|
|
557
584
|
if (editorMode.value === 'create') {
|
|
558
|
-
const body: ForecastCreateBody = {}
|
|
585
|
+
const body: ForecastCreateBody = { busiType: values.busiType }
|
|
559
586
|
for (const [key, value] of Object.entries(values)) {
|
|
560
587
|
if (key !== 'custRefSeq' && value?.trim()) Object.assign(body, { [key]: value.trim() })
|
|
561
588
|
}
|
|
@@ -577,7 +604,7 @@ const submit = async () => {
|
|
|
577
604
|
}
|
|
578
605
|
if (disposed) return
|
|
579
606
|
ElMessage.success(editorMode.value === 'create' ? '新增请求已处理' : '修改请求已处理')
|
|
580
|
-
|
|
607
|
+
backToList()
|
|
581
608
|
} catch (error) {
|
|
582
609
|
if (!disposed) ElMessage.error(getErrorMessage(error))
|
|
583
610
|
} finally {
|
|
@@ -641,7 +668,8 @@ const openEditFromDetail = () => {
|
|
|
641
668
|
if (!predRefNo) return
|
|
642
669
|
const row = recallForecastRow(predRefNo)
|
|
643
670
|
if (row) rememberForecastRow(row)
|
|
644
|
-
|
|
671
|
+
detailDialogVisible.value = false
|
|
672
|
+
openEdit({ ...row, predRefNo })
|
|
645
673
|
}
|
|
646
674
|
|
|
647
675
|
const displayValue = (field: DetailField) => {
|
|
@@ -649,6 +677,7 @@ const displayValue = (field: DetailField) => {
|
|
|
649
677
|
if (value === undefined || value === null || value === '') return '—'
|
|
650
678
|
if (field.kind === 'amount') return formatAmount(value)
|
|
651
679
|
if (field.kind === 'date') return formatDate(value)
|
|
680
|
+
if (field.key === 'busiType') return busiTypeLabels[value] ?? value
|
|
652
681
|
return String(value)
|
|
653
682
|
}
|
|
654
683
|
|
|
@@ -667,9 +696,9 @@ const loadDetail = async () => {
|
|
|
667
696
|
}
|
|
668
697
|
}
|
|
669
698
|
|
|
670
|
-
//
|
|
671
|
-
|
|
672
|
-
if (
|
|
699
|
+
// 每次进入详情视图都按预报流水号读取最新数据。
|
|
700
|
+
watch([detailDialogVisible, detailDialogPredRefNo], ([visible]) => {
|
|
701
|
+
if (visible) void loadDetail()
|
|
673
702
|
})
|
|
674
703
|
</script>
|
|
675
704
|
|
|
@@ -696,7 +725,7 @@ onActivated(() => {
|
|
|
696
725
|
<el-button link type="primary" :disabled="editorDisabled" @click="backToList">返回列表</el-button>
|
|
697
726
|
</el-alert>
|
|
698
727
|
<template v-else>
|
|
699
|
-
<p class="editor-note"
|
|
728
|
+
<p class="editor-note">金额单位为元;业务类型按接口提供的选项选择,其他未提供字典的字段按业务代码填写。</p>
|
|
700
729
|
<el-form :model="form" label-position="top" :disabled="editorDisabled" @submit.prevent="submit">
|
|
701
730
|
<section v-for="group in visibleGroups" :key="group.title" class="editor-group" :aria-label="group.title">
|
|
702
731
|
<h3>{{ group.title }}</h3>
|
|
@@ -722,6 +751,14 @@ onActivated(() => {
|
|
|
722
751
|
:editable="false"
|
|
723
752
|
clearable
|
|
724
753
|
/>
|
|
754
|
+
<el-select
|
|
755
|
+
v-else-if="field.kind === 'businessType'"
|
|
756
|
+
v-model="form.busiType"
|
|
757
|
+
placeholder="请选择业务类型"
|
|
758
|
+
clearable
|
|
759
|
+
>
|
|
760
|
+
<el-option v-for="option in busiTypeOptions" :key="option.value" :label="option.label" :value="option.value" />
|
|
761
|
+
</el-select>
|
|
725
762
|
<el-input
|
|
726
763
|
v-else-if="field.kind === 'textarea'"
|
|
727
764
|
v-model="form[field.key]"
|
|
@@ -804,6 +841,11 @@ onActivated(() => {
|
|
|
804
841
|
<el-form-item class="filter-date" label="交易日期" required>
|
|
805
842
|
<el-date-picker v-model="listFilters.tranDate" type="date" format="YYYY-MM-DD" value-format="YYYYMMDD" :clearable="false" :editable="false" />
|
|
806
843
|
</el-form-item>
|
|
844
|
+
<el-form-item label="业务类型">
|
|
845
|
+
<el-select v-model="listFilters.busiType" clearable placeholder="不限">
|
|
846
|
+
<el-option v-for="option in busiTypeOptions" :key="option.value" :label="option.label" :value="option.value" />
|
|
847
|
+
</el-select>
|
|
848
|
+
</el-form-item>
|
|
807
849
|
<!-- label 为查询字段名称,输入框较窄,占位文案统一为简短提示。 -->
|
|
808
850
|
<el-form-item v-for="[key, label] in listTextFilters" :key="key" :label="label">
|
|
809
851
|
<el-input v-model="listFilters[key]" placeholder="请输入" :maxlength="200" clearable />
|
|
@@ -909,7 +951,7 @@ onActivated(() => {
|
|
|
909
951
|
.filter-form :deep(.filter-date) { flex: 1.5 0 116px; }
|
|
910
952
|
.filter-form :deep(.filter-amount) { flex: 1.2 0 92px; }
|
|
911
953
|
.filter-form :deep(.el-form-item__label) { height: auto; margin-bottom: 4px; padding: 0; font-size: 12px; line-height: 16px; }
|
|
912
|
-
.filter-form :deep(.el-date-editor) { width: 100%; min-width: 0; }
|
|
954
|
+
.filter-form :deep(.el-date-editor), .filter-form :deep(.el-select) { width: 100%; min-width: 0; }
|
|
913
955
|
.filter-actions { flex: 0 0 auto; }
|
|
914
956
|
.forecast-table-region { flex: 1; min-width: 0; min-height: 0; }
|
|
915
957
|
.forecast-table { padding: 10px; }
|
|
@@ -954,7 +996,7 @@ onActivated(() => {
|
|
|
954
996
|
.editor-fields { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); column-gap: 18px; }
|
|
955
997
|
.editor-fields :deep(.el-form-item) { min-width: 0; margin-bottom: 14px; }
|
|
956
998
|
.editor-fields :deep(.el-form-item__label) { display: flex; align-items: center; gap: 6px; margin-bottom: 5px; padding: 0; line-height: 20px; }
|
|
957
|
-
.editor-fields :deep(.el-date-editor.el-input) { width: 100%; }
|
|
999
|
+
.editor-fields :deep(.el-date-editor.el-input), .editor-fields :deep(.el-select) { width: 100%; }
|
|
958
1000
|
.editor-field--wide { grid-column: 1 / -1; }
|
|
959
1001
|
.field-unavailable { color: var(--el-text-color-secondary); font-size: 11px; font-weight: 400; }
|
|
960
1002
|
.editor-footer { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px 12px; padding-top: 10px; border-top: 1px solid var(--el-border-color-lighter); }
|
|
@@ -9,8 +9,8 @@ export interface Tam120501001InBody {
|
|
|
9
9
|
billDate?: string
|
|
10
10
|
/** 票号 */
|
|
11
11
|
billNo?: string
|
|
12
|
-
/**
|
|
13
|
-
busiType
|
|
12
|
+
/** 业务类型:201 兑回、202 兑出、203 付现(新增必填)。 */
|
|
13
|
+
busiType: string
|
|
14
14
|
/** 客户名称 */
|
|
15
15
|
clientName?: string
|
|
16
16
|
/** 客户类型 */
|
|
@@ -37,8 +37,6 @@ export interface Tam120501001InBody {
|
|
|
37
37
|
predAmt?: string
|
|
38
38
|
/** 预报日期 */
|
|
39
39
|
predDate?: string
|
|
40
|
-
/** 预报人员姓名 */
|
|
41
|
-
predName?: string
|
|
42
40
|
/** 预报机构名称 */
|
|
43
41
|
predOrgName?: string
|
|
44
42
|
/** 用途 */
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import baseHttpClient from "@chic/http"
|
|
2
|
+
import type { InjectionQueryParams } from './typs'
|
|
2
3
|
|
|
3
4
|
export default {
|
|
4
5
|
/**
|
|
5
6
|
* 头寸-数字人民币注资明细查询。
|
|
6
7
|
* @param params 查询参数:capAdjustType 为调整类型,txnProceSts 为交易处理状态;
|
|
8
|
+
* txnDtStart、txnDtEnd 为交易日期起止范围,格式为 YYYYMMDD,可分别填写。
|
|
7
9
|
* 空值字段不发送,表示不限制该条件。
|
|
8
10
|
* 接口未声明分页请求参数,列表在页面内按返回结果本地分页。
|
|
9
11
|
*/
|
|
10
|
-
queryListApi: (params:
|
|
12
|
+
queryListApi: (params: InjectionQueryParams) => baseHttpClient.post<any>("/TAM/0001/140504001", params)
|
|
11
13
|
}
|
|
@@ -14,6 +14,8 @@ type AmountSortOrder = 'ascending' | 'descending' | null
|
|
|
14
14
|
const defaultFilters: InjectionFilters = {
|
|
15
15
|
capAdjustType: '', // 调整类型。
|
|
16
16
|
txnProceSts: '', // 交易处理状态。
|
|
17
|
+
txnDtStart: '', // 交易开始日期,不填时不限制。
|
|
18
|
+
txnDtEnd: '', // 交易结束日期,不填时不限制。
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
// 重复输入项共用一份配置,依次为字段、名称和长度限制。
|
|
@@ -241,13 +243,25 @@ const detailValues = computed(() => {
|
|
|
241
243
|
}
|
|
242
244
|
})
|
|
243
245
|
|
|
246
|
+
// 校验 YYYYMMDD 日期,避免只检查位数后放过不存在的日期。
|
|
247
|
+
const isValidDate = (value: string): boolean => {
|
|
248
|
+
if (!/^\d{8}$/.test(value)) return false
|
|
249
|
+
const year = Number(value.slice(0, 4))
|
|
250
|
+
const month = Number(value.slice(4, 6))
|
|
251
|
+
const day = Number(value.slice(6, 8))
|
|
252
|
+
if (year < 1 || month < 1 || month > 12 || day < 1) return false
|
|
253
|
+
const leap = year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0)
|
|
254
|
+
const monthDays = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
255
|
+
return day <= (monthDays[month - 1] || 0)
|
|
256
|
+
}
|
|
257
|
+
|
|
244
258
|
// 读取表单并组装请求参数;空值字段不发送,表示不限制该条件。
|
|
245
259
|
const getQueryParams = (): InjectionQueryParams => {
|
|
246
|
-
const params: InjectionQueryParams = {
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
260
|
+
const params: InjectionQueryParams = {}
|
|
261
|
+
for (const key of Object.keys(defaultFilters) as (keyof InjectionFilters)[]) {
|
|
262
|
+
const value = filters.value[key]?.trim()
|
|
263
|
+
if (value) params[key] = value
|
|
264
|
+
}
|
|
251
265
|
return params
|
|
252
266
|
}
|
|
253
267
|
// 仅查询、重置时更新;翻页和刷新沿用这份条件。
|
|
@@ -261,7 +275,7 @@ const loadList = async () => {
|
|
|
261
275
|
try {
|
|
262
276
|
// head 为公共响应头,body 为包含明细列表的业务数据;
|
|
263
277
|
// body 内的 totalCount、totalPage 供服务端分页时使用,当前接按返回的完整列表本地分页。
|
|
264
|
-
const { head, body } = await API.queryListApi(appliedQuery)
|
|
278
|
+
const { head, body } = await API.queryListApi(appliedQuery)
|
|
265
279
|
if (currentRequestId !== requestId) return
|
|
266
280
|
if (head?.retCode !== '00000') {
|
|
267
281
|
throw new Error(head?.retMsg || '查询失败')
|
|
@@ -285,7 +299,13 @@ const loadList = async () => {
|
|
|
285
299
|
|
|
286
300
|
// 新查询从第一页开始;重置后复用同一查询流程。
|
|
287
301
|
const search = () => {
|
|
288
|
-
|
|
302
|
+
const query = getQueryParams()
|
|
303
|
+
if (query.txnDtStart && !isValidDate(query.txnDtStart)) return void ElMessage.warning('请选择有效的交易开始日期')
|
|
304
|
+
if (query.txnDtEnd && !isValidDate(query.txnDtEnd)) return void ElMessage.warning('请选择有效的交易结束日期')
|
|
305
|
+
if (query.txnDtStart && query.txnDtEnd && query.txnDtStart > query.txnDtEnd) {
|
|
306
|
+
return void ElMessage.warning('交易开始日期不能晚于结束日期')
|
|
307
|
+
}
|
|
308
|
+
appliedQuery = query
|
|
289
309
|
pagination.current = 1
|
|
290
310
|
void loadList()
|
|
291
311
|
}
|
|
@@ -323,6 +343,12 @@ onBeforeUnmount(() => {
|
|
|
323
343
|
<!-- 查询条件:控件尺寸跟随表格设置,卡片内边距为 10px,点击查询生效。 -->
|
|
324
344
|
<el-card v-show="showSearchBar" class="injection-search" shadow="never" :body-style="{ padding: '10px' }">
|
|
325
345
|
<el-form class="filter-form" :model="filters" :size="tableSettings.size" label-position="top" @submit.prevent="search()">
|
|
346
|
+
<el-form-item class="filter-date" label="交易开始日期">
|
|
347
|
+
<el-date-picker v-model="filters.txnDtStart" type="date" format="YYYY-MM-DD" value-format="YYYYMMDD" placeholder="不限" :editable="false" clearable />
|
|
348
|
+
</el-form-item>
|
|
349
|
+
<el-form-item class="filter-date" label="交易结束日期">
|
|
350
|
+
<el-date-picker v-model="filters.txnDtEnd" type="date" format="YYYY-MM-DD" value-format="YYYYMMDD" placeholder="不限" :editable="false" clearable />
|
|
351
|
+
</el-form-item>
|
|
326
352
|
<!-- prop 为查询字段,label 为字段名称,maxlength 为允许输入的最大字符数。 -->
|
|
327
353
|
<el-form-item
|
|
328
354
|
v-for="[prop, label, maxlength] in filterFields"
|
|
@@ -448,7 +474,9 @@ onBeforeUnmount(() => {
|
|
|
448
474
|
.filter-form { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px 12px; }
|
|
449
475
|
/* 筛选项较窄时换行,宽屏时封顶 280px,输入框不至于无限拉长。 */
|
|
450
476
|
.filter-form :deep(.el-form-item) { flex: 1 0 105px; max-width: 280px; min-width: 0; margin: 0; }
|
|
477
|
+
.filter-form :deep(.filter-date) { flex-basis: 160px; }
|
|
451
478
|
.filter-form :deep(.el-form-item__label) { margin-bottom: 4px; }
|
|
479
|
+
.filter-form :deep(.el-date-editor) { width: 100%; min-width: 0; }
|
|
452
480
|
.filter-actions { flex-shrink: 0; margin-left: auto; }
|
|
453
481
|
.amount-cell { color: var(--el-color-primary-dark-2); font-variant-numeric: tabular-nums; font-weight: 600; }
|
|
454
482
|
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
export interface InjectionFilters {
|
|
3
3
|
capAdjustType: string // 调整类型。
|
|
4
4
|
txnProceSts: string // 交易处理状态。
|
|
5
|
+
txnDtStart: string // 交易开始日期,格式为 YYYYMMDD。
|
|
6
|
+
txnDtEnd: string // 交易结束日期,格式为 YYYYMMDD。
|
|
5
7
|
}
|
|
6
8
|
|
|
7
9
|
/** 列表查询参数;空字段在提交前剔除,表示不限制。 */
|
|
8
|
-
export
|
|
9
|
-
}
|
|
10
|
+
export type InjectionQueryParams = Partial<InjectionFilters>
|
|
10
11
|
|
|
11
12
|
/** 接口返回的数字人民币注资明细字段。 */
|
|
12
13
|
export interface InjectionRecord {
|
|
@@ -23,14 +23,15 @@ export default {
|
|
|
23
23
|
* 分页查询核销预报信息。
|
|
24
24
|
* @param params 查询参数:tranDate 为交易日期(必填,YYYYMMDD);pageIndex、pageSize 为页码和每页条数。
|
|
25
25
|
* 业务筛选:acctNo 为我方账号,banchPre 为机构关键字,busiType 为业务类型,
|
|
26
|
-
* clientName 为客户名称,competName 为交易对手名称,payDirec
|
|
27
|
-
* startTransAmt、endTransAmt 为金额范围,verifyStatusList
|
|
26
|
+
* clientName 为客户名称,competName 为交易对手名称,payDirec 为收支方向(1 收入、2 支出),
|
|
27
|
+
* startTransAmt、endTransAmt 为金额范围,verifyStatusList 待核销传 [1,3]、已核销传 [2]。
|
|
28
28
|
*/
|
|
29
29
|
queryForecastApi: (params: unknown) => baseHttpClient.post<any>("/TAM/0001/140502001", params),
|
|
30
30
|
/**
|
|
31
31
|
* 分页查询核销流水信息。
|
|
32
32
|
* @param params 查询参数:tranDate 为交易日期(必填,YYYYMMDD);pageIndex、pageSize 为页码和每页条数。
|
|
33
|
-
* 业务筛选:orgName 为我方机构,
|
|
33
|
+
* 业务筛选:busiType 为业务类型(201 兑回、202 兑出、203 付现),orgName 为我方机构,
|
|
34
|
+
* payerName、payeeName 为付款人和收款人名称,
|
|
34
35
|
* payDirec 为收支方向,startTransAmt、endTransAmt 为金额范围,verifyStatus 为核销状态。
|
|
35
36
|
*/
|
|
36
37
|
queryFlowApi: (params: unknown) => baseHttpClient.post<any>("/TAM/0001/140502002", params)
|
|
@@ -123,12 +123,14 @@ const compareAmounts = (left: string, right: string): number => {
|
|
|
123
123
|
|
|
124
124
|
/** 业务类型字典:201 兑回、202 兑出、203 付现;超出字典的值保留原文。 */
|
|
125
125
|
const busiTypeLabels: Record<string, string> = { '201': '兑回', '202': '兑出', '203': '付现' }
|
|
126
|
-
/**
|
|
127
|
-
const
|
|
126
|
+
/** 核销预报使用 1 收入、2 支出;流水接口仍使用 I 来账、O 往账。 */
|
|
127
|
+
const forecastPayDirecLabels: Record<string, string> = { '1': '收入', '2': '支出' }
|
|
128
|
+
const flowPayDirecLabels: Record<string, string> = { I: '来账', O: '往账' }
|
|
128
129
|
/** 支付渠道:核销预报为 8、流水为 5,均表示数字人民币;其余渠道保留原文。 */
|
|
129
130
|
const payChannelLabels: Record<string, string> = { '5': '数字人民币', '8': '数字人民币' }
|
|
130
|
-
/**
|
|
131
|
-
const verifyStatusLabels: Record<string, string> = { '1': '未核销', '2': '已核销' }
|
|
131
|
+
/** 响应核销状态字典;流水筛选仍按请求协议仅提供 1、2。 */
|
|
132
|
+
const verifyStatusLabels: Record<string, string> = { '1': '未核销', '2': '已核销', '3': '部分核销' }
|
|
133
|
+
const tranStatusLabels: Record<string, string> = { S: '成功', P: '处理中', F: '失败' }
|
|
132
134
|
|
|
133
135
|
/** 读取字典文案;空值显示横线,未登记的代码保留原文展示。 */
|
|
134
136
|
const dictLabel = (labels: Record<string, string>, value?: string): string => {
|
|
@@ -171,6 +173,9 @@ const openWriteOff = () => {
|
|
|
171
173
|
if (busy.value) return
|
|
172
174
|
const predRows = predSelection.value.filter((row) => row.predRefNo?.trim())
|
|
173
175
|
if (!predRows.length) return void ElMessage.warning('请先勾选带预报流水号的预报记录')
|
|
176
|
+
const selected = [...predRows, ...flowSelection.value]
|
|
177
|
+
if (selected.some(row => row.verifyStatus === '3')) return void ElMessage.warning('部分核销记录暂不支持继续核销')
|
|
178
|
+
if (selected.some(row => row.verifyStatus !== '1')) return void ElMessage.warning('请仅勾选未核销记录')
|
|
174
179
|
writeOffPredItems.value = predRows.map((row, index) => ({
|
|
175
180
|
// 预报流水号可能重复,追加下标保证弹窗表格的渲染键唯一。
|
|
176
181
|
key: `${row.predRefNo?.trim()}-${index}`,
|
|
@@ -239,6 +244,9 @@ const cancelWriteOff = async () => {
|
|
|
239
244
|
const predRows = predSelection.value.filter((row) => row.predRefNo?.trim())
|
|
240
245
|
const flowRows = flowSelection.value.filter(hasRefNo)
|
|
241
246
|
if (!predRows.length && !flowRows.length) return void ElMessage.warning('请先勾选要撤销核销的预报或流水')
|
|
247
|
+
const selected = [...predRows, ...flowRows]
|
|
248
|
+
if (selected.some(row => row.verifyStatus === '3')) return void ElMessage.warning('部分核销记录暂不支持撤销核销')
|
|
249
|
+
if (selected.some(row => row.verifyStatus !== '2')) return void ElMessage.warning('请仅勾选已核销记录')
|
|
242
250
|
const summaryParts: string[] = []
|
|
243
251
|
if (predRows.length) summaryParts.push(`预报 ${predRows.length} 条`)
|
|
244
252
|
if (flowRows.length) summaryParts.push(`流水 ${flowRows.length} 条`)
|
|
@@ -300,7 +308,7 @@ type ForecastAppliedQuery = {
|
|
|
300
308
|
busiType?: string // 业务类型:201 兑回、202 兑出、203 付现。
|
|
301
309
|
clientName?: string // 客户名称。
|
|
302
310
|
competName?: string // 交易对手名称。
|
|
303
|
-
payDirec?: string // 收支方向:
|
|
311
|
+
payDirec?: string // 收支方向:1 收入、2 支出。
|
|
304
312
|
startTransAmt?: string // 起始金额。
|
|
305
313
|
endTransAmt?: string // 截止金额。
|
|
306
314
|
verifyStatusList?: string[] // 核销状态列表。
|
|
@@ -317,7 +325,7 @@ const createForecastFilters = () => ({
|
|
|
317
325
|
payDirec: '',
|
|
318
326
|
startTransAmt: '',
|
|
319
327
|
endTransAmt: '',
|
|
320
|
-
|
|
328
|
+
verifyStatusGroup: '', // 待核销包含未核销和部分核销两种状态。
|
|
321
329
|
})
|
|
322
330
|
const forecastFilters = ref(createForecastFilters())
|
|
323
331
|
let forecastAppliedQuery: ForecastAppliedQuery = { tranDate: dcpsRuntime.date }
|
|
@@ -331,8 +339,10 @@ const forecastTextFilters = [
|
|
|
331
339
|
] as const
|
|
332
340
|
// 已在接口附件声明的枚举,直接生成下拉选项;缺省字典的状态不猜测。
|
|
333
341
|
const busiTypeOptions = Object.entries(busiTypeLabels).map(([value, label]) => ({ value, label }))
|
|
334
|
-
const
|
|
335
|
-
const
|
|
342
|
+
const forecastPayDirecOptions = Object.entries(forecastPayDirecLabels).map(([value, label]) => ({ value, label }))
|
|
343
|
+
const flowPayDirecOptions = Object.entries(flowPayDirecLabels).map(([value, label]) => ({ value, label }))
|
|
344
|
+
const flowVerifyStatusOptions = ['1', '2'].map(value => ({ value, label: verifyStatusLabels[value] }))
|
|
345
|
+
const forecastVerifyStatusOptions = [{ value: 'pending', label: '待核销' }, { value: 'verified', label: '已核销' }]
|
|
336
346
|
|
|
337
347
|
// 列表、分页与请求状态;请求序号防止旧响应覆盖新结果。
|
|
338
348
|
const forecastRows = ref<ForecastRow[]>([])
|
|
@@ -372,7 +382,8 @@ const forecastColumns: ColumnOption<ForecastRow>[] = [
|
|
|
372
382
|
forecastDictColumn('busiType', '业务类型', 95, busiTypeLabels),
|
|
373
383
|
forecastTextColumn('clientName', '客户名称', 170),
|
|
374
384
|
forecastTextColumn('competName', '交易对手名称', 170),
|
|
375
|
-
|
|
385
|
+
forecastTextColumn('competAcct', '交易对手账号', 190),
|
|
386
|
+
forecastDictColumn('payDirec', '收支方向', 90, forecastPayDirecLabels),
|
|
376
387
|
forecastDictColumn('payChannel', '支付渠道', 110, payChannelLabels),
|
|
377
388
|
{ prop: 'predAmt', label: '预报金额(元)', width: 160, align: 'right', useSlot: true, slotName: 'predAmount' },
|
|
378
389
|
{ prop: 'verifyAmt', label: '已核销金额(元)', width: 160, align: 'right', useSlot: true, slotName: 'verifyAmount' },
|
|
@@ -445,7 +456,8 @@ const searchForecast = () => {
|
|
|
445
456
|
if (source.payDirec.trim()) query.payDirec = source.payDirec.trim()
|
|
446
457
|
if (startTransAmt) query.startTransAmt = startTransAmt
|
|
447
458
|
if (endTransAmt) query.endTransAmt = endTransAmt
|
|
448
|
-
if (source.
|
|
459
|
+
if (source.verifyStatusGroup === 'pending') query.verifyStatusList = ['1', '3']
|
|
460
|
+
if (source.verifyStatusGroup === 'verified') query.verifyStatusList = ['2']
|
|
449
461
|
forecastAppliedQuery = query
|
|
450
462
|
forecastPagination.current = 1
|
|
451
463
|
void loadForecastList()
|
|
@@ -468,6 +480,8 @@ const changeForecastPage = (current: number, size = forecastPagination.size) =>
|
|
|
468
480
|
*/
|
|
469
481
|
const openEdit = (row: ForecastRow) => {
|
|
470
482
|
if (busy.value || !forecastHasReference(row)) return
|
|
483
|
+
if (row.verifyStatus === '3') return void ElMessage.warning('部分核销记录暂不支持修改核销金额')
|
|
484
|
+
if (row.verifyStatus !== '1') return void ElMessage.warning('请先撤销核销后再修改核销金额')
|
|
471
485
|
editForm.value = {
|
|
472
486
|
predRefNo: row.predRefNo?.trim() || '',
|
|
473
487
|
predAmt: row.predAmt?.trim() || '',
|
|
@@ -507,6 +521,7 @@ const submitEdit = async () => {
|
|
|
507
521
|
type FlowAppliedQuery = {
|
|
508
522
|
tranDate: string // 必填交易日期,格式为 YYYYMMDD。
|
|
509
523
|
orgName?: string // 我方机构。
|
|
524
|
+
busiType?: string // 业务类型:201 兑回、202 兑出、203 付现。
|
|
510
525
|
payerName?: string // 付款人名称。
|
|
511
526
|
payeeName?: string // 收款人名称。
|
|
512
527
|
payDirec?: string // 收支方向:I 来账、O 往账。
|
|
@@ -519,6 +534,7 @@ type FlowAppliedQuery = {
|
|
|
519
534
|
const createFlowFilters = () => ({
|
|
520
535
|
tranDate: dcpsRuntime.date,
|
|
521
536
|
orgName: '',
|
|
537
|
+
busiType: '',
|
|
522
538
|
payerName: '',
|
|
523
539
|
payeeName: '',
|
|
524
540
|
payDirec: '',
|
|
@@ -571,13 +587,13 @@ const flowColumns: ColumnOption<FlowRow>[] = [
|
|
|
571
587
|
flowDictColumn('busiType', '业务类型', 95, busiTypeLabels),
|
|
572
588
|
flowTextColumn('payerName', '付款人名称', 170),
|
|
573
589
|
flowTextColumn('payeeName', '收款人名称', 170),
|
|
574
|
-
flowDictColumn('payDirec', '收支方向', 90,
|
|
590
|
+
flowDictColumn('payDirec', '收支方向', 90, flowPayDirecLabels),
|
|
575
591
|
{ prop: 'tranAmt', label: '交易金额(元)', width: 160, align: 'right', useSlot: true, slotName: 'tranAmount' },
|
|
576
592
|
flowDictColumn('verifyStatus', '核销状态', 95, verifyStatusLabels),
|
|
577
593
|
flowTextColumn('branchName', '我方机构', 150),
|
|
578
594
|
flowTextColumn('collBankName', '收款行名称', 160),
|
|
579
595
|
flowTextColumn('payBankName', '付款行名称', 160),
|
|
580
|
-
|
|
596
|
+
flowDictColumn('tranStatus', '交易状态', 110, tranStatusLabels),
|
|
581
597
|
flowTextColumn('remark', '备注', 160),
|
|
582
598
|
flowTextColumn('branchCode', '机构行号', 150, false),
|
|
583
599
|
flowTextColumn('collBankNo', '收款行行号', 170, false),
|
|
@@ -643,6 +659,7 @@ const searchFlow = () => {
|
|
|
643
659
|
const value = source[key].trim()
|
|
644
660
|
if (value) query[key] = value
|
|
645
661
|
}
|
|
662
|
+
if (source.busiType.trim()) query.busiType = source.busiType.trim()
|
|
646
663
|
if (source.payDirec.trim()) query.payDirec = source.payDirec.trim()
|
|
647
664
|
if (source.verifyStatus.trim()) query.verifyStatus = source.verifyStatus.trim()
|
|
648
665
|
if (startTransAmt) query.startTransAmt = startTransAmt
|
|
@@ -662,6 +679,11 @@ const changeFlowPage = (current: number, size = flowPagination.size) => {
|
|
|
662
679
|
void loadFlowList()
|
|
663
680
|
}
|
|
664
681
|
|
|
682
|
+
/** 未定义部分核销的继续处理契约,操作仅允许原协议的未核销/已核销记录。 */
|
|
683
|
+
const canWriteOff = computed(() => predSelection.value.length > 0 && [...predSelection.value, ...flowSelection.value].every(row => row.verifyStatus === '1'))
|
|
684
|
+
const canCancelWriteOff = computed(() => (predSelection.value.length + flowSelection.value.length) > 0 && [...predSelection.value, ...flowSelection.value].every(row => row.verifyStatus === '2'))
|
|
685
|
+
const hasPartialRows = computed(() => [...forecastRows.value, ...flowRows.value].some(row => row.verifyStatus === '3'))
|
|
686
|
+
|
|
665
687
|
/** 核销、撤销核销成功后统一刷新两个列表;勾选在各自的刷新流程中清空。 */
|
|
666
688
|
const refreshBoth = async () => {
|
|
667
689
|
await Promise.all([loadForecastList(), loadFlowList()])
|
|
@@ -683,6 +705,7 @@ onBeforeUnmount(() => {
|
|
|
683
705
|
<template>
|
|
684
706
|
<ElConfigProvider :size="forecastTableSettings.size">
|
|
685
707
|
<main class="dcps-page">
|
|
708
|
+
<el-alert v-if="hasPartialRows" title="部分核销记录可查询,暂不支持继续核销、修改或撤销操作。" type="info" :closable="false" show-icon />
|
|
686
709
|
<!-- 上方:预报信息表格,核销、撤销核销按钮统一放在本表格工具栏。 -->
|
|
687
710
|
<section class="view-region">
|
|
688
711
|
<!-- 查询条件:控件尺寸跟随表格设置,卡片内边距为 10px,点击查询生效。 -->
|
|
@@ -692,8 +715,8 @@ onBeforeUnmount(() => {
|
|
|
692
715
|
<el-date-picker v-model="forecastFilters.tranDate" type="date" format="YYYY-MM-DD" value-format="YYYYMMDD" :clearable="false" :editable="false" />
|
|
693
716
|
</el-form-item>
|
|
694
717
|
<el-form-item class="filter-status" label="核销状态">
|
|
695
|
-
<el-select v-model="forecastFilters.
|
|
696
|
-
<el-option v-for="option in
|
|
718
|
+
<el-select v-model="forecastFilters.verifyStatusGroup" clearable placeholder="不限">
|
|
719
|
+
<el-option v-for="option in forecastVerifyStatusOptions" :key="option.value" :label="option.label" :value="option.value" />
|
|
697
720
|
</el-select>
|
|
698
721
|
</el-form-item>
|
|
699
722
|
<el-form-item class="filter-status" label="业务类型">
|
|
@@ -703,7 +726,7 @@ onBeforeUnmount(() => {
|
|
|
703
726
|
</el-form-item>
|
|
704
727
|
<el-form-item class="filter-status" label="收支方向">
|
|
705
728
|
<el-select v-model="forecastFilters.payDirec" clearable placeholder="不限">
|
|
706
|
-
<el-option v-for="option in
|
|
729
|
+
<el-option v-for="option in forecastPayDirecOptions" :key="option.value" :label="option.label" :value="option.value" />
|
|
707
730
|
</el-select>
|
|
708
731
|
</el-form-item>
|
|
709
732
|
<!-- prop 为查询字段,label 为字段名称,maxlength 为允许输入的最大字符数。 -->
|
|
@@ -754,13 +777,15 @@ onBeforeUnmount(() => {
|
|
|
754
777
|
<!-- 核销必选预报,单独勾选流水时按钮保持禁用;按钮文案带上两个表格的勾选总数。 -->
|
|
755
778
|
<el-button
|
|
756
779
|
type="primary"
|
|
757
|
-
:disabled="busy || !
|
|
780
|
+
:disabled="busy || !canWriteOff"
|
|
781
|
+
title="仅支持未核销记录"
|
|
758
782
|
@click="openWriteOff"
|
|
759
783
|
>核销({{ predSelection.length + flowSelection.length }})</el-button>
|
|
760
784
|
<el-button
|
|
761
785
|
type="warning"
|
|
762
786
|
plain
|
|
763
|
-
:disabled="busy || !
|
|
787
|
+
:disabled="busy || !canCancelWriteOff"
|
|
788
|
+
title="仅支持已核销记录"
|
|
764
789
|
@click="cancelWriteOff"
|
|
765
790
|
>撤销核销({{ predSelection.length + flowSelection.length }})</el-button>
|
|
766
791
|
</div>
|
|
@@ -770,7 +795,7 @@ onBeforeUnmount(() => {
|
|
|
770
795
|
<template #verifyAmount="{ row }"><span class="amount-cell">{{ formatAmount(row.verifyAmt) }}</span></template>
|
|
771
796
|
<template #actions="{ row }">
|
|
772
797
|
<div class="row-actions" @dblclick.stop>
|
|
773
|
-
<el-button link type="primary" :disabled="busy || !forecastHasReference(row)" @click.stop="openEdit(row)">修改</el-button>
|
|
798
|
+
<el-button link type="primary" :disabled="busy || !forecastHasReference(row) || row.verifyStatus !== '1'" title="仅未核销记录可修改核销金额" @click.stop="openEdit(row)">修改</el-button>
|
|
774
799
|
</div>
|
|
775
800
|
</template>
|
|
776
801
|
</ArtDataTable>
|
|
@@ -824,12 +849,17 @@ onBeforeUnmount(() => {
|
|
|
824
849
|
</el-form-item>
|
|
825
850
|
<el-form-item class="filter-status" label="核销状态">
|
|
826
851
|
<el-select v-model="flowFilters.verifyStatus" clearable placeholder="不限">
|
|
827
|
-
<el-option v-for="option in
|
|
852
|
+
<el-option v-for="option in flowVerifyStatusOptions" :key="option.value" :label="option.label" :value="option.value" />
|
|
853
|
+
</el-select>
|
|
854
|
+
</el-form-item>
|
|
855
|
+
<el-form-item class="filter-status" label="业务类型">
|
|
856
|
+
<el-select v-model="flowFilters.busiType" clearable placeholder="不限">
|
|
857
|
+
<el-option v-for="option in busiTypeOptions" :key="option.value" :label="option.label" :value="option.value" />
|
|
828
858
|
</el-select>
|
|
829
859
|
</el-form-item>
|
|
830
860
|
<el-form-item class="filter-status" label="收支方向">
|
|
831
861
|
<el-select v-model="flowFilters.payDirec" clearable placeholder="不限">
|
|
832
|
-
<el-option v-for="option in
|
|
862
|
+
<el-option v-for="option in flowPayDirecOptions" :key="option.value" :label="option.label" :value="option.value" />
|
|
833
863
|
</el-select>
|
|
834
864
|
</el-form-item>
|
|
835
865
|
<!-- prop 为查询字段,label 为字段名称,maxlength 为允许输入的最大字符数。 -->
|
|
@@ -93,13 +93,13 @@ export interface Tam140502001InBody {
|
|
|
93
93
|
endTransAmt?: string
|
|
94
94
|
/** 支付渠道,8:数字人民币 */
|
|
95
95
|
payChannel?: string
|
|
96
|
-
/**
|
|
96
|
+
/** 收支方向:1 收入、2 支出。 */
|
|
97
97
|
payDirec?: string
|
|
98
98
|
/** 起始金额 */
|
|
99
99
|
startTransAmt?: string
|
|
100
100
|
/** 交易日期 */
|
|
101
101
|
tranDate: string
|
|
102
|
-
/**
|
|
102
|
+
/** 核销状态列表:待核销 [1, 3]、已核销 [2]。 */
|
|
103
103
|
verifyStatusList?: Array<string>
|
|
104
104
|
}
|
|
105
105
|
|
|
@@ -117,6 +117,8 @@ export interface Tam140502001OutBodyResultList {
|
|
|
117
117
|
busiType?: string
|
|
118
118
|
/** 客户名称 */
|
|
119
119
|
clientName?: string
|
|
120
|
+
/** 交易对手账号 */
|
|
121
|
+
competAcct?: string
|
|
120
122
|
/** 交易对手名称 */
|
|
121
123
|
competName?: string
|
|
122
124
|
/** 释放状态 */
|
|
@@ -125,7 +127,7 @@ export interface Tam140502001OutBodyResultList {
|
|
|
125
127
|
groundBranchName?: string
|
|
126
128
|
/** 支付渠道,8:数字人民币 */
|
|
127
129
|
payChannel?: string
|
|
128
|
-
/**
|
|
130
|
+
/** 收支方向:1 收入、2 支出。 */
|
|
129
131
|
payDirec?: string
|
|
130
132
|
/** 预报金额 */
|
|
131
133
|
predAmt?: string
|
|
@@ -143,6 +145,8 @@ export interface Tam140502001OutBodyResultList {
|
|
|
143
145
|
|
|
144
146
|
/** Tam140502002In.Body */
|
|
145
147
|
export interface Tam140502002InBody {
|
|
148
|
+
/** 业务类型:201 兑回、202 兑出、203 付现。 */
|
|
149
|
+
busiType?: string
|
|
146
150
|
/** 截止金额 */
|
|
147
151
|
endTransAmt?: string
|
|
148
152
|
/** 我方机构 */
|
|
@@ -177,8 +181,8 @@ export interface Tam140502002OutBodyResultList {
|
|
|
177
181
|
branchCode?: string
|
|
178
182
|
/** 机构名称 */
|
|
179
183
|
branchName?: string
|
|
180
|
-
/**
|
|
181
|
-
|
|
184
|
+
/** 交易状态:S 成功、P 处理中、F 失败。 */
|
|
185
|
+
tranStatus?: string
|
|
182
186
|
/** 业务类型,201:兑回 ,202:兑出,203:付现 */
|
|
183
187
|
busiType?: string
|
|
184
188
|
/** 收款行行名称 */
|
|
@@ -210,7 +214,7 @@ export interface Tam140502002OutBodyResultList {
|
|
|
210
214
|
tranDate?: string
|
|
211
215
|
/** 交易时间 */
|
|
212
216
|
tranTime?: string
|
|
213
|
-
/**
|
|
217
|
+
/** 核销状态:1 未核销、2 已核销、3 部分核销。 */
|
|
214
218
|
verifyStatus?: string
|
|
215
219
|
}
|
|
216
220
|
|