vue3-element-plus-app-chu 0.0.9 → 0.0.10
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/package.json +1 -1
- package/src/view/dcps/digital-rmb-account-balance/api.ts +17 -0
- package/src/view/dcps/digital-rmb-account-balance/index.vue +428 -0
- package/src/view/dcps/digital-rmb-account-balance/typs.ts +38 -0
- package/src/view/dcps/digital-rmb-injection/api.ts +11 -0
- package/src/view/dcps/digital-rmb-injection/components/InjectionDetailDialog.vue +171 -0
- package/src/view/dcps/digital-rmb-injection/index.vue +365 -0
- package/src/view/dcps/digital-rmb-injection/typs.ts +41 -0
- package/src/view/dcps/digital-rmb-write-off/api.ts +186 -0
- package/src/view/dcps/digital-rmb-write-off/components/FlowWriteOffView.vue +299 -0
- package/src/view/dcps/digital-rmb-write-off/components/ForecastWriteOffView.vue +437 -0
- package/src/view/dcps/digital-rmb-write-off/index.vue +262 -0
- package/src/view/dcps/digital-rmb-write-off/typs.ts +59 -0
- package/src/view/dcps/layout/index.vue +25 -1
package/package.json
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import baseHttpClient from "@chic/http"
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
/**
|
|
5
|
+
* 头寸-账户余额查询申请。
|
|
6
|
+
* @param params 请求参数;接口未声明筛选字段,保持空对象即可。
|
|
7
|
+
* 页面在进入和点击查询时各发起一次请求,展示返回的单条账户行余额信息。
|
|
8
|
+
*/
|
|
9
|
+
queryBalanceApi: (params: unknown = {}) => baseHttpClient.post<any>("/TAM/0001/140506001", params),
|
|
10
|
+
/**
|
|
11
|
+
* 注资、预注资管理-注资/预注资发起。
|
|
12
|
+
* @param params 提交参数:adjustType 为调整类型(PRFD 注资调增、FDRD 注资调减、
|
|
13
|
+
* FFIC 预注资调增、FFRD 预注资调减),tranAmt 为调整金额(元)。
|
|
14
|
+
* 提交成功后重新查询账户行余额。
|
|
15
|
+
*/
|
|
16
|
+
createApi: (params: unknown) => baseHttpClient.post<any>("/TAM/0001/120506001", params)
|
|
17
|
+
}
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
|
3
|
+
import { ElMessage } from 'element-plus'
|
|
4
|
+
import API from './api'
|
|
5
|
+
import type { AccountBalanceInfo, InjectionCreateParams } from './typs'
|
|
6
|
+
|
|
7
|
+
// 余额信息、请求与加载状态。
|
|
8
|
+
const balance = ref<AccountBalanceInfo | null>(null) // 当前展示的账户行余额信息,未查询到时为 null。
|
|
9
|
+
const loading = ref(false) // 查询是否进行中,同时控制查询按钮状态。
|
|
10
|
+
const lastQueryTime = ref('') // 最近一次成功查询的本地时间,展示在标题区。
|
|
11
|
+
let requestId = 0 // 最新请求的序号,用于忽略过期响应。
|
|
12
|
+
let disposed = false // 页面是否已卸载,避免未完成请求继续更新界面。
|
|
13
|
+
|
|
14
|
+
// 提取异常提示;error 为捕获到的请求或处理异常。
|
|
15
|
+
const getErrorMessage = (error: unknown) => error instanceof Error ? error.message : '请求失败,请稍后重试'
|
|
16
|
+
|
|
17
|
+
// 将接口时间 value 转为浏览器本地时间,空值显示横线,无效时间保留原文。
|
|
18
|
+
const formatDateTime = (value?: string) => {
|
|
19
|
+
if (!value?.trim()) return '—'
|
|
20
|
+
const date = new Date(value) // 解析后的日期对象,用于判断接口时间是否有效。
|
|
21
|
+
return Number.isNaN(date.getTime()) ? value : date.toLocaleString('zh-CN', { hour12: false })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 将金额字符串 value 拆成符号、整数和小数,避免转为浮点数后丢失精度。
|
|
25
|
+
const parseAmount = (value?: string) => {
|
|
26
|
+
const text = String(value ?? '').trim()
|
|
27
|
+
// 仅去掉合法千分位分隔符,避免把格式错误的 1,23 当成正常金额。
|
|
28
|
+
const normalized = /^[+-]?\d{1,3}(?:,\d{3})+(?:\.\d*)?$/.test(text) ? text.replace(/,/g, '') : text
|
|
29
|
+
const match = /^([+-]?)(\d*)(?:\.(\d*))?$/.exec(normalized) // 匹配结果:1 为符号,2 为整数,3 为小数。
|
|
30
|
+
if (!match || (!match[2] && !match[3])) return null
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
negative: match[1] === '-', // 是否为负数。
|
|
34
|
+
integer: (match[2] || '0').replace(/^0+(?=\d)/, ''), // 去掉前导零的整数部分。
|
|
35
|
+
fraction: (match[3] || '').replace(/0+$/, ''), // 去掉末尾零的小数部分。
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 格式化金额 value:先四舍五入到分,再添加千分位,统一展示两位小数。
|
|
40
|
+
const formatAmount = (value?: string) => {
|
|
41
|
+
const amount = parseAmount(value) // 解析后的十进制金额,无法识别时为 null。
|
|
42
|
+
if (!amount) return String(value ?? '').trim() || '—'
|
|
43
|
+
|
|
44
|
+
const fraction = amount.fraction.padEnd(3, '0') // 至少保留三位小数,第三位用于判断进位。
|
|
45
|
+
let cents = BigInt(amount.integer + fraction.slice(0, 2)) // 金额绝对值对应的整数分,使用 BigInt 保证精度。
|
|
46
|
+
if (fraction.charAt(2) >= '5') cents += 1n
|
|
47
|
+
|
|
48
|
+
const digits = cents.toString().padStart(3, '0') // 进位后的分字符串,至少三位以保证整数部分存在。
|
|
49
|
+
const integer = digits.slice(0, -2).replace(/\B(?=(\d{3})+(?!\d))/g, ',') // 带千分位的元整数部分。
|
|
50
|
+
const sign = amount.negative ? '-' : '' // 展示用的金额符号。
|
|
51
|
+
return `${sign}${integer}.${digits.slice(-2)}`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 钱包状态展示:接口暂未提供字典,保留原值;有映射时优先展示中文。
|
|
55
|
+
const walletStatusLabels: Record<string, string> = {}
|
|
56
|
+
const formatWalletStatus = (value?: string) => value?.trim() ? (walletStatusLabels[value.trim()] ?? value.trim()) : '—'
|
|
57
|
+
|
|
58
|
+
// 注资/预注资发起弹窗状态。
|
|
59
|
+
const dialogVisible = ref(false) // 发起弹窗是否显示。
|
|
60
|
+
const formError = reactive({ adjustType: '', tranAmt: '' }) // 每个表单项的校验提示,空字符串表示没有错误。
|
|
61
|
+
const createForm = reactive({ adjustType: '', tranAmt: '' }) // 正在编辑的调整类型与调整金额。
|
|
62
|
+
const pendingCreate = ref(false) // 提交是否进行中,避免重复提交。
|
|
63
|
+
|
|
64
|
+
// 调整类型字典:接口已声明枚举与中文含义。
|
|
65
|
+
const adjustTypeOptions = [
|
|
66
|
+
{ value: 'PRFD', label: '注资调增' },
|
|
67
|
+
{ value: 'FDRD', label: '注资调减' },
|
|
68
|
+
{ value: 'FFIC', label: '预注资调增' },
|
|
69
|
+
{ value: 'FFRD', label: '预注资调减' },
|
|
70
|
+
] as const
|
|
71
|
+
|
|
72
|
+
// 打开发起弹窗,恢复默认表单。
|
|
73
|
+
const openCreateDialog = () => {
|
|
74
|
+
createForm.adjustType = ''
|
|
75
|
+
createForm.tranAmt = ''
|
|
76
|
+
formError.adjustType = ''
|
|
77
|
+
formError.tranAmt = ''
|
|
78
|
+
dialogVisible.value = true
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 校验表单;返回是否通过,同时填写各字段错误提示。
|
|
82
|
+
const validateForm = () => {
|
|
83
|
+
const adjustType = createForm.adjustType // 调整类型,字典值必选。
|
|
84
|
+
const amountText = createForm.tranAmt.trim() // 去除首尾空白后的调整金额。
|
|
85
|
+
formError.adjustType = adjustType ? '' : '请选择调整类型'
|
|
86
|
+
formError.tranAmt = /^\d+(?:\.\d+)?$/.test(amountText) && /^[1-9]/.test(amountText)
|
|
87
|
+
? '' // 正数金额:整数或小数,不允许负数、前导零以外形态。
|
|
88
|
+
: '请输入大于 0 的调整金额'
|
|
89
|
+
return !formError.adjustType && !formError.tranAmt
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 提交注资/预注资发起;成功后关闭弹窗,并重新查询账户行余额。
|
|
93
|
+
const submitCreate = async () => {
|
|
94
|
+
if (pendingCreate.value || loading.value) return
|
|
95
|
+
if (!validateForm()) return
|
|
96
|
+
const params: InjectionCreateParams = {
|
|
97
|
+
adjustType: createForm.adjustType as InjectionCreateParams['adjustType'],
|
|
98
|
+
tranAmt: createForm.tranAmt.trim(),
|
|
99
|
+
}
|
|
100
|
+
pendingCreate.value = true
|
|
101
|
+
try {
|
|
102
|
+
// head 为公共响应头,请求是否成功统一以 retCode 判断;接口未声明业务返回字段。
|
|
103
|
+
const { head } = await API.createApi(params) as any
|
|
104
|
+
if (disposed) return
|
|
105
|
+
if (head?.retCode !== '00000') {
|
|
106
|
+
throw new Error(head?.retMsg || '提交失败')
|
|
107
|
+
}
|
|
108
|
+
ElMessage.success('注资/预注资发起已提交')
|
|
109
|
+
dialogVisible.value = false
|
|
110
|
+
void query() // 重新查询账户行余额,展示最新余额。
|
|
111
|
+
} catch (error) { // error 为提交请求或业务处理失败信息。
|
|
112
|
+
if (!disposed) ElMessage.error(getErrorMessage(error))
|
|
113
|
+
} finally {
|
|
114
|
+
pendingCreate.value = false
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 摘要金额:接口字段 → 卡片标题,顺序即展示顺序。
|
|
119
|
+
const summaryAmounts = [
|
|
120
|
+
['bal', '结算钱包余额'],
|
|
121
|
+
['amount', '实时余额'],
|
|
122
|
+
['enableAmount', '其中:可用余额'],
|
|
123
|
+
['yesterdayAmount', '昨日余额'],
|
|
124
|
+
['nonBal', '对立账户余额(预注资金额)'],
|
|
125
|
+
] as const
|
|
126
|
+
|
|
127
|
+
// 明细分组配置;title 为分组标题,fields 每项为 [字段名, 展示名称, 是否金额]。
|
|
128
|
+
const detailGroups = [
|
|
129
|
+
{
|
|
130
|
+
title: '机构与账户',
|
|
131
|
+
fields: [
|
|
132
|
+
['bankName', '机构名称', false],
|
|
133
|
+
['bankCode', '机构编码', false],
|
|
134
|
+
['innerName', '行内名称', false],
|
|
135
|
+
['innerAccount', '行内账号', false],
|
|
136
|
+
],
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
title: '结算钱包',
|
|
140
|
+
fields: [
|
|
141
|
+
['sttlmWlltNm', '结算钱包名称', false],
|
|
142
|
+
['sttlmWlltId', '结算钱包 ID', false],
|
|
143
|
+
['sttlmWlltSts', '结算钱包状态', false],
|
|
144
|
+
['openDate', '开立时间', false],
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
title: '注资参数',
|
|
149
|
+
fields: [
|
|
150
|
+
['ciSts', '注资预注资状态', false],
|
|
151
|
+
['ciLmt', '注资最低金额(元)', true],
|
|
152
|
+
['realTmTxLmt', '实时业务注资下限(元)', true],
|
|
153
|
+
['wltWarnAmt', '钱包预警金额(元)', true],
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
title: '轧差保证金',
|
|
158
|
+
fields: [
|
|
159
|
+
['ntQt', '净额轧差保证金(元)', true],
|
|
160
|
+
['ntQtAmt', '轧差保证金定额(元)', true],
|
|
161
|
+
['ntQtLmt', '净额轧差保证金下限(元)', true],
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
title: '报文信息',
|
|
166
|
+
fields: [
|
|
167
|
+
['refNo', '流水号', false],
|
|
168
|
+
['replyRefNo', '应答流水号', false],
|
|
169
|
+
['ptMsgId', '报文标识号', false],
|
|
170
|
+
['prtryDataType', '报文类型', false],
|
|
171
|
+
['sendOrganizeCode', '发起机构', false],
|
|
172
|
+
['rcvOrganizeCode', '接收机构', false],
|
|
173
|
+
['msgSendTime', '业务处理时间', false],
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
title: '备注',
|
|
178
|
+
fields: [
|
|
179
|
+
['remark', '备注', false],
|
|
180
|
+
['rmk', '交易备注', false],
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
] as const
|
|
184
|
+
|
|
185
|
+
// 展示数据:金额统一千分位、时间本地化,字段名 → 展示值。
|
|
186
|
+
const displayValues = computed<Record<string, string> | null>(() => {
|
|
187
|
+
if (!balance.value) return null
|
|
188
|
+
const raw = balance.value as unknown as Record<string, string | undefined>
|
|
189
|
+
return Object.fromEntries(Object.entries(raw).map(([key, value]) => {
|
|
190
|
+
const isAmount = detailGroups.some(group => group.fields.some(([field, , isMoney]) => field === key && isMoney))
|
|
191
|
+
|| summaryAmounts.some(([field]) => field === key) // 摘要金额同样千分位展示。
|
|
192
|
+
const isTime = key === 'msgSendTime'
|
|
193
|
+
if (key === 'sttlmWlltSts') return [key, formatWalletStatus(value)] // 钱包状态:先按字典映射,缺字典时保留原值。
|
|
194
|
+
return [key, isAmount ? formatAmount(value) : isTime ? formatDateTime(value) : String(value ?? '')]
|
|
195
|
+
}))
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
// 读取展示值,空值显示占位符;field 为字段名。
|
|
199
|
+
const displayValue = (field: string) => displayValues.value?.[field]?.trim() || '—'
|
|
200
|
+
|
|
201
|
+
// 发起查询申请;接口未声明筛选条件,固定空参数。
|
|
202
|
+
const query = async () => {
|
|
203
|
+
if (disposed) return
|
|
204
|
+
const currentRequestId = ++requestId // 本次请求序号,仅与最新序号相等时允许更新界面。
|
|
205
|
+
loading.value = true
|
|
206
|
+
try {
|
|
207
|
+
// head 为公共响应头,body 为单条账户行余额信息。
|
|
208
|
+
const { head, body } = await API.queryBalanceApi() as any
|
|
209
|
+
if (currentRequestId !== requestId) return
|
|
210
|
+
if (head?.retCode !== '00000') {
|
|
211
|
+
throw new Error(head?.retMsg || '查询失败')
|
|
212
|
+
}
|
|
213
|
+
if (!body || typeof body !== 'object') {
|
|
214
|
+
throw new Error('查询结果格式异常')
|
|
215
|
+
}
|
|
216
|
+
balance.value = body
|
|
217
|
+
lastQueryTime.value = formatDateTime(new Date().toISOString())
|
|
218
|
+
} catch (error) { // error 为接口请求或响应数据校验异常。
|
|
219
|
+
if (currentRequestId !== requestId) return
|
|
220
|
+
balance.value = null
|
|
221
|
+
lastQueryTime.value = ''
|
|
222
|
+
ElMessage.error(getErrorMessage(error))
|
|
223
|
+
} finally {
|
|
224
|
+
if (currentRequestId === requestId) loading.value = false
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 页面首次显示时自动发起查询申请。
|
|
229
|
+
onMounted(query)
|
|
230
|
+
// 页面离开后,未完成的请求不再更新界面或弹出消息。
|
|
231
|
+
onBeforeUnmount(() => {
|
|
232
|
+
disposed = true
|
|
233
|
+
requestId += 1
|
|
234
|
+
})
|
|
235
|
+
</script>
|
|
236
|
+
|
|
237
|
+
<template>
|
|
238
|
+
<main class="dcps-page">
|
|
239
|
+
<!-- 标题区:页面名称、最近查询时间、发起入口与查询按钮。 -->
|
|
240
|
+
<el-card class="balance-header" shadow="never" :body-style="{ padding: '10px 14px' }">
|
|
241
|
+
<div class="balance-heading">
|
|
242
|
+
<h1 class="balance-title">数字人民币账户行余额查询申请</h1>
|
|
243
|
+
<span v-if="lastQueryTime" class="balance-updated">最近查询:{{ lastQueryTime }}</span>
|
|
244
|
+
<span class="balance-actions">
|
|
245
|
+
<el-button @click="openCreateDialog">注资/预注资发起</el-button>
|
|
246
|
+
<el-button type="primary" :loading="loading">查询</el-button>
|
|
247
|
+
</span>
|
|
248
|
+
</div>
|
|
249
|
+
</el-card>
|
|
250
|
+
|
|
251
|
+
<el-scrollbar class="balance-scroll">
|
|
252
|
+
<div class="balance-content">
|
|
253
|
+
<!-- 余额摘要:钱包识别信息与关键金额。 -->
|
|
254
|
+
<el-card v-if="balance" class="balance-summary" shadow="never" :body-style="{ padding: '12px 14px' }">
|
|
255
|
+
<header class="wallet-head">
|
|
256
|
+
<div class="wallet-identity">
|
|
257
|
+
<strong>{{ displayValue('sttlmWlltNm') }}</strong>
|
|
258
|
+
<span>{{ displayValue('sttlmWlltId') }} · {{ displayValue('bankName') }}</span>
|
|
259
|
+
</div>
|
|
260
|
+
<el-tag class="wallet-status" type="warning" effect="light">{{ displayValue('sttlmWlltSts') }}</el-tag>
|
|
261
|
+
</header>
|
|
262
|
+
<!-- item 为摘要金额配置,value 为格式化后的展示值。 -->
|
|
263
|
+
<div class="amount-grid">
|
|
264
|
+
<div v-for="[field, label] in summaryAmounts" :key="field" class="amount-card">
|
|
265
|
+
<span class="amount-label">{{ label }}</span>
|
|
266
|
+
<strong class="amount-value">{{ displayValue(field) }}</strong>
|
|
267
|
+
<span class="amount-unit">元</span>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
</el-card>
|
|
271
|
+
|
|
272
|
+
<!-- 明细分组;group 为当前分组,包含标题和字段配置。无数据时展示空状态。 -->
|
|
273
|
+
<el-card class="balance-detail" shadow="never" :body-style="{ padding: '12px 14px' }">
|
|
274
|
+
<template v-if="balance">
|
|
275
|
+
<!-- field 为字段名,isMoney 为金额标记,label 为展示名称。 -->
|
|
276
|
+
<section v-for="group in detailGroups" :key="group.title" class="detail-group" :aria-label="group.title">
|
|
277
|
+
<h3>{{ group.title }}</h3>
|
|
278
|
+
<dl class="detail-fields">
|
|
279
|
+
<div v-for="[field, label] in group.fields" :key="field" class="detail-field">
|
|
280
|
+
<dt>{{ label }}</dt>
|
|
281
|
+
<dd :class="{ 'detail-money': group.fields.some(([f, , isMoney]) => f === field && isMoney) }">
|
|
282
|
+
{{ displayValue(field) }}
|
|
283
|
+
</dd>
|
|
284
|
+
</div>
|
|
285
|
+
</dl>
|
|
286
|
+
</section>
|
|
287
|
+
</template>
|
|
288
|
+
<el-empty v-else-if="!loading" description="暂无账户行余额信息,请点击查询" />
|
|
289
|
+
<div v-else class="balance-loading">正在查询账户行余额…</div>
|
|
290
|
+
</el-card>
|
|
291
|
+
</div>
|
|
292
|
+
</el-scrollbar>
|
|
293
|
+
|
|
294
|
+
<!-- 注资/预注资发起弹窗:提交期间禁止关闭,等待服务端返回结果。 -->
|
|
295
|
+
<el-dialog
|
|
296
|
+
v-model="dialogVisible"
|
|
297
|
+
title="注资/预注资发起"
|
|
298
|
+
width="min(460px, calc(100vw - 32px))"
|
|
299
|
+
append-to-body
|
|
300
|
+
destroy-on-close
|
|
301
|
+
:show-close="!pendingCreate"
|
|
302
|
+
:close-on-click-modal="!pendingCreate"
|
|
303
|
+
:close-on-press-escape="!pendingCreate"
|
|
304
|
+
>
|
|
305
|
+
<el-form :disabled="pendingCreate" label-position="top" @submit.prevent="submitCreate()">
|
|
306
|
+
<!-- value 为接口调整类型枚举,label 为中文含义。 -->
|
|
307
|
+
<el-form-item label="调整类型" :error="formError.adjustType">
|
|
308
|
+
<el-select v-model="createForm.adjustType" placeholder="请选择调整类型" clearable>
|
|
309
|
+
<el-option
|
|
310
|
+
v-for="option in adjustTypeOptions"
|
|
311
|
+
:key="option.value"
|
|
312
|
+
:label="option.label"
|
|
313
|
+
:value="option.value"
|
|
314
|
+
/>
|
|
315
|
+
</el-select>
|
|
316
|
+
</el-form-item>
|
|
317
|
+
<el-form-item label="调整金额(元)" :error="formError.tranAmt">
|
|
318
|
+
<el-input
|
|
319
|
+
v-model="createForm.tranAmt"
|
|
320
|
+
placeholder="输入大于 0 的调整金额"
|
|
321
|
+
maxlength="15"
|
|
322
|
+
clearable
|
|
323
|
+
@input="formError.tranAmt = ''"
|
|
324
|
+
/>
|
|
325
|
+
</el-form-item>
|
|
326
|
+
</el-form>
|
|
327
|
+
<template #footer>
|
|
328
|
+
<el-button :disabled="pendingCreate" @click="dialogVisible = false">取消</el-button>
|
|
329
|
+
<el-button type="primary" :loading="pendingCreate" @click="submitCreate()">提交</el-button>
|
|
330
|
+
</template>
|
|
331
|
+
</el-dialog>
|
|
332
|
+
</main>
|
|
333
|
+
</template>
|
|
334
|
+
|
|
335
|
+
<style scoped>
|
|
336
|
+
/* 页面先扣除宿主占用高度,再使用 flex 分配剩余高度,内容由滚动区承载。 */
|
|
337
|
+
.dcps-page {
|
|
338
|
+
/* 手动修改这里:顶部导航/页签 + 底部状态栏 + 宿主上下留白的合计高度。
|
|
339
|
+
独立页面保持 0px;例如顶部 90px、底部 24px、上下留白 6px,填写 120px。
|
|
340
|
+
不包含浏览器标签栏、地址栏,也不包含本页面自身的 padding。 */
|
|
341
|
+
--host-layout-height: 0px;
|
|
342
|
+
box-sizing: border-box;
|
|
343
|
+
display: flex;
|
|
344
|
+
flex-direction: column;
|
|
345
|
+
gap: 5px;
|
|
346
|
+
height: calc(100vh - var(--host-layout-height));
|
|
347
|
+
height: calc(100dvh - var(--host-layout-height));
|
|
348
|
+
min-height: 0;
|
|
349
|
+
min-width: 0;
|
|
350
|
+
padding: 5px;
|
|
351
|
+
overflow: hidden;
|
|
352
|
+
background: var(--el-bg-color-page, #f7f8fa);
|
|
353
|
+
}
|
|
354
|
+
.balance-header { flex: 0 0 auto; }
|
|
355
|
+
.balance-heading { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 14px; }
|
|
356
|
+
.balance-title { margin: 0; color: var(--el-text-color-primary); font-size: 16px; font-weight: 600; line-height: 22px; }
|
|
357
|
+
.balance-updated { color: var(--el-text-color-secondary); font-size: 12px; }
|
|
358
|
+
.balance-actions { margin-left: auto; display: flex; }
|
|
359
|
+
.balance-actions :deep(.el-button + .el-button) { margin-left: 8px; }
|
|
360
|
+
|
|
361
|
+
.balance-scroll { flex: 1; min-height: 0; min-width: 0; }
|
|
362
|
+
.balance-content { display: flex; flex-direction: column; gap: 5px; padding-bottom: 12px; }
|
|
363
|
+
|
|
364
|
+
/* 摘要卡片:钱包识别信息一行,关键金额等宽排布。 */
|
|
365
|
+
.wallet-head { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
|
|
366
|
+
.wallet-identity strong { display: block; color: var(--el-text-color-primary); font-size: 14px; }
|
|
367
|
+
.wallet-identity span { margin-top: 3px; color: var(--el-text-color-secondary); font-size: 12px; overflow-wrap: anywhere; }
|
|
368
|
+
.wallet-status { margin-left: auto; }
|
|
369
|
+
.amount-grid {
|
|
370
|
+
display: grid;
|
|
371
|
+
grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
372
|
+
gap: 8px;
|
|
373
|
+
}
|
|
374
|
+
.amount-card {
|
|
375
|
+
min-width: 0;
|
|
376
|
+
padding: 10px 12px;
|
|
377
|
+
border: 1px solid var(--el-border-color-lighter);
|
|
378
|
+
border-radius: 8px;
|
|
379
|
+
background: var(--el-fill-color-light);
|
|
380
|
+
}
|
|
381
|
+
.amount-label { display: block; margin-bottom: 6px; color: var(--el-text-color-secondary); font-size: 11px; }
|
|
382
|
+
.amount-value { color: var(--el-color-primary-dark-2); font-size: 18px; font-variant-numeric: tabular-nums; font-weight: 600; overflow-wrap: anywhere; }
|
|
383
|
+
.amount-unit { margin-left: 4px; color: var(--el-text-color-secondary); font-size: 11px; }
|
|
384
|
+
|
|
385
|
+
.balance-detail .detail-group { margin-top: 14px; }
|
|
386
|
+
.balance-detail .detail-group:first-child { margin-top: 0; }
|
|
387
|
+
.balance-detail .detail-group h3 {
|
|
388
|
+
margin: 0 0 8px;
|
|
389
|
+
color: var(--el-text-color-primary);
|
|
390
|
+
font-size: 13px;
|
|
391
|
+
}
|
|
392
|
+
.detail-fields {
|
|
393
|
+
display: grid;
|
|
394
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
395
|
+
margin: 0;
|
|
396
|
+
border: 1px solid var(--el-border-color-lighter);
|
|
397
|
+
border-radius: 8px;
|
|
398
|
+
overflow: hidden;
|
|
399
|
+
}
|
|
400
|
+
.detail-field {
|
|
401
|
+
margin: 0;
|
|
402
|
+
padding: 9px 14px;
|
|
403
|
+
border-right: 1px solid var(--el-border-color-lighter);
|
|
404
|
+
border-bottom: 1px solid var(--el-border-color-lighter);
|
|
405
|
+
}
|
|
406
|
+
.detail-field:nth-child(4n) { border-right: 0; }
|
|
407
|
+
.detail-field dt { margin-bottom: 4px; color: var(--el-text-color-secondary); font-size: 11px; }
|
|
408
|
+
.detail-field dd { margin: 0; color: var(--el-text-color-primary); overflow-wrap: anywhere; font-size: 12px; }
|
|
409
|
+
.detail-money { color: var(--el-color-primary-dark-2); font-variant-numeric: tabular-nums; font-weight: 600; }
|
|
410
|
+
.balance-loading { padding: 40px 0; color: var(--el-text-color-secondary); text-align: center; }
|
|
411
|
+
|
|
412
|
+
@media (max-width: 1200px) {
|
|
413
|
+
.detail-fields { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
414
|
+
.detail-field:nth-child(4n) { border-right: 1px solid var(--el-border-color-lighter); }
|
|
415
|
+
.detail-field:nth-child(3n) { border-right: 0; }
|
|
416
|
+
.amount-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
417
|
+
}
|
|
418
|
+
@media (max-width: 900px) {
|
|
419
|
+
.detail-fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
420
|
+
.detail-field:nth-child(3n) { border-right: 1px solid var(--el-border-color-lighter); }
|
|
421
|
+
.detail-field:nth-child(2n) { border-right: 0; }
|
|
422
|
+
}
|
|
423
|
+
@media (max-width: 560px) {
|
|
424
|
+
.detail-fields { grid-template-columns: minmax(0, 1fr); }
|
|
425
|
+
.detail-field { border-right: 0 !important; }
|
|
426
|
+
.amount-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
427
|
+
}
|
|
428
|
+
</style>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** 接口返回的数字人民币账户行余额信息;单条对象,金额单位为元,保留字符串精度。 */
|
|
2
|
+
export interface AccountBalanceInfo {
|
|
3
|
+
amount?: string // 实时余额。
|
|
4
|
+
bal?: string // 结算钱包余额。
|
|
5
|
+
bankCode?: string // 机构编码。
|
|
6
|
+
bankName?: string // 机构名称。
|
|
7
|
+
ciLmt?: string // 注资最低金额。
|
|
8
|
+
ciSts?: string // 注资预注资状态。
|
|
9
|
+
enableAmount?: string // 其中:可用余额。
|
|
10
|
+
innerAccount?: string // 行内账号。
|
|
11
|
+
innerName?: string // 行内名称。
|
|
12
|
+
msgSendTime?: string // 业务处理时间。
|
|
13
|
+
nonBal?: string // 对立账户余额(预注资金额)。
|
|
14
|
+
ntQt?: string // 净额轧差保证金。
|
|
15
|
+
ntQtAmt?: string // 轧差保证金定额。
|
|
16
|
+
ntQtLmt?: string // 净额轧差保证金下限。
|
|
17
|
+
openDate?: string // 开立时间。
|
|
18
|
+
prtryDataType?: string // 报文类型。
|
|
19
|
+
ptMsgId?: string // 报文标识号。
|
|
20
|
+
rcvOrganizeCode?: string // 接收机构。
|
|
21
|
+
realTmTxLmt?: string // 实时业务注资下限。
|
|
22
|
+
refNo?: string // 流水号。
|
|
23
|
+
remark?: string // 备注。
|
|
24
|
+
replyRefNo?: string // 应答流水号。
|
|
25
|
+
rmk?: string // 交易备注。
|
|
26
|
+
sendOrganizeCode?: string // 发起机构。
|
|
27
|
+
sttlmWlltId?: string // 结算钱包 ID。
|
|
28
|
+
sttlmWlltNm?: string // 结算钱包名称。
|
|
29
|
+
sttlmWlltSts?: string // 结算钱包状态。
|
|
30
|
+
wltWarnAmt?: string // 钱包预警金额。
|
|
31
|
+
yesterdayAmount?: string // 昨日余额。
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 注资/预注资发起提交参数。 */
|
|
35
|
+
export interface InjectionCreateParams {
|
|
36
|
+
adjustType: 'PRFD' | 'FDRD' | 'FFIC' | 'FFRD' // 调整类型:PRFD 注资调增、FDRD 注资调减、FFIC 预注资调增、FFRD 预注资调减。
|
|
37
|
+
tranAmt: string // 调整金额,单位为元,十进制字符串。
|
|
38
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import baseHttpClient from "@chic/http"
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
/**
|
|
5
|
+
* 头寸-数字人民币注资明细查询。
|
|
6
|
+
* @param params 查询参数:capAdjustType 为调整类型,txnProceSts 为交易处理状态;
|
|
7
|
+
* 空值字段不发送,表示不限制该条件。
|
|
8
|
+
* 接口未声明分页请求参数,列表在页面内按返回结果本地分页。
|
|
9
|
+
*/
|
|
10
|
+
queryListApi: (params: unknown) => baseHttpClient.post<any>("/TAM/0001/140504001", params)
|
|
11
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// 父页面传入的弹窗状态、注资明细原始数据和格式化后的展示数据。
|
|
3
|
+
const props = defineProps<{
|
|
4
|
+
// 是否显示详情弹窗。
|
|
5
|
+
modelValue: boolean
|
|
6
|
+
// 原始注资明细数据,未选择时为 null。
|
|
7
|
+
record: any
|
|
8
|
+
// 父页面格式化后的金额和时间,仅用于展示。
|
|
9
|
+
displayValues: any
|
|
10
|
+
}>()
|
|
11
|
+
// 向父页面同步弹窗显隐状态。
|
|
12
|
+
const emit = defineEmits<{
|
|
13
|
+
// visible 为更新后的弹窗显隐状态。
|
|
14
|
+
'update:modelValue': [visible: boolean]
|
|
15
|
+
}>()
|
|
16
|
+
|
|
17
|
+
// 详情分组配置;title 为分组标题,fields 每项为 [明细字段名, 展示名称]。
|
|
18
|
+
const detailGroups = [
|
|
19
|
+
{
|
|
20
|
+
title: '交易信息',
|
|
21
|
+
fields: [
|
|
22
|
+
['txnDt', '交易日期'],
|
|
23
|
+
['txnTm', '交易时间'],
|
|
24
|
+
['refNo', '参考号'],
|
|
25
|
+
['bkkpSeqNo', '记账流水号'],
|
|
26
|
+
['capAdjustType', '调整类型'],
|
|
27
|
+
['ciMode', '注资方式'],
|
|
28
|
+
['txnProceSts', '交易处理状态'],
|
|
29
|
+
['pbocSts', '人行状态'],
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
title: '金额信息',
|
|
34
|
+
fields: [
|
|
35
|
+
['adjustAmt', '调整金额(元)'],
|
|
36
|
+
['ciLowestLimitamt', '注资最低限额(元)'],
|
|
37
|
+
['netAmtNettgGuaranty', '轧差注资定额(元)'],
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
title: '账户信息',
|
|
42
|
+
fields: [
|
|
43
|
+
['payerAcctNo', '付款人账号'],
|
|
44
|
+
['payerName', '付款人名称'],
|
|
45
|
+
['payeeAcctNo', '收款人账号'],
|
|
46
|
+
['payeeAcctName', '收款人账户名称'],
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
title: '报文信息',
|
|
51
|
+
fields: [
|
|
52
|
+
['msgNo', '报文编号'],
|
|
53
|
+
['msgIndNo', '报文标识号'],
|
|
54
|
+
['origMsgCd', '原报文类型'],
|
|
55
|
+
['origMsgIndNo', '原报文标识号'],
|
|
56
|
+
['proceDt', '处理日期'],
|
|
57
|
+
['createTm', '创建时间'],
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
] as const
|
|
61
|
+
|
|
62
|
+
/** 读取详情展示值,空值显示占位符;field 为明细字段名。 */
|
|
63
|
+
const displayValue = (field: string) => props.displayValues?.[field]?.trim() || '—'
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<template>
|
|
67
|
+
<el-dialog
|
|
68
|
+
:model-value="modelValue"
|
|
69
|
+
title="数字人民币注资明细"
|
|
70
|
+
style="--el-dialog-padding-primary: 10px; padding: 10px 16px; margin-bottom: 16px"
|
|
71
|
+
width="min(1180px, calc(100vw - 32px))"
|
|
72
|
+
top="16px"
|
|
73
|
+
append-to-body
|
|
74
|
+
destroy-on-close
|
|
75
|
+
@update:model-value="emit('update:modelValue', $event)"
|
|
76
|
+
>
|
|
77
|
+
<el-scrollbar class="detail-scrollbar">
|
|
78
|
+
<div v-if="record" class="detail-content">
|
|
79
|
+
<!-- 金额摘要 -->
|
|
80
|
+
<header class="detail-summary">
|
|
81
|
+
<div>
|
|
82
|
+
<span class="detail-label">调整金额(元)</span>
|
|
83
|
+
<strong class="detail-amount">{{ displayValue('adjustAmt') }}</strong>
|
|
84
|
+
</div>
|
|
85
|
+
<div>
|
|
86
|
+
<span class="detail-label">交易处理状态</span>
|
|
87
|
+
<strong class="detail-status">{{ displayValue('txnProceSts') }}</strong>
|
|
88
|
+
</div>
|
|
89
|
+
<div class="detail-reference">
|
|
90
|
+
<span class="detail-label">记账流水号</span>
|
|
91
|
+
<strong>{{ displayValue('bkkpSeqNo') }}</strong>
|
|
92
|
+
</div>
|
|
93
|
+
</header>
|
|
94
|
+
|
|
95
|
+
<!-- group 为当前详情分组,包含标题和字段配置。 -->
|
|
96
|
+
<section v-for="group in detailGroups" :key="group.title" class="detail-group" :aria-label="group.title">
|
|
97
|
+
<h3>{{ group.title }}</h3>
|
|
98
|
+
<dl class="detail-fields">
|
|
99
|
+
<!-- field 为明细字段名,label 为字段的中文展示名称。 -->
|
|
100
|
+
<div
|
|
101
|
+
v-for="[field, label] in group.fields"
|
|
102
|
+
:key="field"
|
|
103
|
+
class="detail-field"
|
|
104
|
+
>
|
|
105
|
+
<dt>{{ label }}</dt>
|
|
106
|
+
<dd>{{ displayValue(field) }}</dd>
|
|
107
|
+
</div>
|
|
108
|
+
</dl>
|
|
109
|
+
</section>
|
|
110
|
+
</div>
|
|
111
|
+
</el-scrollbar>
|
|
112
|
+
<template #footer>
|
|
113
|
+
<el-button @click="emit('update:modelValue', false)">关闭</el-button>
|
|
114
|
+
</template>
|
|
115
|
+
</el-dialog>
|
|
116
|
+
</template>
|
|
117
|
+
|
|
118
|
+
<style scoped>
|
|
119
|
+
.detail-scrollbar { max-height: calc(100vh - 240px); }
|
|
120
|
+
.detail-content { padding: 2px 4px 8px; }
|
|
121
|
+
|
|
122
|
+
.detail-summary {
|
|
123
|
+
display: flex;
|
|
124
|
+
flex-wrap: wrap;
|
|
125
|
+
gap: 12px 36px;
|
|
126
|
+
align-items: center;
|
|
127
|
+
padding: 12px 14px;
|
|
128
|
+
border-radius: 10px;
|
|
129
|
+
background: var(--el-fill-color-light);
|
|
130
|
+
}
|
|
131
|
+
.detail-label { margin-bottom: 5px; color: var(--el-text-color-secondary); font-size: 11px; display: block; }
|
|
132
|
+
.detail-summary strong { overflow-wrap: anywhere; font-size: 13px; }
|
|
133
|
+
.detail-amount { color: var(--el-color-primary-dark-2); font-variant-numeric: tabular-nums; }
|
|
134
|
+
.detail-reference { margin-left: auto; }
|
|
135
|
+
.detail-reference strong { font-weight: 600; }
|
|
136
|
+
|
|
137
|
+
.detail-group { margin-top: 16px; }
|
|
138
|
+
.detail-group h3 { margin: 0 0 10px; color: var(--el-text-color-primary); font-size: 13px; }
|
|
139
|
+
.detail-fields {
|
|
140
|
+
display: grid;
|
|
141
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
142
|
+
margin: 0;
|
|
143
|
+
border: 1px solid var(--el-border-color-lighter);
|
|
144
|
+
border-radius: 8px;
|
|
145
|
+
overflow: hidden;
|
|
146
|
+
}
|
|
147
|
+
.detail-field {
|
|
148
|
+
margin: 0;
|
|
149
|
+
padding: 10px 14px;
|
|
150
|
+
border-right: 1px solid var(--el-border-color-lighter);
|
|
151
|
+
border-bottom: 1px solid var(--el-border-color-lighter);
|
|
152
|
+
}
|
|
153
|
+
.detail-field:nth-child(4n) { border-right: 0; }
|
|
154
|
+
.detail-field dt { margin-bottom: 4px; color: var(--el-text-color-secondary); font-size: 11px; }
|
|
155
|
+
.detail-field dd { margin: 0; color: var(--el-text-color-primary); overflow-wrap: anywhere; font-size: 12px; }
|
|
156
|
+
|
|
157
|
+
@media (max-width: 1200px) {
|
|
158
|
+
.detail-fields { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
159
|
+
.detail-field:nth-child(4n) { border-right: 1px solid var(--el-border-color-lighter); }
|
|
160
|
+
.detail-field:nth-child(3n) { border-right: 0; }
|
|
161
|
+
}
|
|
162
|
+
@media (max-width: 900px) {
|
|
163
|
+
.detail-fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
164
|
+
.detail-field:nth-child(3n) { border-right: 1px solid var(--el-border-color-lighter); }
|
|
165
|
+
.detail-field:nth-child(2n) { border-right: 0; }
|
|
166
|
+
}
|
|
167
|
+
@media (max-width: 560px) {
|
|
168
|
+
.detail-fields { grid-template-columns: minmax(0, 1fr); }
|
|
169
|
+
.detail-field { border-right: 0 !important; }
|
|
170
|
+
}
|
|
171
|
+
</style>
|