minitest2.0 0.0.6 → 0.0.8

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.
Files changed (32) hide show
  1. package/package.json +1 -1
  2. package/public/images/dashboard/01-rmb-great-wall-bg.jpg +0 -0
  3. package/public/images/dashboard/02-city-clearing-skyline-bg.jpg +0 -0
  4. package/public/images/dashboard/03-cross-border-rmb-world-map-bg.jpg +0 -0
  5. package/public/images/dashboard/04-digital-currency-fintech-bg.jpg +0 -0
  6. package/public/images/dashboard/05-usd-liberty-bg.jpg +0 -0
  7. package/public/images/dashboard/06-eur-europe-architecture-bg.jpg +0 -0
  8. package/public/images/dashboard/07-hkd-hong-kong-skyline-bg.jpg +0 -0
  9. package/public/images/dashboard/08-jpy-mount-fuji-bg.jpg +0 -0
  10. package/src/api/dashboard.ts +44 -0
  11. package/src/api/foreign-position.ts +71 -0
  12. package/src/router/index.ts +29 -17
  13. package/src/utils/foreign-forecast.ts +37 -5
  14. package/src/views/{article-detail → announcement/detail}/index.vue +35 -1
  15. package/src/views/dashboard/index.vue +535 -2
  16. package/src/views/foreign/position/create/index.vue +363 -0
  17. package/src/views/foreign/position/detail/index.vue +390 -0
  18. package/src/views/foreign/position/index.vue +1223 -0
  19. package/src/views/foreign-position/index.vue +0 -26
  20. /package/src/views/{article-edit → announcement/edit}/index.vue +0 -0
  21. /package/src/views/{articles → announcement}/index.vue +0 -0
  22. /package/src/views/{settings → mine/settings}/index.vue +0 -0
  23. /package/src/views/{clearing-detail → rmb/clearing-detail}/index.vue +0 -0
  24. /package/src/views/{net-debit → rmb/net-debit}/index.vue +0 -0
  25. /package/src/views/{pbc-position → rmb/pbc-position}/index.vue +0 -0
  26. /package/src/views/{rmb-position-create → rmb/position/create}/index.vue +0 -0
  27. /package/src/views/{rmb-position-detail → rmb/position/detail}/index.vue +0 -0
  28. /package/src/views/{rmb-position → rmb/position}/index.vue +0 -0
  29. /package/src/views/{position-estimate → rmb/position-estimate}/index.vue +0 -0
  30. /package/src/views/{opening-reserve-estimate → rmb/position-estimate/opening-reserve}/index.vue +0 -0
  31. /package/src/views/{position-estimate-report → rmb/position-estimate/report}/index.vue +0 -0
  32. /package/src/views/{warning → rmb/warning}/index.vue +0 -0
