minitest2.0 0.0.3 → 0.0.5

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.
@@ -1,26 +1,516 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ buildNetDebitQueryPayload,
4
+ buildNetDebitSendPayload,
5
+ queryNetDebitQuota,
6
+ sendNetDebitRequest,
7
+ type NetDebitQuotaDetail,
8
+ } from '@/api/net-debit'
9
+ import { showAppToast } from '@/utils/toast'
10
+
11
+ defineOptions({ name: 'NetDebitPage' })
12
+
13
+ type QuotaMetric = {
14
+ key: string
15
+ label: string
16
+ value: string
17
+ icon: string
18
+ primary?: boolean
19
+ }
20
+
21
+ type QuotaItem = {
22
+ label: string
23
+ value: string
24
+ icon: string
25
+ }
26
+
27
+ type QuotaSection = {
28
+ key: string
29
+ title: string
30
+ items: QuotaItem[]
31
+ }
32
+
33
+ const quotaDetail = ref<NetDebitQuotaDetail | null>(null)
34
+ const sending = ref(false)
35
+ const querying = ref(false)
36
+
37
+ const actionDisabled = computed(() => sending.value || querying.value)
38
+ const requestRefNo = computed(() => quotaDetail.value?.requestRefNo || quotaDetail.value?.refNo || '')
39
+
40
+ const quotaMetrics = computed<QuotaMetric[]>(() => {
41
+ const detail = quotaDetail.value
42
+
43
+ return [
44
+ {
45
+ key: 'currentAvailableDrAmt',
46
+ label: '可用净借记限额(亿元)',
47
+ value: formatYuanToBillionText(detail?.currentAvailableDrAmt),
48
+ icon: 'balance-o',
49
+ primary: true,
50
+ },
51
+ {
52
+ key: 'availableDrAmt',
53
+ label: '净借记限额(亿元)',
54
+ value: formatYuanToBillionText(detail?.availableDrAmt),
55
+ icon: 'gold-coin-o',
56
+ },
57
+ {
58
+ key: 'creditAmt',
59
+ label: '授信额度(亿元)',
60
+ value: formatYuanToBillionText(detail?.creditAmt),
61
+ icon: 'card',
62
+ },
63
+ ]
64
+ })
65
+
66
+ const quotaSections = computed<QuotaSection[]>(() => {
67
+ const detail = quotaDetail.value
68
+
69
+ return [
70
+ {
71
+ key: 'quota',
72
+ title: '额度信息',
73
+ items: [
74
+ {
75
+ label: '质押额度(亿元)',
76
+ value: formatYuanToBillionText(detail?.impawnLimit),
77
+ icon: 'paid',
78
+ },
79
+ {
80
+ label: '圈存资金(亿元)',
81
+ value: formatYuanToBillionText(detail?.encloseAmt),
82
+ icon: 'balance-list-o',
83
+ },
84
+ ],
85
+ },
86
+ {
87
+ key: 'status',
88
+ title: '处理状态',
89
+ items: [
90
+ { label: '查询处理状态', value: fieldText(detail?.busiStatus), icon: 'passed' },
91
+ { label: '业务处理状态', value: fieldText(detail?.status), icon: 'notes-o' },
92
+ { label: '委托日期', value: formatDate(detail?.consignDate), icon: 'clock' },
93
+ { label: '应答委托日期', value: formatDate(detail?.responseDate), icon: 'clock-o' },
94
+ ],
95
+ },
96
+ {
97
+ key: 'message',
98
+ title: '报文信息',
99
+ items: [
100
+ {
101
+ label: '平台流水号',
102
+ value: fieldText(detail?.refNo || detail?.requestRefNo),
103
+ icon: 'description',
104
+ },
105
+ { label: '报文标识号', value: fieldText(detail?.msgRefNo), icon: 'cluster' },
106
+ { label: '应答报文序号', value: fieldText(detail?.responseNo), icon: 'orders-o' },
107
+ { label: '被查询行行号', value: fieldText(detail?.rcvBankCode), icon: 'hotel-o' },
108
+ { label: '被查询清算行行号', value: fieldText(detail?.settleBankCode), icon: 'hotel-o' },
109
+ ],
110
+ },
111
+ ]
112
+ })
113
+
114
+ async function handleSend() {
115
+ if (actionDisabled.value) return
116
+
117
+ sending.value = true
118
+ const payload = buildNetDebitSendPayload()
119
+ console.log('[净借记可用额度发送入参]', payload)
120
+
121
+ try {
122
+ const response = await sendNetDebitRequest(payload)
123
+ const refNo = response.body.refNo || ''
124
+
125
+ quotaDetail.value = {
126
+ requestRefNo: refNo,
127
+ refNo,
128
+ }
129
+ showAppToast(refNo ? `发送成功:${refNo}` : '发送成功')
130
+ } catch (error) {
131
+ console.error('[净借记可用额度发送失败]', error)
132
+ showAppToast(getErrorMessage(error, '发送失败,请重试'))
133
+ } finally {
134
+ sending.value = false
135
+ }
136
+ }
137
+
138
+ async function handleQuery() {
139
+ if (actionDisabled.value) return
140
+
141
+ const msgId = requestRefNo.value
142
+ if (!msgId) {
143
+ showDialog({
144
+ title: '提示',
145
+ message: '请先点击发送获取编号',
146
+ confirmButtonText: '知道了',
147
+ })
148
+ return
149
+ }
150
+
151
+ querying.value = true
152
+ const payload = buildNetDebitQueryPayload(msgId)
153
+ console.log('[净借记可用额度查询入参]', payload)
154
+
155
+ try {
156
+ const response = await queryNetDebitQuota(payload)
157
+
158
+ quotaDetail.value = {
159
+ ...response.body,
160
+ requestRefNo: msgId,
161
+ refNo: response.body.refNo || msgId,
162
+ }
163
+ showAppToast('查询完成')
164
+ } catch (error) {
165
+ console.error('[净借记可用额度查询失败]', error)
166
+ showAppToast(getErrorMessage(error, '查询失败,请重试'))
167
+ } finally {
168
+ querying.value = false
169
+ }
170
+ }
171
+
172
+ function fieldText(value?: string) {
173
+ const text = value?.trim()
174
+
175
+ return text || '-'
176
+ }
177
+
178
+ function formatDate(value?: string) {
179
+ const text = value?.trim()
180
+
181
+ if (!text) return '-'
182
+
183
+ const compactDate = text.match(/^(\d{4})(\d{2})(\d{2})$/)
184
+ if (compactDate) {
185
+ return `${compactDate[1]}-${compactDate[2]}-${compactDate[3]}`
186
+ }
187
+
188
+ return text
189
+ }
190
+
191
+ function formatYuanToBillionText(value?: string) {
192
+ if (!value?.trim()) return '-'
193
+
194
+ const amount = Number(value.replaceAll(',', ''))
195
+ if (!Number.isFinite(amount)) return '-'
196
+
197
+ return new Intl.NumberFormat('zh-CN', {
198
+ minimumFractionDigits: 2,
199
+ maximumFractionDigits: 2,
200
+ }).format(amount / 100000000)
201
+ }
202
+
203
+ function getErrorMessage(error: unknown, fallback: string) {
204
+ return error instanceof Error && error.message ? error.message : fallback
205
+ }
206
+ </script>
207
+
1
208
  <template>
