minitest2.0 0.0.0

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 (89) hide show
  1. package/.editorconfig +8 -0
  2. package/.env.development +3 -0
  3. package/.env.production +3 -0
  4. package/.env.test +3 -0
  5. package/.gitattributes +1 -0
  6. package/.oxlintrc.json +10 -0
  7. package/.prettierrc.json +6 -0
  8. package/.vscode/extensions.json +9 -0
  9. package/.vscode/settings.json +13 -0
  10. package/README.md +179 -0
  11. package/auto-imports.d.ts +629 -0
  12. package/components.d.ts +21 -0
  13. package/design-qa.md +36 -0
  14. package/docs/MX_API.md +244 -0
  15. package/docs/REQUEST.md +217 -0
  16. package/docs/jsapi.js +515 -0
  17. package/docs/package-json-guide.md +132 -0
  18. package/env.d.ts +15 -0
  19. package/eslint.config.ts +26 -0
  20. package/index.html +16 -0
  21. package/package.json +83 -0
  22. package/plugins/bump-version.ts +61 -0
  23. package/postcss.config.ts +15 -0
  24. package/public/favicon.ico +0 -0
  25. package/public/images/12a73787-86a9-4891-a65f-66104746f6a8.png +0 -0
  26. package/public/images/5798d7aa-ba8b-4605-8079-58b35495ac55.png +0 -0
  27. package/public/images/73fef1e4-0fd0-4a1a-9b8b-a70a5b6acbbc.png +0 -0
  28. package/public/images/bc685b4c-0cca-4a79-924c-a8ee10e6f8eb.png +0 -0
  29. package/public/images/c3dbbd9d-be56-490e-b9f4-6ee17ebefffc.png +0 -0
  30. package/public/images/ea745a10-42aa-4f44-8d7f-3ab02cc0adcd.png +0 -0
  31. package/public/images/f5876785-b927-4347-ba19-999114240649.png +0 -0
  32. package/public/images/img.png +0 -0
  33. package/public/images/opening-reserve-estimate.png +0 -0
  34. package/public/images/position-estimate-report.png +0 -0
  35. package/src/App.vue +131 -0
  36. package/src/api/announcement.ts +405 -0
  37. package/src/api/health.ts +13 -0
  38. package/src/api/pbc-position.ts +178 -0
  39. package/src/api/rmb-position.ts +233 -0
  40. package/src/api/tam.ts +173 -0
  41. package/src/api/user.ts +92 -0
  42. package/src/auto-imports.d.ts +633 -0
  43. package/src/components/AppTitleBar.vue +376 -0
  44. package/src/components.d.ts +33 -0
  45. package/src/config/config.properties +3 -0
  46. package/src/config/env.ts +6 -0
  47. package/src/config/plugin.properties.pro +6 -0
  48. package/src/config/plugin.properties.test +6 -0
  49. package/src/core/mxApi/index.ts +451 -0
  50. package/src/core/request/index.ts +135 -0
  51. package/src/main.ts +40 -0
  52. package/src/router/index.ts +144 -0
  53. package/src/stores/app.ts +103 -0
  54. package/src/stores/counter.ts +12 -0
  55. package/src/stores/user.ts +137 -0
  56. package/src/styles/nprogress.css +22 -0
  57. package/src/styles/vant-overrides.css +42 -0
  58. package/src/types/api.d.ts +14 -0
  59. package/src/types/nprogress.d.ts +8 -0
  60. package/src/utils/auth-token.ts +241 -0
  61. package/src/utils/code-highlight.ts +165 -0
  62. package/src/utils/copy.ts +36 -0
  63. package/src/utils/query.ts +27 -0
  64. package/src/utils/request.ts +238 -0
  65. package/src/utils/rmb-forecast.ts +84 -0
  66. package/src/utils/toast.ts +61 -0
  67. package/src/views/article-detail/index.vue +289 -0
  68. package/src/views/article-edit/index.vue +600 -0
  69. package/src/views/articles/index.vue +293 -0
  70. package/src/views/clearing-detail/index.vue +26 -0
  71. package/src/views/dashboard/index.vue +18 -0
  72. package/src/views/foreign-position/index.vue +26 -0
  73. package/src/views/home/index.vue +213 -0
  74. package/src/views/login/index.vue +275 -0
  75. package/src/views/mine/index.vue +147 -0
  76. package/src/views/net-debit/index.vue +26 -0
  77. package/src/views/opening-reserve-estimate/index.vue +28 -0
  78. package/src/views/pbc-position/index.vue +357 -0
  79. package/src/views/position-estimate/index.vue +67 -0
  80. package/src/views/position-estimate-report/index.vue +28 -0
  81. package/src/views/rmb-position/index.vue +1013 -0
  82. package/src/views/rmb-position-create/index.vue +221 -0
  83. package/src/views/rmb-position-detail/index.vue +355 -0
  84. package/src/views/settings/index.vue +67 -0
  85. package/src/views/warning/index.vue +26 -0
  86. package/tsconfig.app.json +18 -0
  87. package/tsconfig.json +11 -0
  88. package/tsconfig.node.json +28 -0
  89. package/vite.config.ts +100 -0