@@ -0,0 +1,390 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ buildForeignForecastDetailPayload,
4
+ queryForeignForecastDetail,
5
+ type ForeignForecastDetailRecord,
6
+ } from '@/api/foreign-position'
7
+ import {
8
+ formatForeignForecastAmountText,
9
+ formatForeignForecastAmountYuanText,
10
+ formatForeignForecastDate,
11
+ resolveForeignBusinessType,
12
+ resolveForeignCurrency,
13
+ resolveForeignStatus,
14
+ } from '@/utils/foreign-forecast'
15
+ import { showAppToast } from '@/utils/toast'
16
+
17
+ defineOptions({ name: 'ForeignPositionDetailPage' })
18
+
19
+ type DetailField = {
20
+ label: string
21
+ value: string
22
+ }
23
+
24
+ type DetailSection = {
25
+ title: string
26
+ fields: DetailField[]
27
+ }
28
+
29
+ const route = useRoute()
30
+ const router = useRouter()
31
+
32
+ const predRefNo = computed(() => String(route.params.predRefNo || ''))
33
+ const detail = ref<ForeignForecastDetailRecord>()
34
+ const loading = ref(false)
35
+ const loadError = ref(false)
36
+ const currencyValue = computed(() => getRecordCurrency(detail.value))
37
+ const currencyOption = computed(() => resolveForeignCurrency(currencyValue.value))
38
+ const statusOption = computed(() => resolveForeignStatus(detail.value?.predStatus))
39
+ const heroAmount = computed(() =>
40
+ formatForeignForecastAmountText(detail.value?.predAmt, currencyValue.value),
41
+ )
42
+ const heroAmountYuan = computed(() => formatForeignForecastAmountYuanText(detail.value?.predAmt))
43
+ const heroDate = computed(() => formatForeignForecastDate(detail.value?.tranDate))
44
+
45
+ const detailSections = computed<DetailSection[]>(() => {
46
+ const item = detail.value
47
+
48
+ if (!item) return []
49
+
50
+ return [
51
+ {
52
+ title: '基础信息',
53
+ fields: [
54
+ { label: '预报流水号', value: fieldText(item.predRefNo) },
55
+ { label: '交易日期', value: formatForeignForecastDate(item.tranDate) },
56
+ { label: '预报日期', value: formatForeignForecastDate(item.predDate) },
57
+ { label: '预报状态', value: statusOption.value.label },
58
+ { label: '核销状态', value: fieldText(item.verifyStatus) },
59
+ { label: '额度标志', value: fieldText(item.eduFlag) },
60
+ ],
61
+ },
62
+ {
63
+ title: '机构与人员',
64
+ fields: [
65
+ { label: '预报机构', value: fieldText(item.predBranchName || item.predBranch) },
66
+ { label: '预报机构代码', value: fieldText(item.predBranch) },
67
+ { label: '预报人员编号', value: fieldText(item.predCode) },
68
+ { label: '预报人员姓名', value: fieldText(item.predName) },
69
+ { label: '落地机构名称', value: fieldText(item.groundBranch) },
70
+ { label: '落地机构代码', value: fieldText(item.groundBranchCode) },
71
+ ],
72
+ },
73
+ {
74
+ title: '业务与币种',
75
+ fields: [
76
+ { label: '币种', value: currencyOption.value.label },
77
+ { label: '业务类型', value: resolveForeignBusinessType(item.busiType) },
78
+ { label: '客户名称', value: fieldText(item.clientName) },
79
+ { label: '交易对手名称', value: fieldText(item.competName) },
80
+ { label: '交易对手账号', value: fieldText(item.competAcct) },
81
+ {
82
+ label: '清算行 SWIFT',
83
+ value: fieldText(item.bankSwift || item.swiftCode || item.bicCode),
84
+ },
85
+ { label: '标准名称', value: fieldText(item.standardName) },
86
+ { label: '用途', value: fieldText(item.purpose) },
87
+ { label: '备注', value: fieldText(item.remark) },
88
+ ],
89
+ },
90
+ {
91
+ title: '支付与金额',
92
+ fields: [
93
+ {
94
+ label: '预报金额',
95
+ value: formatForeignForecastAmountText(item.predAmt, currencyValue.value),
96
+ },
97
+ {
98
+ label: '已核销金额',
99
+ value: formatForeignForecastAmountText(item.verifyAmt, currencyValue.value),
100
+ },
101
+ { label: '支付渠道', value: fieldText(item.payChannel) },
102
+ { label: '收支方向', value: fieldText(item.payDirec) },
103
+ ],
104
+ },
105
+ {
106
+ title: '票据与联系',
107
+ fields: [
108
+ { label: '出票日', value: formatForeignForecastDate(item.billDate) },
109
+ { label: '票号', value: fieldText(item.billNo) },
110
+ { label: '联系人', value: fieldText(item.linkMan) },
111
+ { label: '联系电话', value: fieldText(item.phoneNo) },
112
+ ],
113
+ },
114
+ ]
115
+ })
116
+
117
+ onMounted(() => {
118
+ loadDetail()
119
+ })
120
+
121
+ async function loadDetail() {
122
+ if (!predRefNo.value) {
123
+ loadError.value = true
124
+ return
125
+ }
126
+
127
+ loading.value = true
128
+ loadError.value = false
129
+
130
+ const payload = buildForeignForecastDetailPayload(predRefNo.value)
131
+ console.log('[外币预报详情入参]', payload)
132
+
133
+ try {
134
+ const response = await queryForeignForecastDetail(payload)
135
+ detail.value = response.body
136
+ } catch (error) {
137
+ console.error('[外币预报详情加载失败]', error)
138
+ loadError.value = true
139
+ showAppToast('详情加载失败,请重试')
140
+ } finally {
141
+ loading.value = false
142
+ }
143
+ }
144
+
145
+ function getRecordCurrency(record?: ForeignForecastDetailRecord) {
146
+ return (
147
+ record?.ccy ||
148
+ record?.currency ||
149
+ record?.currencyCode ||
150
+ record?.currCode ||
151
+ record?.curType ||
152
+ record?.tranCcy ||
153
+ ''
154
+ )
155
+ .trim()
156
+ .toUpperCase()
157
+ }
158
+
159
+ function fieldText(value?: string) {
160
+ const text = value?.trim()
161
+ return text || '-'
162
+ }
163
+ </script>
164
+
165
+ <template>
166
+ <main class="detail-page">
167
+ <section v-if="loading" class="state-section">
168
+ <van-loading vertical>加载中...</van-loading>
169
+ </section>
170
+
171
+ <template v-else-if="detail">
172
+ <section class="hero-card" aria-label="外币预报详情概要">
173
+ <div class="hero-topline">
174
+ <span class="status-tag" :class="statusOption.className">{{ statusOption.label }}</span>
175
+ <span class="hero-date">{{ heroDate }}</span>
176
+ </div>
177
+ <strong>{{ heroAmount }}</strong>
178
+ <span v-if="heroAmountYuan !== '-'" class="hero-amount-yuan">{{ heroAmountYuan }}</span>
179
+ <p>{{ detail.predRefNo || predRefNo }}</p>
180
+ </section>
181
+
182
+ <section
183
+ v-for="section in detailSections"
184
+ :key="section.title"
185
+ class="detail-card"
186
+ :aria-label="section.title"
187
+ >
188
+ <h2>{{ section.title }}</h2>
189
+ <dl class="detail-list">
190
+ <div v-for="field in section.fields" :key="`${section.title}-${field.label}`">
191
+ <dt>{{ field.label }}</dt>
192
+ <dd>{{ field.value }}</dd>
193
+ </div>
194
+ </dl>
195
+ </section>
196
+ </template>
197
+
198
+ <van-empty v-else image="error" :description="loadError ? '详情加载失败' : '详情不存在'">
199
+ <van-button v-if="loadError" round type="danger" @click="loadDetail">重试</van-button>
200
+ <van-button v-else round type="danger" @click="router.replace('/foreign-position')">
201
+ 返回列表
202
+ </van-button>
203
+ </van-empty>
204
+ </main>
205
+ </template>
206
+
207
+ <style scoped>
208
+ .detail-page {
209
+ width: 100%;
210
+ max-width: 750px;
211
+ min-height: var(--app-page-min-height, 100vh);
212
+ margin: 0 auto;
213
+ padding: 13px 13px calc(env(safe-area-inset-bottom, 0px) + 24px);
214
+ color: #080d1f;
215
+ background:
216
+ radial-gradient(circle at 50% 0%, rgba(255, 242, 243, 0.92), transparent 118px),
217
+ linear-gradient(180deg, #ffffff 0%, #fbfcfd 54%, #ffffff 100%);
218
+ box-sizing: border-box;
219
+ }
220
+
221
+ button {
222
+ border: 0;
223
+ padding: 0;
224
+ background: transparent;
225
+ appearance: none;
226
+ }
227
+
228
+ .state-section {
229
+ display: flex;
230
+ justify-content: center;
231
+ padding-top: 120px;
232
+ }
233
+
234
+ .hero-card {
235
+ margin-top: 15px;
236
+ border-radius: 13px;
237
+ padding: 16px;
238
+ color: #ffffff;
239
+ background: linear-gradient(135deg, #f22f3d 0%, #ff5660 100%);
240
+ box-shadow: 0 13px 28px rgba(244, 29, 48, 0.18);
241
+ }
242
+
243
+ .hero-topline {
244
+ display: flex;
245
+ align-items: center;
246
+ justify-content: space-between;
247
+ gap: 10px;
248
+ }
249
+
250
+ .hero-card strong {
251
+ display: block;
252
+ margin-top: 18px;
253
+ font-size: 27px;
254
+ font-weight: 800;
255
+ line-height: 1.1;
256
+ }
257
+
258
+ .hero-amount-yuan {
259
+ display: block;
260
+ margin-top: 7px;
261
+ overflow-wrap: anywhere;
262
+ color: rgba(255, 255, 255, 0.74);
263
+ font-size: 12px;
264
+ font-weight: 500;
265
+ line-height: 1.35;
266
+ }
267
+
268
+ .hero-card p {
269
+ overflow: hidden;
270
+ margin: 10px 0 0;
271
+ color: rgba(255, 255, 255, 0.82);
272
+ font-size: 12px;
273
+ line-height: 1.3;
274
+ text-overflow: ellipsis;
275
+ white-space: nowrap;
276
+ }
277
+
278
+ .hero-date {
279
+ color: rgba(255, 255, 255, 0.88);
280
+ font-size: 13px;
281
+ font-weight: 650;
282
+ }
283
+
284
+ .detail-card {
285
+ margin-top: 10px;
286
+ border-radius: 13px;
287
+ padding: 15px;
288
+ background: rgba(255, 255, 255, 0.96);
289
+ box-shadow: 0 7px 22px rgba(21, 28, 45, 0.055);
290
+ }
291
+
292
+ .detail-card h2 {
293
+ margin: 0;
294
+ color: #111728;
295
+ font-size: 15px;
296
+ font-weight: 760;
297
+ line-height: 1.2;
298
+ }
299
+
300
+ .detail-list {
301
+ margin: 12px 0 0;
302
+ }
303
+
304
+ .detail-list div {
305
+ display: grid;
306
+ grid-template-columns: 96px minmax(0, 1fr);
307
+ gap: 12px;
308
+ padding: 10px 0;
309
+ border-top: 1px solid #eef0f4;
310
+ }
311
+
312
+ .detail-list div:first-child {
313
+ border-top: 0;
314
+ }
315
+
316
+ .detail-list dt,
317
+ .detail-list dd {
318
+ margin: 0;
319
+ font-size: 13px;
320
+ line-height: 1.42;
321
+ }
322
+
323
+ .detail-list dt {
324
+ color: #7f8798;
325
+ }
326
+
327
+ .detail-list dd {
328
+ overflow-wrap: anywhere;
329
+ color: #151827;
330
+ font-weight: 650;
331
+ text-align: right;
332
+ }
333
+
334
+ .status-tag {
335
+ display: inline-flex;
336
+ align-items: center;
337
+ justify-content: center;
338
+ min-height: 20px;
339
+ border: 1px solid transparent;
340
+ border-radius: 4px;
341
+ padding: 2px 7px;
342
+ font-size: 11px;
343
+ font-weight: 650;
344
+ line-height: 1.25;
345
+ white-space: nowrap;
346
+ }
347
+
348
+ .status-tag-confirmed {
349
+ border-color: #d8e1ee;
350
+ color: #687083;
351
+ background: #ffffff;
352
+ }
353
+
354
+ .status-tag-pending {
355
+ border-color: #ffd99a;
356
+ color: #a05a00;
357
+ background: #fff4d8;
358
+ }
359
+
360
+ .status-tag-cancelled {
361
+ border-color: #d6dbe3;
362
+ color: #687083;
363
+ background: #f3f5f8;
364
+ }
365
+
366
+ .status-tag-verified {
367
+ border-color: #b7ebc9;
368
+ color: #0b8f3a;
369
+ background: #e4f8eb;
370
+ }
371
+
372
+ .status-tag-rejected {
373
+ border-color: #ffc0cb;
374
+ color: #bf2549;
375
+ background: #ffe8ed;
376
+ }
377
+
378
+ .status-tag-unknown {
379
+ border-color: #d6dbe3;
380
+ color: #687083;
381
+ background: #f3f5f8;
382
+ }
383
+
384
+ @media (max-width: 340px) {
385
+ .detail-list div {
386
+ grid-template-columns: 84px minmax(0, 1fr);
387
+ gap: 8px;
388
+ }
389
+ }
390
+ </style>