2
- <main class="page">
3
- <section class="page-body">
4
- <p class="placeholder">页面建设中…</p>
209
+ <main class="net-debit-page">
210
+ <section class="summary-strip" aria-label="净借记额度汇总">
211
+ <article
212
+ v-for="metric in quotaMetrics"
213
+ :key="metric.key"
214
+ class="quota-metric"
215
+ :class="{ primary: metric.primary }"
216
+ >
217
+ <span class="metric-icon" aria-hidden="true"><van-icon :name="metric.icon" /></span>
218
+ <span class="metric-label">{{ metric.label }}</span>
219
+ <strong>{{ metric.value }}</strong>
220
+ </article>
221
+ </section>
222
+
223
+ <nav class="action-bar" :class="{ 'single-action': !requestRefNo }" aria-label="净借记额度操作">
224
+ <button
225
+ class="action-btn send-btn"
226
+ type="button"
227
+ :disabled="actionDisabled"
228
+ @click="handleSend"
229
+ >
230
+ <van-icon name="description" />
231
+ <span>{{ sending ? '发送中' : '发送' }}</span>
232
+ </button>
233
+ <button
234
+ v-if="requestRefNo"
235
+ class="action-btn query-btn"
236
+ type="button"
237
+ :disabled="actionDisabled"
238
+ @click="handleQuery"
239
+ >
240
+ <van-icon name="search" />
241
+ <span>{{ querying ? '查询中' : '查询' }}</span>
242
+ </button>
243
+ </nav>
244
+
245
+ <section class="detail-panel" aria-label="净借记额度明细">
246
+ <section v-for="section in quotaSections" :key="section.key" class="detail-section">
247
+ <header class="section-title">
248
+ <span class="section-title-mark" aria-hidden="true"></span>
249
+ <h2>{{ section.title }}</h2>
250
+ </header>
251
+
252
+ <dl class="detail-list">
253
+ <div v-for="item in section.items" :key="`${section.key}-${item.label}`" class="detail-row">
254
+ <dt>
255
+ <span class="field-icon" aria-hidden="true">
256
+ <van-icon :name="item.icon" />
257
+ </span>
258
+ <span class="field-label">{{ item.label }}</span>
259
+ </dt>
260
+ <dd>{{ item.value }}</dd>
261
+ </div>
262
+ </dl>
263
+ </section>
5
264
  </section>