@@ -0,0 +1,1013 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ buildRmbForecastQueryPayload,
4
+ queryRmbForecastPage,
5
+ type RmbForecastQueryForm,
6
+ type RmbForecastRecord,
7
+ } from '@/api/rmb-position'
8
+ import { useAppStore } from '@/stores/app'
9
+ import {
10
+ forecastStatusMap,
11
+ formatForecastAmount,
12
+ formatRmbForecastDate,
13
+ resolveBusinessType,
14
+ resolveClientType,
15
+ resolveForecastStatus,
16
+ toBillionYuan,
17
+ } from '@/utils/rmb-forecast'
18
+ import { showAppToast } from '@/utils/toast'
19
+
20
+ defineOptions({ name: 'RmbPositionPage' })
21
+
22
+ type Forecast = {
23
+ id: number
24
+ institution: string
25
+ date: string
26
+ customerType: string
27
+ business: string
28
+ amount: number
29
+ predRefNo: string
30
+ status: string
31
+ statusClass: string
32
+ }
33
+
34
+ type PendingForecast = {
35
+ institution: string
36
+ date: string
37
+ customerType: string
38
+ business: string
39
+ amount: number
40
+ }
41
+
42
+ const router = useRouter()
43
+ const appStore = useAppStore()
44
+ const defaultTranDate = appStore.systemWorkDate
45
+ const heroImage = `${import.meta.env.BASE_URL}images/5798d7aa-ba8b-4605-8079-58b35495ac55.png`
46
+ const pageSize = 10
47
+ const backTopThreshold = 520
48
+ const pendingForecastKey = 'rmb-position:pending-create'
49
+
50
+ const queryForm = reactive({
51
+ institution: '',
52
+ minAmount: '',
53
+ maxAmount: '',
54
+ })
55
+
56
+ const selectedDate = ref(defaultTranDate)
57
+ const showDatePicker = ref(false)
58
+ const datePickerValue = ref(selectedDate.value.split('-'))
59
+ const minDate = new Date(2020, 0, 1)
60
+ const maxDate = new Date(2030, 11, 31)
61
+ const isFilterExpanded = ref(true)
62
+
63
+ const forecastList = ref<Forecast[]>([])
64
+ const forecastTotal = ref(0)
65
+ const currentPage = ref(1)
66
+ const listLoading = ref(false)
67
+ const listFinished = ref(false)
68
+ const listError = ref(false)
69
+ const requesting = ref(false)
70
+ const showBackTop = ref(false)
71
+ const filterSummary = computed(() => {
72
+ const institution = queryForm.institution.trim() || '全部机构'
73
+ const amount =
74
+ queryForm.minAmount || queryForm.maxAmount
75
+ ? `${queryForm.minAmount || '不限'}-${queryForm.maxAmount || '不限'}亿元`
76
+ : '全部金额'
77
+
78
+ return `${selectedDate.value} · ${institution} · ${amount}`
79
+ })
80
+
81
+ onMounted(async () => {
82
+ updateBackTopVisibility()
83
+ window.addEventListener('scroll', updateBackTopVisibility, { passive: true })
84
+ await resetAndLoad()
85
+ consumePendingForecast()
86
+ })
87
+
88
+ onUnmounted(() => {
89
+ window.removeEventListener('scroll', updateBackTopVisibility)
90
+ })
91
+
92
+ function mapRmbRecordToForecast(record: RmbForecastRecord, index: number): Forecast {
93
+ const busiType = record.busiType || ''
94
+ const clientType = record.clientType || ''
95
+ const status = resolveForecastStatus(record.predStatus)
96
+
97
+ return {
98
+ id: index + 1,
99
+ institution: record.groundBranch || '未填写机构',
100
+ date: formatRmbForecastDate(record.tranDate),
101
+ customerType: resolveClientType(clientType),
102
+ business: resolveBusinessType(busiType),
103
+ amount: toBillionYuan(record.predAmt),
104
+ predRefNo: record.predRefNo || '',
105
+ status: status.label,
106
+ statusClass: status.className,
107
+ }
108
+ }
109
+
110
+ function buildQueryForm(): RmbForecastQueryForm {
111
+ return {
112
+ tranDate: selectedDate.value,
113
+ groundBranch: queryForm.institution.trim(),
114
+ startTranAmt: queryForm.minAmount,
115
+ endTranAmt: queryForm.maxAmount,
116
+ }
117
+ }
118
+
119
+ function parseAmount(value: string) {
120
+ if (!value.trim()) return undefined
121
+
122
+ const amount = Number(value)
123
+ return Number.isFinite(amount) ? amount : undefined
124
+ }
125
+
126
+ function updateBackTopVisibility() {
127
+ showBackTop.value = window.scrollY > backTopThreshold
128
+ }
129
+
130
+ function scrollToTop() {
131
+ showBackTop.value = false
132
+ window.scrollTo({
133
+ top: 0,
134
+ behavior: 'smooth',
135
+ })
136
+ }
137
+
138
+ function openCreatePage() {
139
+ router.push({
140
+ name: 'RmbPositionCreate',
141
+ query: { tranDate: selectedDate.value },
142
+ })
143
+ }
144
+
145
+ async function handleSearch() {
146
+ const minAmount = parseAmount(queryForm.minAmount)
147
+ const maxAmount = parseAmount(queryForm.maxAmount)
148
+
149
+ if (
150
+ (queryForm.minAmount && minAmount === undefined) ||
151
+ (queryForm.maxAmount && maxAmount === undefined)
152
+ ) {
153
+ showAppToast('请输入正确的金额')
154
+ return
155
+ }
156
+
157
+ if (minAmount !== undefined && maxAmount !== undefined && minAmount > maxAmount) {
158
+ showAppToast('下限金额不能大于上限金额')
159
+ return
160
+ }
161
+
162
+ await resetAndLoad()
163
+
164
+ if (!listError.value) {
165
+ showAppToast(`查询到 ${forecastTotal.value} 笔预报`)
166
+ }
167
+ }
168
+
169
+ async function handleReset() {
170
+ selectedDate.value = defaultTranDate
171
+ datePickerValue.value = selectedDate.value.split('-')
172
+ queryForm.institution = ''
173
+ queryForm.minAmount = ''
174
+ queryForm.maxAmount = ''
175
+
176
+ await resetAndLoad()
177
+
178
+ if (!listError.value) {
179
+ showAppToast('已重置筛选条件')
180
+ }
181
+ }
182
+
183
+ async function resetAndLoad() {
184
+ currentPage.value = 1
185
+ forecastList.value = []
186
+ forecastTotal.value = 0
187
+ listFinished.value = false
188
+ listError.value = false
189
+
190
+ await loadNextPage()
191
+ }
192
+
193
+ async function loadNextPage() {
194
+ if (requesting.value || listFinished.value) {
195
+ listLoading.value = false
196
+ return
197
+ }
198
+
199
+ requesting.value = true
200
+ listLoading.value = true
201
+ listError.value = false
202
+
203
+ const payload = buildRmbForecastQueryPayload({
204
+ form: buildQueryForm(),
205
+ pageIndex: currentPage.value,
206
+ pageSize,
207
+ })
208
+
209
+ console.log('[人民币预报查询入参]', payload)
210
+
211
+ try {
212
+ const response = await queryRmbForecastPage(payload)
213
+ const nextList = response.body.resultList.map((item, index) =>
214
+ mapRmbRecordToForecast(item, forecastList.value.length + index),
215
+ )
216
+
217
+ forecastList.value.push(...nextList)
218
+ forecastTotal.value = Number(response.body.totalCount) || forecastList.value.length
219
+ currentPage.value += 1
220
+ listFinished.value =
221
+ forecastList.value.length >= forecastTotal.value || nextList.length < pageSize
222
+ } catch (error) {
223
+ console.error('[人民币预报查询失败]', error)
224
+ listError.value = true
225
+ showAppToast('数据加载失败,请重试')
226
+ } finally {
227
+ requesting.value = false
228
+ listLoading.value = false
229
+ }
230
+ }
231
+
232
+ function confirmDate() {
233
+ selectedDate.value = datePickerValue.value.join('-')
234
+ showDatePicker.value = false
235
+ }
236
+
237
+ function openDetail(item: Forecast) {
238
+ if (!item.predRefNo) {
239
+ showAppToast('暂无预报流水号')
240
+ return
241
+ }
242
+
243
+ router.push({
244
+ name: 'RmbPositionDetail',
245
+ params: { predRefNo: item.predRefNo },
246
+ })
247
+ }
248
+
249
+ function consumePendingForecast() {
250
+ const payload = sessionStorage.getItem(pendingForecastKey)
251
+
252
+ if (!payload) return
253
+
254
+ sessionStorage.removeItem(pendingForecastKey)
255
+
256
+ try {
257
+ const forecast = JSON.parse(payload) as Partial<PendingForecast>
258
+
259
+ if (!isPendingForecast(forecast)) return
260
+
261
+ forecastList.value.unshift({
262
+ id: Date.now(),
263
+ institution: forecast.institution,
264
+ date: forecast.date,
265
+ customerType: forecast.customerType,
266
+ business: forecast.business,
267
+ amount: forecast.amount,
268
+ predRefNo: '',
269
+ status: forecastStatusMap['1'].label,
270
+ statusClass: forecastStatusMap['1'].className,
271
+ })
272
+ forecastTotal.value += 1
273
+ } catch (error) {
274
+ console.error('[人民币预报新增结果解析失败]', error)
275
+ }
276
+ }
277
+
278
+ function isPendingForecast(value: Partial<PendingForecast> | null): value is PendingForecast {
279
+ return Boolean(
280
+ value &&
281
+ typeof value.institution === 'string' &&
282
+ typeof value.date === 'string' &&
283
+ typeof value.customerType === 'string' &&
284
+ typeof value.business === 'string' &&
285
+ typeof value.amount === 'number' &&
286
+ Number.isFinite(value.amount),
287
+ )
288
+ }
289
+ </script>
290
+
291
+ <template>
292
+ <main class="rmb-page">
293
+ <section class="query-card">
294
+ <div class="hero-panel">
295
+ <div class="hero-copy">
296
+ <h2>本币预报信息查询</h2>
297
+ <p>查询本币预报及交易明细</p>
298
+ </div>
299
+ <img class="hero-image" :src="heroImage" alt="本币预报信息查询" />
300
+ </div>
301
+ </section>
302
+
303
+ <section class="filter-sticky">
304
+ <div class="filter-panel" :class="{ 'is-collapsed': !isFilterExpanded }">
305
+ <div class="filter-toolbar">
306
+ <div class="filter-heading">
307
+ <strong>筛选条件</strong>
308
+ <span>{{ filterSummary }}</span>
309
+ </div>
310
+ <button
311
+ class="filter-toggle"
312
+ type="button"
313
+ :aria-expanded="isFilterExpanded"
314
+ @click="isFilterExpanded = !isFilterExpanded"
315
+ >
316
+ <van-icon :name="isFilterExpanded ? 'arrow-up' : 'arrow-down'" />
317
+ <span>{{ isFilterExpanded ? '收起' : '展开' }}</span>
318
+ </button>
319
+ </div>
320
+
321
+ <Transition name="filter-body">
322
+ <div v-show="isFilterExpanded" class="filter-body">
323
+ <button class="filter-row" type="button" @click="showDatePicker = true">
324
+ <span class="row-icon row-icon-calendar"><van-icon name="calendar-o" /></span>
325
+ <span class="row-label">交易日期</span>
326
+ <span class="row-value">{{ selectedDate }}</span>
327
+ <van-icon class="row-calendar" name="calendar-o" />
328
+ <van-icon class="row-arrow" name="arrow" />
329
+ </button>
330
+
331
+ <label class="filter-row">
332
+ <span class="row-icon row-icon-bank"><van-icon name="hotel-o" /></span>
333
+ <span class="row-label">落地机构</span>
334
+ <input
335
+ v-model.trim="queryForm.institution"
336
+ class="row-input"
337
+ type="text"
338
+ inputmode="text"
339
+ placeholder="请输入落地机构关键字"
340
+ />
341
+ <van-icon class="row-arrow" name="arrow" />
342
+ </label>
343
+
344
+ <div class="filter-row amount-row">
345
+ <span class="row-icon row-icon-money"><van-icon name="gold-coin-o" /></span>
346
+ <span class="row-label">金额区间</span>
347
+ <input
348
+ v-model.trim="queryForm.minAmount"
349
+ class="amount-input"
350
+ type="number"
351
+ inputmode="decimal"
352
+ placeholder="请输入下限金额"
353
+ />
354
+ <span class="amount-divider">-</span>
355
+ <input
356
+ v-model.trim="queryForm.maxAmount"
357
+ class="amount-input"
358
+ type="number"
359
+ inputmode="decimal"
360
+ placeholder="请输入上限金额"
361
+ />
362
+ </div>
363
+
364
+ <div class="filter-actions">
365
+ <button class="reset-btn" type="button" @click="handleReset">
366
+ <van-icon name="replay" />
367
+ <span>重置</span>
368
+ </button>
369
+ <button class="search-btn" type="button" @click="handleSearch">
370
+ <van-icon name="search" />
371
+ <span>查询</span>
372
+ </button>
373
+ </div>
374
+ </div>
375
+ </Transition>
376
+ </div>
377
+ </section>
378
+
379
+ <section class="forecast-card" aria-label="预报列表">
380
+ <van-list
381
+ v-model:loading="listLoading"
382
+ v-model:error="listError"
383
+ :finished="listFinished"
384
+ :immediate-check="false"
385
+ error-text="加载失败,点击重试"
386
+ finished-text="没有更多了"
387
+ loading-text="加载中..."
388
+ @load="loadNextPage"
389
+ >
390
+ <button
391
+ v-for="item in forecastList"
392
+ :key="item.id"
393
+ class="forecast-item"
394
+ type="button"
395
+ @click="openDetail(item)"
396
+ >
397
+ <span class="forecast-avatar"><van-icon name="hotel-o" /></span>
398
+ <span class="forecast-main">
399
+ <strong>{{ item.institution }}</strong>
400
+ <span class="forecast-date">{{ item.date }}</span>
401
+ <span class="forecast-business">{{ item.customerType }} | {{ item.business }}</span>
402
+ </span>
403
+ <span class="forecast-side">
404
+ <span class="forecast-amount">
405
+ {{ formatForecastAmount(item.amount) }} <em>亿元</em>
406
+ </span>
407
+ <span class="status-tag" :class="item.statusClass">{{ item.status }}</span>
408
+ </span>
409
+ <van-icon class="forecast-arrow" name="arrow" />
410
+ </button>
411
+
412
+ <van-empty
413
+ v-if="!listLoading && forecastList.length === 0"
414
+ image-size="72"
415
+ description="暂无预报信息"
416
+ />
417
+ </van-list>
418
+ </section>
419
+
420
+ <button class="fab" type="button" aria-label="新增预报" @click="openCreatePage">
421
+ <van-icon name="plus" />
422
+ </button>
423
+
424
+ <Transition name="back-top">
425
+ <button
426
+ v-show="showBackTop"
427
+ class="back-top-btn"
428
+ type="button"
429
+ aria-label="返回顶部"
430
+ @click="scrollToTop"
431
+ >
432
+ <van-icon name="arrow-up" />
433
+ </button>
434
+ </Transition>
435
+
436
+ <van-popup v-model:show="showDatePicker" position="bottom" round safe-area-inset-bottom>
437
+ <van-date-picker
438
+ v-model="datePickerValue"
439
+ title="选择交易日期"
440
+ :min-date="minDate"
441
+ :max-date="maxDate"
442
+ @cancel="showDatePicker = false"
443
+ @confirm="confirmDate"
444
+ />
445
+ </van-popup>
446
+ </main>
447
+ </template>
448
+
449
+ <style scoped>
450
+ .rmb-page {
451
+ position: relative;
452
+ width: 100%;
453
+ max-width: 750px;
454
+ min-height: var(--app-page-min-height, 100vh);
455
+ margin: 0 auto;
456
+ padding: 13px 13px calc(env(safe-area-inset-bottom, 0px) + 118px);
457
+ color: #080d1f;
458
+ background:
459
+ radial-gradient(circle at 50% 0%, rgba(255, 242, 243, 0.92), transparent 118px),
460
+ linear-gradient(180deg, #ffffff 0%, #fbfcfd 54%, #ffffff 100%);
461
+ box-sizing: border-box;
462
+ }
463
+
464
+ button,
465
+ input {
466
+ font: inherit;
467
+ }
468
+
469
+ button {
470
+ border: 0;
471
+ padding: 0;
472
+ background: transparent;
473
+ appearance: none;
474
+ }
475
+
476
+ .query-card {
477
+ overflow: hidden;
478
+ border-radius: 13px;
479
+ box-shadow: 0 10px 24px rgba(31, 41, 55, 0.06);
480
+ }
481
+
482
+ .hero-panel {
483
+ position: relative;
484
+ height: 143px;
485
+ overflow: hidden;
486
+ border-radius: 13px 13px 0 0;
487
+ background:
488
+ radial-gradient(circle at 72% 80%, rgba(255, 255, 255, 0.16), transparent 74px),
489
+ radial-gradient(circle at 92% 14%, rgba(255, 255, 255, 0.13), transparent 58px),
490
+ linear-gradient(135deg, #ff2736 0%, #ff3a42 42%, #ff454b 100%);
491
+ }
492
+
493
+ .hero-panel::after {
494
+ position: absolute;
495
+ right: 15px;
496
+ bottom: 14px;
497
+ width: 26px;
498
+ height: 64px;
499
+ border-radius: 3px;
500
+ background: rgba(255, 137, 137, 0.34);
501
+ box-shadow: -21px 14px 0 rgba(255, 137, 137, 0.28);
502
+ content: '';
503
+ }
504
+
505
+ .hero-copy {
506
+ position: relative;
507
+ z-index: 2;
508
+ padding: 32px 0 0 15px;
509
+ color: #ffffff;
510
+ }
511
+
512
+ .hero-copy h2 {
513
+ margin: 0;
514
+ font-size: 22px;
515
+ font-weight: 800;
516
+ line-height: 1.2;
517
+ }
518
+
519
+ .hero-copy p {
520
+ margin: 18px 0 0;
521
+ font-size: 13px;
522
+ font-weight: 600;
523
+ line-height: 1.2;
524
+ }
525
+
526
+ .hero-image {
527
+ position: absolute;
528
+ right: 17px;
529
+ bottom: 3px;
530
+ z-index: 1;
531
+ width: 118px;
532
+ height: 118px;
533
+ object-fit: contain;
534
+ filter: drop-shadow(0 12px 20px rgba(165, 0, 18, 0.16));
535
+ }
536
+
537
+ .filter-sticky {
538
+ position: sticky;
539
+ top: calc(50px + env(safe-area-inset-top, 0px));
540
+ z-index: 15;
541
+ margin-top: -18px;
542
+ }
543
+
544
+ .filter-panel {
545
+ position: relative;
546
+ z-index: 1;
547
+ padding: 12px 13px 14px;
548
+ border-radius: 13px;
549
+ background: #ffffff;
550
+ box-shadow: 0 -4px 18px rgba(255, 66, 76, 0.06);
551
+ }
552
+
553
+ .filter-panel.is-collapsed {
554
+ padding-bottom: 12px;
555
+ }
556
+
557
+ .filter-toolbar {
558
+ display: grid;
559
+ grid-template-columns: minmax(0, 1fr) 72px;
560
+ column-gap: 10px;
561
+ align-items: center;
562
+ }
563
+
564
+ .filter-heading {
565
+ min-width: 0;
566
+ }
567
+
568
+ .filter-heading strong,
569
+ .filter-heading span {
570
+ display: block;
571
+ overflow: hidden;
572
+ text-overflow: ellipsis;
573
+ white-space: nowrap;
574
+ }
575
+
576
+ .filter-heading strong {
577
+ color: #101525;
578
+ font-size: 14px;
579
+ font-weight: 750;
580
+ line-height: 1.25;
581
+ }
582
+
583
+ .filter-heading span {
584
+ margin-top: 3px;
585
+ color: #818899;
586
+ font-size: 11px;
587
+ line-height: 1.25;
588
+ }
589
+
590
+ .filter-toggle {
591
+ display: inline-flex;
592
+ align-items: center;
593
+ justify-content: center;
594
+ gap: 4px;
595
+ height: 30px;
596
+ border: 1px solid #ffd5da;
597
+ border-radius: 15px;
598
+ color: #f42b3a;
599
+ font-size: 12px;
600
+ font-weight: 650;
601
+ background: #fff7f8;
602
+ }
603
+
604
+ .filter-toggle .van-icon {
605
+ font-size: 14px;
606
+ }
607
+
608
+ .filter-body {
609
+ overflow: hidden;
610
+ margin-top: 8px;
611
+ border-top: 1px solid #eef0f4;
612
+ }
613
+
614
+ .filter-body-enter-active,
615
+ .filter-body-leave-active {
616
+ transition:
617
+ opacity 0.16s ease,
618
+ transform 0.16s ease;
619
+ }
620
+
621
+ .filter-body-enter-from,
622
+ .filter-body-leave-to {
623
+ opacity: 0;
624
+ transform: translateY(-6px);
625
+ }
626
+
627
+ .filter-row {
628
+ display: flex;
629
+ align-items: center;
630
+ width: 100%;
631
+ min-height: 48px;
632
+ color: #080d1f;
633
+ text-align: left;
634
+ box-sizing: border-box;
635
+ }
636
+
637
+ .filter-row + .filter-row {
638
+ border-top: 1px solid #eef0f4;
639
+ }
640
+
641
+ .row-icon {
642
+ display: flex;
643
+ align-items: center;
644
+ justify-content: center;
645
+ width: 24px;
646
+ height: 24px;
647
+ flex: 0 0 24px;
648
+ border-radius: 6px;
649
+ color: #f42b3a;
650
+ font-size: 16px;
651
+ background: linear-gradient(135deg, #fff2f3 0%, #ffe4e6 100%);
652
+ }
653
+
654
+ .row-label {
655
+ width: 70px;
656
+ flex: 0 0 70px;
657
+ margin-left: 11px;
658
+ color: #151827;
659
+ font-size: 14px;
660
+ font-weight: 650;
661
+ line-height: 1.2;
662
+ }
663
+
664
+ .row-value {
665
+ margin-left: auto;
666
+ color: #202638;
667
+ font-size: 14px;
668
+ line-height: 1.2;
669
+ }
670
+
671
+ .row-input {
672
+ width: 0;
673
+ min-width: 0;
674
+ flex: 1;
675
+ border: 0;
676
+ outline: 0;
677
+ color: #202638;
678
+ font-size: 13px;
679
+ background: transparent;
680
+ }
681
+
682
+ .row-input::placeholder,
683
+ .amount-input::placeholder {
684
+ color: #a6adbd;
685
+ }
686
+
687
+ .amount-input::placeholder {
688
+ font-size: 10px;
689
+ }
690
+
691
+ .row-calendar {
692
+ margin-left: 12px;
693
+ color: #a7adba;
694
+ font-size: 16px;
695
+ }
696
+
697
+ .row-arrow {
698
+ margin-left: 12px;
699
+ color: #9fa6b5;
700
+ font-size: 15px;
701
+ }
702
+
703
+ .amount-row {
704
+ gap: 0;
705
+ min-height: 52px;
706
+ }
707
+
708
+ .amount-row .row-label {
709
+ width: 58px;
710
+ flex-basis: 58px;
711
+ margin-right: 8px;
712
+ }
713
+
714
+ .amount-input {
715
+ width: 0;
716
+ min-width: 0;
717
+ flex: 1 1 0;
718
+ height: 28px;
719
+ border: 1px solid #e5e8ef;
720
+ border-radius: 6px;
721
+ padding: 0 4px;
722
+ color: #202638;
723
+ font-size: 11px;
724
+ text-align: center;
725
+ outline: 0;
726
+ background: #ffffff;
727
+ box-sizing: border-box;
728
+ }
729
+
730
+ .amount-divider {
731
+ flex: 0 0 18px;
732
+ text-align: center;
733
+ }
734
+
735
+ .amount-divider {
736
+ color: #8c94a5;
737
+ font-size: 13px;
738
+ }
739
+
740
+ .filter-actions {
741
+ display: grid;
742
+ grid-template-columns: 92px minmax(0, 1fr);
743
+ gap: 10px;
744
+ margin-top: 12px;
745
+ }
746
+
747
+ .reset-btn,
748
+ .search-btn {
749
+ display: flex;
750
+ align-items: center;
751
+ justify-content: center;
752
+ gap: 8px;
753
+ width: 100%;
754
+ height: 34px;
755
+ border-radius: 18px;
756
+ font-size: 14px;
757
+ font-weight: 650;
758
+ }
759
+
760
+ .reset-btn {
761
+ border: 1px solid #ffd5da;
762
+ color: #f42b3a;
763
+ background: #fff7f8;
764
+ }
765
+
766
+ .search-btn {
767
+ color: #ffffff;
768
+ background: linear-gradient(135deg, #ff3340 0%, #ff1d34 100%);
769
+ box-shadow: 0 8px 16px rgba(255, 36, 52, 0.18);
770
+ }
771
+
772
+ .reset-btn .van-icon,
773
+ .search-btn .van-icon {
774
+ font-size: 17px;
775
+ }
776
+
777
+ .forecast-card {
778
+ border-radius: 13px;
779
+ background: rgba(255, 255, 255, 0.95);
780
+ box-shadow: 0 7px 22px rgba(21, 28, 45, 0.055);
781
+ }
782
+
783
+ .forecast-card {
784
+ margin-top: 9px;
785
+ overflow: hidden;
786
+ }
787
+
788
+ .forecast-item {
789
+ position: relative;
790
+ display: grid;
791
+ grid-template-columns: 42px minmax(0, 1fr) auto 14px;
792
+ column-gap: 10px;
793
+ align-items: center;
794
+ width: 100%;
795
+ min-height: 91px;
796
+ padding: 14px 17px;
797
+ color: #080d1f;
798
+ text-align: left;
799
+ box-sizing: border-box;
800
+ }
801
+
802
+ .forecast-item + .forecast-item {
803
+ border-top: 1px solid #edf0f3;
804
+ }
805
+
806
+ .forecast-avatar {
807
+ display: flex;
808
+ align-items: center;
809
+ justify-content: center;
810
+ width: 36px;
811
+ height: 36px;
812
+ border-radius: 50%;
813
+ color: #111728;
814
+ font-size: 24px;
815
+ background: #fff0f1;
816
+ }
817
+
818
+ .forecast-avatar .van-icon {
819
+ transform: translateY(1px);
820
+ }
821
+
822
+ .forecast-main {
823
+ min-width: 0;
824
+ }
825
+
826
+ .forecast-main strong {
827
+ display: block;
828
+ overflow: hidden;
829
+ color: #080d1f;
830
+ font-size: 14px;
831
+ font-weight: 760;
832
+ line-height: 1.35;
833
+ text-overflow: ellipsis;
834
+ white-space: nowrap;
835
+ }
836
+
837
+ .forecast-date,
838
+ .forecast-business {
839
+ display: block;
840
+ overflow: hidden;
841
+ margin-top: 7px;
842
+ font-size: 12px;
843
+ line-height: 1.2;
844
+ text-overflow: ellipsis;
845
+ white-space: nowrap;
846
+ }
847
+
848
+ .forecast-date {
849
+ color: #8a92a3;
850
+ }
851
+
852
+ .forecast-business {
853
+ color: #111728;
854
+ }
855
+
856
+ .forecast-side {
857
+ display: flex;
858
+ align-items: flex-end;
859
+ flex-direction: column;
860
+ min-width: 92px;
861
+ }
862
+
863
+ .forecast-amount {
864
+ color: #080d1f;
865
+ font-size: 15px;
866
+ font-weight: 650;
867
+ line-height: 1.2;
868
+ white-space: nowrap;
869
+ }
870
+
871
+ .forecast-amount em {
872
+ color: #7f8798;
873
+ font-size: 11px;
874
+ font-style: normal;
875
+ font-weight: 400;
876
+ }
877
+
878
+ .status-tag {
879
+ display: inline-flex;
880
+ align-items: center;
881
+ justify-content: center;
882
+ min-height: 20px;
883
+ max-width: 128px;
884
+ margin-top: 10px;
885
+ border: 1px solid transparent;
886
+ border-radius: 4px;
887
+ padding: 2px 7px;
888
+ font-size: 11px;
889
+ font-weight: 650;
890
+ line-height: 1.25;
891
+ text-align: right;
892
+ white-space: nowrap;
893
+ }
894
+
895
+ .status-tag-normal {
896
+ border-color: #b7ebc9;
897
+ color: #0b8f3a;
898
+ background: #e4f8eb;
899
+ }
900
+
901
+ .status-tag-delayed {
902
+ border-color: #ffd99a;
903
+ color: #a05a00;
904
+ background: #fff4d8;
905
+ }
906
+
907
+ .status-tag-normal-delete {
908
+ border-color: #b7d7ff;
909
+ color: #1d5fbf;
910
+ background: #e6f0ff;
911
+ }
912
+
913
+ .status-tag-normal-delay-delete {
914
+ border-color: #d5c2ff;
915
+ color: #6e43c4;
916
+ background: #f0eaff;
917
+ }
918
+
919
+ .status-tag-delayed-late-delete {
920
+ border-color: #ffc0cb;
921
+ color: #bf2549;
922
+ background: #ffe8ed;
923
+ }
924
+
925
+ .status-tag-late-delay-delete {
926
+ border-color: #a7dced;
927
+ color: #08768f;
928
+ background: #dff6fb;
929
+ }
930
+
931
+ .status-tag-unknown {
932
+ border-color: #d6dbe3;
933
+ color: #687083;
934
+ background: #f3f5f8;
935
+ }
936
+
937
+ .forecast-arrow {
938
+ color: #9da4b3;
939
+ font-size: 15px;
940
+ }
941
+
942
+ .fab {
943
+ position: fixed;
944
+ right: max(25px, calc((100vw - 750px) / 2 + 25px));
945
+ bottom: calc(env(safe-area-inset-bottom, 0px) + 35px);
946
+ z-index: 20;
947
+ display: flex;
948
+ align-items: center;
949
+ justify-content: center;
950
+ width: 52px;
951
+ height: 52px;
952
+ border-radius: 50%;
953
+ color: #ffffff;
954
+ font-size: 31px;
955
+ background: linear-gradient(145deg, #ff4050 0%, #f50f2d 100%);
956
+ box-shadow: 0 13px 28px rgba(244, 29, 48, 0.34);
957
+ }
958
+
959
+ .back-top-btn {
960
+ position: fixed;
961
+ right: max(31px, calc((100vw - 750px) / 2 + 31px));
962
+ bottom: calc(env(safe-area-inset-bottom, 0px) + 99px);
963
+ z-index: 20;
964
+ display: flex;
965
+ align-items: center;
966
+ justify-content: center;
967
+ width: 40px;
968
+ height: 40px;
969
+ border: 1px solid rgba(226, 232, 240, 0.9);
970
+ border-radius: 50%;
971
+ color: #f22f3d;
972
+ font-size: 20px;
973
+ background: rgba(255, 255, 255, 0.94);
974
+ box-shadow: 0 10px 24px rgba(21, 28, 45, 0.13);
975
+ backdrop-filter: blur(12px);
976
+ }
977
+
978
+ .back-top-enter-active,
979
+ .back-top-leave-active {
980
+ transition:
981
+ opacity 0.18s ease,
982
+ transform 0.18s ease;
983
+ }
984
+
985
+ .back-top-enter-from,
986
+ .back-top-leave-to {
987
+ opacity: 0;
988
+ transform: translateY(8px) scale(0.92);
989
+ }
990
+
991
+ @media (max-width: 340px) {
992
+ .amount-row {
993
+ gap: 5px;
994
+ }
995
+
996
+ .amount-input {
997
+ width: 0;
998
+ padding: 0 5px;
999
+ font-size: 11px;
1000
+ }
1001
+
1002
+ .row-label {
1003
+ width: 64px;
1004
+ flex-basis: 64px;
1005
+ margin-left: 9px;
1006
+ }
1007
+
1008
+ .amount-row .row-label {
1009
+ width: 52px;
1010
+ flex-basis: 52px;
1011
+ }
1012
+ }
1013
+ </style>