6
265
  </main>
7
266
  </template>
8
267
 
9
268
  <style scoped>
10
- .page {
269
+ .net-debit-page {
270
+ width: 100%;
11
271
  max-width: 750px;
12
272
  min-height: var(--app-page-min-height, 100vh);
13
273
  margin: 0 auto;
14
- padding: 0 11px;
15
- background: #f7f8fa;
274
+ padding: 13px 13px calc(env(safe-area-inset-bottom, 0px) + 16px);
275
+ color: #171b20;
276
+ background: linear-gradient(180deg, #ffffff 0%, #f8f9fb 48%, #ffffff 100%);
277
+ box-sizing: border-box;
16
278
  }
17
- .page-body {
18
- padding: 24px 0;
279
+
280
+ button {
281
+ border: 0;
282
+ padding: 0;
283
+ font: inherit;
284
+ background: transparent;
285
+ appearance: none;
286
+ }
287
+
288
+ .summary-strip {
289
+ display: grid;
290
+ grid-template-columns: repeat(2, minmax(0, 1fr));
291
+ gap: 8px;
292
+ }
293
+
294
+ .quota-metric {
295
+ display: grid;
296
+ grid-template-rows: 28px auto minmax(24px, auto);
297
+ min-width: 0;
298
+ min-height: 104px;
299
+ padding: 12px 10px 11px;
300
+ border: 1px solid #edf0f4;
301
+ border-radius: 8px;
302
+ background: #ffffff;
303
+ box-shadow: 0 8px 22px rgba(34, 40, 56, 0.05);
304
+ box-sizing: border-box;
305
+ }
306
+
307
+ .quota-metric.primary {
308
+ grid-column: 1 / -1;
309
+ min-height: 112px;
310
+ border-color: #ffdcdf;
311
+ background: linear-gradient(135deg, #ffffff 0%, #fff5f6 100%);
312
+ }
313
+
314
+ .metric-icon {
315
+ display: inline-flex;
316
+ align-items: center;
317
+ justify-content: center;
318
+ width: 28px;
319
+ height: 28px;
320
+ border-radius: 8px;
321
+ color: #ef252d;
322
+ font-size: 17px;
323
+ background: #fff0f2;
19
324
  }
20
- .placeholder {
21
- text-align: center;
22
- color: #969799;
325
+
326
+ .metric-label {
327
+ min-width: 0;
328
+ margin-top: 9px;
329
+ color: #727a88;
330
+ font-size: 12px;
331
+ font-weight: 600;
332
+ line-height: 1.25;
333
+ }
334
+
335
+ .quota-metric strong {
336
+ min-width: 0;
337
+ margin-top: 6px;
338
+ color: #171b20;
339
+ font-size: 16px;
340
+ font-weight: 760;
341
+ line-height: 1.18;
342
+ overflow-wrap: anywhere;
343
+ }
344
+
345
+ .quota-metric.primary strong {
346
+ color: #c91b20;
347
+ font-size: 30px;
348
+ line-height: 1.08;
349
+ }
350
+
351
+ .detail-panel {
352
+ margin-top: 12px;
353
+ }
354
+
355
+ .detail-section {
356
+ overflow: hidden;
357
+ border: 1px solid #edf0f4;
358
+ border-radius: 8px;
359
+ background: #ffffff;
360
+ box-shadow: 0 8px 20px rgba(30, 36, 52, 0.05);
361
+ }
362
+
363
+ .detail-section + .detail-section {
364
+ margin-top: 10px;
365
+ }
366
+
367
+ .section-title {
368
+ display: flex;
369
+ align-items: center;
370
+ gap: 8px;
371
+ min-height: 40px;
372
+ padding: 0 13px;
373
+ border-bottom: 1px solid #edf0f2;
374
+ }
375
+
376
+ .section-title-mark {
377
+ width: 5px;
378
+ height: 16px;
379
+ border-radius: 8px;
380
+ background: #f22f3a;
381
+ }
382
+
383
+ .section-title h2 {
384
+ margin: 0;
385
+ color: #171b20;
386
+ font-size: 15px;
387
+ font-weight: 720;
388
+ line-height: 1;
389
+ }
390
+
391
+ .detail-list {
392
+ margin: 0;
393
+ padding: 4px 13px;
394
+ }
395
+
396
+ .detail-row {
397
+ display: grid;
398
+ grid-template-columns: minmax(0, 1fr) minmax(92px, auto);
399
+ align-items: center;
400
+ min-height: 42px;
401
+ column-gap: 10px;
402
+ }
403
+
404
+ .detail-row + .detail-row {
405
+ border-top: 1px solid #f1f2f4;
406
+ }
407
+
408
+ .detail-row dt {
409
+ display: flex;
410
+ align-items: center;
411
+ min-width: 0;
412
+ margin: 0;
413
+ }
414
+
415
+ .field-icon {
416
+ display: inline-flex;
417
+ align-items: center;
418
+ justify-content: center;
419
+ width: 28px;
420
+ height: 28px;
421
+ flex: 0 0 28px;
422
+ border-radius: 8px;
423
+ color: #ef252d;
424
+ font-size: 17px;
425
+ background: #fff0f2;
426
+ }
427
+
428
+ .field-label {
429
+ min-width: 0;
430
+ margin-left: 10px;
431
+ color: #202532;
23
432
  font-size: 14px;
24
- padding-top: 120px;
433
+ font-weight: 560;
434
+ line-height: 1.25;
435
+ overflow-wrap: anywhere;
436
+ }
437
+
438
+ .detail-row dd {
439
+ min-width: 0;
440
+ margin: 0;
441
+ color: #202532;
442
+ font-size: 14px;
443
+ font-weight: 580;
444
+ line-height: 1.25;
445
+ text-align: right;
446
+ overflow-wrap: anywhere;
447
+ }
448
+
449
+ .action-bar {
450
+ display: grid;
451
+ grid-template-columns: 1fr 1fr;
452
+ gap: 13px;
453
+ margin-top: 10px;
454
+ }
455
+
456
+ .action-bar.single-action {
457
+ grid-template-columns: 1fr;
458
+ }
459
+
460
+ .action-btn {
461
+ display: inline-flex;
462
+ align-items: center;
463
+ justify-content: center;
464
+ gap: 7px;
465
+ height: 44px;
466
+ border-radius: 22px;
467
+ font-size: 18px;
468
+ font-weight: 650;
469
+ line-height: 1;
470
+ }
471
+
472
+ .action-btn .van-icon {
473
+ font-size: 18px;
474
+ }
475
+
476
+ .action-btn:disabled {
477
+ cursor: not-allowed;
478
+ opacity: 0.68;
479
+ }
480
+
481
+ .send-btn {
482
+ color: #ffffff;
483
+ background: linear-gradient(135deg, #ff3338 0%, #ff232b 100%);
484
+ box-shadow:
485
+ 0 12px 22px rgba(255, 44, 50, 0.24),
486
+ inset 0 1px 0 rgba(255, 255, 255, 0.22);
487
+ }
488
+
489
+ .query-btn {
490
+ border: 1px solid #ff3038;
491
+ color: #ef252d;
492
+ background: rgba(255, 255, 255, 0.98);
493
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9);
494
+ }
495
+
496
+ @media (max-width: 350px) {
497
+ .quota-metric {
498
+ padding-right: 8px;
499
+ padding-left: 8px;
500
+ }
501
+
502
+ .quota-metric.primary strong {
503
+ font-size: 26px;
504
+ }
505
+
506
+ .quota-metric strong,
507
+ .field-label,
508
+ .detail-row dd {
509
+ font-size: 13px;
510
+ }
511
+
512
+ .detail-row {
513
+ grid-template-columns: minmax(0, 1fr) minmax(76px, auto);
514
+ }
25
515
  }
26
516
  </style>