minitest2.0 0.0.5 → 0.0.6
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/api/announcement.ts +153 -0
- package/src/api/foreign-position.ts +142 -0
- package/src/router/index.ts +1 -1
- package/src/stores/user.ts +1 -1
- package/src/utils/foreign-forecast.ts +102 -0
- package/src/views/article-detail/index.vue +9 -0
- package/src/views/article-edit/index.vue +9 -0
- package/src/views/opening-reserve-estimate/index.vue +135 -44
- package/src/views/warning/index.vue +566 -12
- package/vite.config.ts +3 -1
package/package.json
CHANGED
package/src/api/announcement.ts
CHANGED
|
@@ -44,6 +44,7 @@ const ALLOWED_TAGS = new Set([
|
|
|
44
44
|
'H2',
|
|
45
45
|
'H3',
|
|
46
46
|
'I',
|
|
47
|
+
'IMG',
|
|
47
48
|
'LI',
|
|
48
49
|
'OL',
|
|
49
50
|
'P',
|
|
@@ -56,6 +57,22 @@ const ALLOWED_TAGS = new Set([
|
|
|
56
57
|
'CODE',
|
|
57
58
|
])
|
|
58
59
|
|
|
60
|
+
const ALLOWED_IMAGE_MIME_TYPES = new Set([
|
|
61
|
+
'image/png',
|
|
62
|
+
'image/jpeg',
|
|
63
|
+
'image/gif',
|
|
64
|
+
'image/webp',
|
|
65
|
+
'image/bmp',
|
|
66
|
+
])
|
|
67
|
+
|
|
68
|
+
const BASE64_IMAGE_SIGNATURES = [
|
|
69
|
+
{ mime: 'image/png', pattern: /^iVBORw0KGgo/ },
|
|
70
|
+
{ mime: 'image/jpeg', pattern: /^\/9j\// },
|
|
71
|
+
{ mime: 'image/gif', pattern: /^R0lGOD/ },
|
|
72
|
+
{ mime: 'image/webp', pattern: /^UklGR/ },
|
|
73
|
+
{ mime: 'image/bmp', pattern: /^Qk/ },
|
|
74
|
+
]
|
|
75
|
+
|
|
59
76
|
export async function queryArticles() {
|
|
60
77
|
const response = await request<unknown>({
|
|
61
78
|
url: LIST_URL,
|
|
@@ -334,6 +351,11 @@ function sanitizeAttributes(element: HTMLElement) {
|
|
|
334
351
|
Array.from(element.attributes).forEach((attribute) => {
|
|
335
352
|
const name = attribute.name.toLowerCase()
|
|
336
353
|
|
|
354
|
+
if (element.tagName === 'IMG') {
|
|
355
|
+
sanitizeImageAttribute(element, attribute)
|
|
356
|
+
return
|
|
357
|
+
}
|
|
358
|
+
|
|
337
359
|
if (element.tagName === 'A' && name === 'href') {
|
|
338
360
|
if (!isSafeHref(attribute.value)) {
|
|
339
361
|
element.removeAttribute(attribute.name)
|
|
@@ -369,6 +391,137 @@ function sanitizeAttributes(element: HTMLElement) {
|
|
|
369
391
|
|
|
370
392
|
element.removeAttribute(attribute.name)
|
|
371
393
|
})
|
|
394
|
+
|
|
395
|
+
if (element.tagName === 'IMG') {
|
|
396
|
+
finalizeImageElement(element)
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function sanitizeImageAttribute(element: HTMLElement, attribute: Attr) {
|
|
401
|
+
const name = attribute.name.toLowerCase()
|
|
402
|
+
|
|
403
|
+
if (name === 'src') {
|
|
404
|
+
const src = normalizeImageSource(attribute.value)
|
|
405
|
+
|
|
406
|
+
if (src) {
|
|
407
|
+
element.setAttribute('src', src)
|
|
408
|
+
return
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (name === 'alt' || name === 'title') {
|
|
413
|
+
element.setAttribute(name, attribute.value.trim().slice(0, 120))
|
|
414
|
+
return
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (name === 'width' || name === 'height') {
|
|
418
|
+
const dimension = normalizeImageDimension(attribute.value)
|
|
419
|
+
|
|
420
|
+
if (dimension) {
|
|
421
|
+
element.setAttribute(name, dimension)
|
|
422
|
+
return
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
element.removeAttribute(attribute.name)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function finalizeImageElement(element: HTMLElement) {
|
|
430
|
+
if (!element.getAttribute('src')) {
|
|
431
|
+
element.remove()
|
|
432
|
+
return
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (!element.hasAttribute('alt')) {
|
|
436
|
+
element.setAttribute('alt', '')
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
element.setAttribute('loading', 'lazy')
|
|
440
|
+
element.setAttribute('decoding', 'async')
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function normalizeImageSource(value: string) {
|
|
444
|
+
const source = value.trim()
|
|
445
|
+
|
|
446
|
+
if (!source) {
|
|
447
|
+
return ''
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const dataImageSource = normalizeDataImageSource(source)
|
|
451
|
+
|
|
452
|
+
if (dataImageSource) {
|
|
453
|
+
return dataImageSource
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const bareBase64Source = normalizeBareBase64ImageSource(source)
|
|
457
|
+
|
|
458
|
+
if (bareBase64Source) {
|
|
459
|
+
return bareBase64Source
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
try {
|
|
463
|
+
const url = new URL(source, window.location.origin)
|
|
464
|
+
|
|
465
|
+
return ['http:', 'https:'].includes(url.protocol) ? source : ''
|
|
466
|
+
} catch {
|
|
467
|
+
return ''
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function normalizeDataImageSource(value: string) {
|
|
472
|
+
const match = value.match(/^data:(image\/[a-z0-9.+-]+);base64,([\s\S]+)$/i)
|
|
473
|
+
|
|
474
|
+
if (!match) {
|
|
475
|
+
return ''
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const mime = normalizeImageMimeType(match[1] || '')
|
|
479
|
+
const base64 = normalizeBase64Payload(match[2] || '')
|
|
480
|
+
|
|
481
|
+
if (!mime || !isBase64Payload(base64)) {
|
|
482
|
+
return ''
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return `data:${mime};base64,${base64}`
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function normalizeBareBase64ImageSource(value: string) {
|
|
489
|
+
const base64 = normalizeBase64Payload(value)
|
|
490
|
+
const mime = getBase64ImageMimeType(base64)
|
|
491
|
+
|
|
492
|
+
if (!mime || !isBase64Payload(base64)) {
|
|
493
|
+
return ''
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return `data:${mime};base64,${base64}`
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function normalizeImageMimeType(value: string) {
|
|
500
|
+
const mime = value.trim().toLowerCase().replace(/^image\/jpg$/, 'image/jpeg')
|
|
501
|
+
|
|
502
|
+
return ALLOWED_IMAGE_MIME_TYPES.has(mime) ? mime : ''
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function normalizeBase64Payload(value: string) {
|
|
506
|
+
return value.replace(/\s+/g, '')
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function isBase64Payload(value: string) {
|
|
510
|
+
return Boolean(value) && value.length % 4 !== 1 && /^[A-Za-z0-9+/]+={0,2}$/.test(value)
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function getBase64ImageMimeType(value: string) {
|
|
514
|
+
return BASE64_IMAGE_SIGNATURES.find(({ pattern }) => pattern.test(value))?.mime || ''
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function normalizeImageDimension(value: string) {
|
|
518
|
+
const dimension = Number.parseInt(value, 10)
|
|
519
|
+
|
|
520
|
+
if (!Number.isFinite(dimension) || dimension <= 0 || dimension > 4096) {
|
|
521
|
+
return ''
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return String(dimension)
|
|
372
525
|
}
|
|
373
526
|
|
|
374
527
|
function normalizeCodeLanguageClass(value: string) {
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildTamRequestPayload,
|
|
3
|
+
tamRequest,
|
|
4
|
+
type TamRequestPayload,
|
|
5
|
+
type TamResponse,
|
|
6
|
+
} from '@/api/tam'
|
|
7
|
+
import { useAppStore } from '@/stores/app'
|
|
8
|
+
|
|
9
|
+
export type ForeignForecastRecord = {
|
|
10
|
+
/** 业务类型。 */
|
|
11
|
+
busiType?: string
|
|
12
|
+
/** 币种字段,不同后端版本可能返回不同命名。 */
|
|
13
|
+
ccy?: string
|
|
14
|
+
currency?: string
|
|
15
|
+
currencyCode?: string
|
|
16
|
+
currCode?: string
|
|
17
|
+
curType?: string
|
|
18
|
+
tranCcy?: string
|
|
19
|
+
/** 客户类型或客户名称,用于列表副标题兜底。 */
|
|
20
|
+
clientName?: string
|
|
21
|
+
clientType?: string
|
|
22
|
+
competName?: string
|
|
23
|
+
/** 交易对手或清算行信息。 */
|
|
24
|
+
bankSwift?: string
|
|
25
|
+
bicCode?: string
|
|
26
|
+
routeCode?: string
|
|
27
|
+
swiftCode?: string
|
|
28
|
+
/** 落地机构,列表主标题展示字段。 */
|
|
29
|
+
groundBranch?: string
|
|
30
|
+
/** 预报金额,接口按元级金额返回,页面转换为万元级展示。 */
|
|
31
|
+
predAmt?: string
|
|
32
|
+
/** 预报流水号,后续详情查询主键。 */
|
|
33
|
+
predRefNo?: string
|
|
34
|
+
/** 预报状态。 */
|
|
35
|
+
predStatus?: string
|
|
36
|
+
/** 交易日期。 */
|
|
37
|
+
tranDate?: string
|
|
38
|
+
/** 核销状态。 */
|
|
39
|
+
verifyStatus?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type ForeignForecastQueryForm = {
|
|
43
|
+
/** 交易时间,当前视图必填筛选项。 */
|
|
44
|
+
tranDate: string
|
|
45
|
+
/** 落地机构关键字。 */
|
|
46
|
+
groundBranch?: string
|
|
47
|
+
/** 币种,当前后端交易未明确入参时页面做兼容筛选。 */
|
|
48
|
+
currency?: string
|
|
49
|
+
/** 业务类型。 */
|
|
50
|
+
busiType?: string
|
|
51
|
+
/** 预报状态。 */
|
|
52
|
+
predStatus?: string
|
|
53
|
+
/** 起始金额,页面单位为万元。 */
|
|
54
|
+
startTranAmt?: string
|
|
55
|
+
/** 截止金额,页面单位为万元。 */
|
|
56
|
+
endTranAmt?: string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type ForeignForecastQueryPayload = {
|
|
60
|
+
appHead: TamRequestPayload<ForeignForecastQueryBody>['appHead']
|
|
61
|
+
body: ForeignForecastQueryBody
|
|
62
|
+
head: TamRequestPayload<ForeignForecastQueryBody>['head']
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type ForeignForecastQueryResult = {
|
|
66
|
+
body: ForeignForecastQueryResultBody
|
|
67
|
+
head: TamResponse<ForeignForecastQueryResultBody>['head']
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
type ForeignForecastQueryBody = {
|
|
71
|
+
busiType: string
|
|
72
|
+
clientName: string
|
|
73
|
+
competName: string
|
|
74
|
+
endTransAmt: string
|
|
75
|
+
groundBranch: string
|
|
76
|
+
payChannel: string
|
|
77
|
+
payDirec: string
|
|
78
|
+
predStatus: string
|
|
79
|
+
sortRule: string
|
|
80
|
+
startTransAmt: string
|
|
81
|
+
tranDate: string
|
|
82
|
+
verifyStatus: string
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
type ForeignForecastQueryResultBody = {
|
|
86
|
+
resultList: ForeignForecastRecord[]
|
|
87
|
+
totalCount: string
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
type QueryOptions = {
|
|
91
|
+
form: ForeignForecastQueryForm
|
|
92
|
+
pageIndex: number
|
|
93
|
+
pageSize: number
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const QUERY_TRAN_CODE = '/TAM/0001/140101001'
|
|
97
|
+
|
|
98
|
+
export function buildForeignForecastQueryPayload({
|
|
99
|
+
form,
|
|
100
|
+
pageIndex,
|
|
101
|
+
pageSize,
|
|
102
|
+
}: QueryOptions): ForeignForecastQueryPayload {
|
|
103
|
+
const tranDate = normalizeDate(form.tranDate || useAppStore().systemWorkDate)
|
|
104
|
+
|
|
105
|
+
return buildTamRequestPayload({
|
|
106
|
+
tranCode: QUERY_TRAN_CODE,
|
|
107
|
+
pageIndex,
|
|
108
|
+
pageSize,
|
|
109
|
+
body: {
|
|
110
|
+
busiType: form.busiType || '',
|
|
111
|
+
clientName: '',
|
|
112
|
+
competName: '',
|
|
113
|
+
endTransAmt: toRequestAmount(form.endTranAmt),
|
|
114
|
+
groundBranch: form.groundBranch || '',
|
|
115
|
+
payChannel: '',
|
|
116
|
+
payDirec: '',
|
|
117
|
+
predStatus: form.predStatus || '',
|
|
118
|
+
sortRule: '',
|
|
119
|
+
startTransAmt: toRequestAmount(form.startTranAmt),
|
|
120
|
+
tranDate,
|
|
121
|
+
verifyStatus: '',
|
|
122
|
+
},
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function queryForeignForecastPage(
|
|
127
|
+
payload: ForeignForecastQueryPayload,
|
|
128
|
+
): Promise<ForeignForecastQueryResult> {
|
|
129
|
+
return tamRequest<ForeignForecastQueryBody, ForeignForecastQueryResultBody>(payload)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function normalizeDate(value: string) {
|
|
133
|
+
if (/^\d{8}$/.test(value)) return value
|
|
134
|
+
return value.replaceAll('-', '')
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function toRequestAmount(value?: string) {
|
|
138
|
+
if (!value?.trim()) return ''
|
|
139
|
+
|
|
140
|
+
const amount = Number(value)
|
|
141
|
+
return Number.isFinite(amount) ? String(Math.round(amount * 10000)) : ''
|
|
142
|
+
}
|
package/src/router/index.ts
CHANGED
package/src/stores/user.ts
CHANGED
|
@@ -20,7 +20,7 @@ export const useUserStore = defineStore(
|
|
|
20
20
|
// -------------------------------- State --------------------------------
|
|
21
21
|
|
|
22
22
|
/** 登录凭证,登录成功后由后端返回,后续请求通过请求头携带 */
|
|
23
|
-
const token = ref('
|
|
23
|
+
const token = ref('')
|
|
24
24
|
const refreshToken = ref('')
|
|
25
25
|
const username = ref('')
|
|
26
26
|
const realName = ref('')
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export type ForeignCurrencyOption = {
|
|
2
|
+
label: string
|
|
3
|
+
shortLabel: string
|
|
4
|
+
value: string
|
|
5
|
+
unit: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type ForeignOption = {
|
|
9
|
+
label: string
|
|
10
|
+
value: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type ForeignStatusOption = ForeignOption & {
|
|
14
|
+
className: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const foreignCurrencyOptions: ForeignCurrencyOption[] = [
|
|
18
|
+
{ label: '全部币种', shortLabel: '全部币种', value: '', unit: '万元' },
|
|
19
|
+
{ label: '美元', shortLabel: '美元', value: 'USD', unit: '万美元' },
|
|
20
|
+
{ label: '欧元', shortLabel: '欧元', value: 'EUR', unit: '万欧元' },
|
|
21
|
+
{ label: '港币', shortLabel: '港币', value: 'HKD', unit: '万港币' },
|
|
22
|
+
{ label: '日元', shortLabel: '日元', value: 'JPY', unit: '万日元' },
|
|
23
|
+
{ label: '英镑', shortLabel: '英镑', value: 'GBP', unit: '万英镑' },
|
|
24
|
+
{ label: '加拿大元', shortLabel: '加元', value: 'CAD', unit: '万加元' },
|
|
25
|
+
{ label: '澳大利亚元', shortLabel: '澳元', value: 'AUD', unit: '万澳元' },
|
|
26
|
+
{ label: '新加坡元', shortLabel: '新元', value: 'SGD', unit: '万新元' },
|
|
27
|
+
{ label: '瑞士法郎', shortLabel: '瑞郎', value: 'CHF', unit: '万瑞郎' },
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
export const foreignBusinessOptions: ForeignOption[] = [
|
|
31
|
+
{ label: '全部业务类型', value: '' },
|
|
32
|
+
{ label: '普通汇款', value: '1' },
|
|
33
|
+
{ label: '外汇买卖', value: '2' },
|
|
34
|
+
{ label: '结售汇', value: '3' },
|
|
35
|
+
{ label: '资金调拨', value: '4' },
|
|
36
|
+
{ label: '同业业务', value: '5' },
|
|
37
|
+
{ label: '贸易融资', value: '6' },
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
export const foreignStatusOptions: ForeignStatusOption[] = [
|
|
41
|
+
{ label: '全部预报状态', value: '', className: 'status-tag-unknown' },
|
|
42
|
+
{ label: '已确认', value: '1', className: 'status-tag-confirmed' },
|
|
43
|
+
{ label: '待确认', value: '2', className: 'status-tag-pending' },
|
|
44
|
+
{ label: '已撤销', value: '3', className: 'status-tag-cancelled' },
|
|
45
|
+
{ label: '已核销', value: '4', className: 'status-tag-verified' },
|
|
46
|
+
{ label: '已驳回', value: '5', className: 'status-tag-rejected' },
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
export const unknownForeignStatus: ForeignStatusOption = {
|
|
50
|
+
label: '未知状态',
|
|
51
|
+
value: '',
|
|
52
|
+
className: 'status-tag-unknown',
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function resolveForeignCurrency(value?: string) {
|
|
56
|
+
const normalizedValue = value?.trim().toUpperCase()
|
|
57
|
+
|
|
58
|
+
if (!normalizedValue) {
|
|
59
|
+
return foreignCurrencyOptions[0]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
foreignCurrencyOptions.find((item) => item.value === normalizedValue) || {
|
|
64
|
+
label: normalizedValue,
|
|
65
|
+
shortLabel: normalizedValue,
|
|
66
|
+
value: normalizedValue,
|
|
67
|
+
unit: `万${normalizedValue}`,
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function resolveForeignBusinessType(value?: string) {
|
|
73
|
+
if (!value?.trim()) return '普通汇款'
|
|
74
|
+
|
|
75
|
+
return foreignBusinessOptions.find((item) => item.value === value)?.label || value
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function resolveForeignStatus(value?: string) {
|
|
79
|
+
if (!value?.trim()) {
|
|
80
|
+
return unknownForeignStatus
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return foreignStatusOptions.find((item) => item.value === value) || unknownForeignStatus
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function formatForeignForecastDate(value?: string) {
|
|
87
|
+
if (value && /^\d{8}$/.test(value)) {
|
|
88
|
+
return `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}`
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return value || '-'
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function toTenThousandAmount(value?: string) {
|
|
95
|
+
const amount = Number(value?.replaceAll(',', '') || 0)
|
|
96
|
+
return Number.isFinite(amount) ? amount / 10000 : 0
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function formatForeignForecastAmount(value: number) {
|
|
100
|
+
const sign = value >= 0 ? '+' : '-'
|
|
101
|
+
return `${sign}${Math.abs(value).toFixed(2)}`
|
|
102
|
+
}
|
|
@@ -219,6 +219,15 @@ function articleContentToText(html: string) {
|
|
|
219
219
|
text-decoration: none;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
.article-content :deep(img) {
|
|
223
|
+
display: block;
|
|
224
|
+
max-width: 100%;
|
|
225
|
+
height: auto;
|
|
226
|
+
margin: 8px auto 14px;
|
|
227
|
+
border-radius: 6px;
|
|
228
|
+
object-fit: contain;
|
|
229
|
+
}
|
|
230
|
+
|
|
222
231
|
.article-content :deep(pre) {
|
|
223
232
|
position: relative;
|
|
224
233
|
overflow-x: auto;
|
|
@@ -514,6 +514,15 @@ function normalizeCodeLanguage(value: string) {
|
|
|
514
514
|
background: #f6f7f9;
|
|
515
515
|
}
|
|
516
516
|
|
|
517
|
+
.rich-editor :deep(img) {
|
|
518
|
+
display: block;
|
|
519
|
+
max-width: 100%;
|
|
520
|
+
height: auto;
|
|
521
|
+
margin: 8px auto 12px;
|
|
522
|
+
border-radius: 6px;
|
|
523
|
+
object-fit: contain;
|
|
524
|
+
}
|
|
525
|
+
|
|
517
526
|
.rich-editor :deep(pre) {
|
|
518
527
|
overflow-x: auto;
|
|
519
528
|
margin: 0 0 12px;
|
|
@@ -14,22 +14,46 @@ type ResultItem = {
|
|
|
14
14
|
icon: string
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
type AmountUnitKey = 'hundredMillion' | 'tenMillion' | 'million' | 'tenThousand' | 'yuan'
|
|
18
|
+
|
|
19
|
+
type AmountUnit = {
|
|
20
|
+
key: AmountUnitKey
|
|
21
|
+
label: string
|
|
22
|
+
divisor: bigint
|
|
23
|
+
}
|
|
24
|
+
|
|
17
25
|
const estimateDetail = ref<OpeningReserveEstimateBody | null>(null)
|
|
18
26
|
const querying = ref(false)
|
|
27
|
+
const selectedUnitKey = ref<AmountUnitKey>('yuan')
|
|
28
|
+
|
|
29
|
+
const defaultAmountUnit: AmountUnit = { key: 'yuan', label: '元', divisor: 1n }
|
|
30
|
+
|
|
31
|
+
const amountUnits: AmountUnit[] = [
|
|
32
|
+
{ key: 'hundredMillion', label: '亿元', divisor: 100000000n },
|
|
33
|
+
{ key: 'tenMillion', label: '千万元', divisor: 10000000n },
|
|
34
|
+
{ key: 'million', label: '百万元', divisor: 1000000n },
|
|
35
|
+
{ key: 'tenThousand', label: '万元', divisor: 10000n },
|
|
36
|
+
defaultAmountUnit,
|
|
37
|
+
]
|
|
19
38
|
|
|
20
39
|
const resultConfig: ResultItem[] = [
|
|
21
|
-
{ key: 'rhAmt', label: '
|
|
22
|
-
{ key: 'unverifyAmtOut', label: '
|
|
23
|
-
{ key: 'unverifyAmtIn', label: '
|
|
24
|
-
{ key: 'pdAmt', label: '
|
|
25
|
-
{ key: 'fzAmt', label: '
|
|
26
|
-
{ key: 'preAmt', label: '
|
|
40
|
+
{ key: 'rhAmt', label: '人行时点头寸', icon: 'balance-o' },
|
|
41
|
+
{ key: 'unverifyAmtOut', label: '预报未核销支出', icon: 'card' },
|
|
42
|
+
{ key: 'unverifyAmtIn', label: '预报未核销收入', icon: 'gold-coin-o' },
|
|
43
|
+
{ key: 'pdAmt', label: '总分行铺底资金之和', icon: 'paid' },
|
|
44
|
+
{ key: 'fzAmt', label: '法定准备金', icon: 'shield-o' },
|
|
45
|
+
{ key: 'preAmt', label: '日初备款', icon: 'chart-trending-o' },
|
|
27
46
|
]
|
|
28
47
|
|
|
48
|
+
const selectedUnit = computed(
|
|
49
|
+
() => amountUnits.find((unit) => unit.key === selectedUnitKey.value) || defaultAmountUnit,
|
|
50
|
+
)
|
|
51
|
+
|
|
29
52
|
const resultItems = computed(() =>
|
|
30
53
|
resultConfig.map((item) => ({
|
|
31
54
|
...item,
|
|
32
|
-
|
|
55
|
+
label: `${item.label}(${selectedUnit.value.label})`,
|
|
56
|
+
value: formatAmountByUnit(estimateDetail.value?.[item.key], selectedUnit.value),
|
|
33
57
|
})),
|
|
34
58
|
)
|
|
35
59
|
|
|
@@ -62,64 +86,65 @@ async function handleQuery(options: { showSuccessToast?: boolean } = {}) {
|
|
|
62
86
|
}
|
|
63
87
|
}
|
|
64
88
|
|
|
65
|
-
function
|
|
89
|
+
function formatAmountByUnit(value: string | undefined, unit: AmountUnit) {
|
|
90
|
+
const cents = parseYuanToCents(value)
|
|
91
|
+
|
|
92
|
+
if (cents === null) return '-'
|
|
93
|
+
|
|
94
|
+
return formatMinorAmount(divideRounded(cents, unit.divisor))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function parseYuanToCents(value?: string) {
|
|
66
98
|
const text = value?.trim()
|
|
67
99
|
|
|
68
|
-
if (!text) return
|
|
100
|
+
if (!text) return null
|
|
69
101
|
|
|
70
102
|
const normalizedText = text.replaceAll(',', '')
|
|
71
103
|
|
|
72
104
|
if (!/^[-+]?\d+(\.\d+)?$/.test(normalizedText)) {
|
|
73
|
-
return
|
|
105
|
+
return null
|
|
74
106
|
}
|
|
75
107
|
|
|
76
|
-
const
|
|
108
|
+
const isNegative = normalizedText.startsWith('-')
|
|
77
109
|
const unsignedText =
|
|
78
110
|
normalizedText.startsWith('-') || normalizedText.startsWith('+')
|
|
79
111
|
? normalizedText.slice(1)
|
|
80
112
|
: normalizedText
|
|
81
113
|
const [rawInteger = '0', rawDecimal = ''] = unsignedText.split('.')
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
114
|
+
const integer = rawInteger.replace(/^0+(?=\d)/, '') || '0'
|
|
115
|
+
const decimalText = `${rawDecimal}000`
|
|
116
|
+
const decimal = decimalText.slice(0, 2)
|
|
117
|
+
const needsRound = Number(decimalText[2]) >= 5
|
|
118
|
+
let cents = BigInt(integer) * 100n + BigInt(decimal)
|
|
119
|
+
|
|
120
|
+
if (needsRound) {
|
|
121
|
+
cents += 1n
|
|
89
122
|
}
|
|
90
123
|
|
|
91
|
-
return
|
|
124
|
+
return isNegative ? -cents : cents
|
|
92
125
|
}
|
|
93
126
|
|
|
94
|
-
function
|
|
95
|
-
const
|
|
127
|
+
function divideRounded(value: bigint, divisor: bigint) {
|
|
128
|
+
const isNegative = value < 0n
|
|
129
|
+
const absoluteValue = isNegative ? -value : value
|
|
130
|
+
let quotient = absoluteValue / divisor
|
|
131
|
+
const remainder = absoluteValue % divisor
|
|
96
132
|
|
|
97
|
-
if (
|
|
98
|
-
|
|
99
|
-
integer,
|
|
100
|
-
decimal: String(centAmount).padStart(2, '0'),
|
|
101
|
-
}
|
|
133
|
+
if (remainder * 2n >= divisor) {
|
|
134
|
+
quotient += 1n
|
|
102
135
|
}
|
|
103
136
|
|
|
104
|
-
return
|
|
105
|
-
integer: incrementIntegerText(integer),
|
|
106
|
-
decimal: '00',
|
|
107
|
-
}
|
|
137
|
+
return isNegative ? -quotient : quotient
|
|
108
138
|
}
|
|
109
139
|
|
|
110
|
-
function
|
|
111
|
-
const
|
|
140
|
+
function formatMinorAmount(value: bigint) {
|
|
141
|
+
const isNegative = value < 0n
|
|
142
|
+
const absoluteValue = isNegative ? -value : value
|
|
143
|
+
const integer = absoluteValue / 100n
|
|
144
|
+
const decimal = String(absoluteValue % 100n).padStart(2, '0')
|
|
145
|
+
const groupedInteger = integer.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
|
112
146
|
|
|
113
|
-
|
|
114
|
-
if (digits[index] !== '9') {
|
|
115
|
-
digits[index] = String(Number(digits[index]) + 1)
|
|
116
|
-
return digits.join('')
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
digits[index] = '0'
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return `1${digits.join('')}`
|
|
147
|
+
return `${isNegative ? '-' : ''}${groupedInteger}.${decimal}`
|
|
123
148
|
}
|
|
124
149
|
|
|
125
150
|
function getErrorMessage(error: unknown, fallback: string) {
|
|
@@ -131,8 +156,21 @@ function getErrorMessage(error: unknown, fallback: string) {
|
|
|
131
156
|
<main class="opening-reserve-page">
|
|
132
157
|
<section class="result-section" aria-label="日初备款数匡算查询结果">
|
|
133
158
|
<div class="section-heading">
|
|
134
|
-
<
|
|
135
|
-
|
|
159
|
+
<div class="section-heading__title">
|
|
160
|
+
<span class="section-heading__mark" aria-hidden="true"></span>
|
|
161
|
+
<h2>查询结果</h2>
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
<label class="unit-switch">
|
|
165
|
+
<span>单位</span>
|
|
166
|
+
<em>{{ selectedUnit.label }}</em>
|
|
167
|
+
<select v-model="selectedUnitKey" aria-label="选择金额单位">
|
|
168
|
+
<option v-for="unit in amountUnits" :key="unit.key" :value="unit.key">
|
|
169
|
+
{{ unit.label }}
|
|
170
|
+
</option>
|
|
171
|
+
</select>
|
|
172
|
+
<van-icon name="arrow-down" aria-hidden="true" />
|
|
173
|
+
</label>
|
|
136
174
|
</div>
|
|
137
175
|
|
|
138
176
|
<div class="result-stack">
|
|
@@ -185,9 +223,17 @@ button {
|
|
|
185
223
|
.section-heading {
|
|
186
224
|
display: flex;
|
|
187
225
|
align-items: center;
|
|
226
|
+
justify-content: space-between;
|
|
227
|
+
gap: 12px;
|
|
188
228
|
margin-bottom: 21px;
|
|
189
229
|
}
|
|
190
230
|
|
|
231
|
+
.section-heading__title {
|
|
232
|
+
display: flex;
|
|
233
|
+
align-items: center;
|
|
234
|
+
min-width: 0;
|
|
235
|
+
}
|
|
236
|
+
|
|
191
237
|
.section-heading__mark {
|
|
192
238
|
width: 7px;
|
|
193
239
|
height: 24px;
|
|
@@ -206,6 +252,51 @@ button {
|
|
|
206
252
|
letter-spacing: 0;
|
|
207
253
|
}
|
|
208
254
|
|
|
255
|
+
.unit-switch {
|
|
256
|
+
position: relative;
|
|
257
|
+
display: inline-flex;
|
|
258
|
+
align-items: center;
|
|
259
|
+
flex: 0 0 auto;
|
|
260
|
+
height: 30px;
|
|
261
|
+
padding: 0 28px 0 11px;
|
|
262
|
+
border: 1px solid #ffd5da;
|
|
263
|
+
border-radius: 999px;
|
|
264
|
+
color: #ef252d;
|
|
265
|
+
font-size: 12px;
|
|
266
|
+
font-weight: 560;
|
|
267
|
+
line-height: 1;
|
|
268
|
+
background: #fff7f8;
|
|
269
|
+
box-shadow: 0 5px 14px rgba(255, 43, 50, 0.08);
|
|
270
|
+
box-sizing: border-box;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.unit-switch span {
|
|
274
|
+
margin-right: 4px;
|
|
275
|
+
color: #8c8f98;
|
|
276
|
+
font-weight: 500;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.unit-switch em {
|
|
280
|
+
font-style: normal;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.unit-switch select {
|
|
284
|
+
position: absolute;
|
|
285
|
+
inset: 0;
|
|
286
|
+
z-index: 2;
|
|
287
|
+
width: 100%;
|
|
288
|
+
height: 100%;
|
|
289
|
+
opacity: 0;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.unit-switch :deep(.van-icon) {
|
|
293
|
+
position: absolute;
|
|
294
|
+
right: 10px;
|
|
295
|
+
color: #ef252d;
|
|
296
|
+
font-size: 12px;
|
|
297
|
+
pointer-events: none;
|
|
298
|
+
}
|
|
299
|
+
|
|
209
300
|
.result-stack {
|
|
210
301
|
display: block;
|
|
211
302
|
}
|
|
@@ -1,26 +1,580 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineOptions({ name: 'WarningPage' })
|
|
3
|
+
|
|
4
|
+
type WarningDetail = {
|
|
5
|
+
title: string
|
|
6
|
+
status: string
|
|
7
|
+
id: string
|
|
8
|
+
time: string
|
|
9
|
+
type: string
|
|
10
|
+
source: string
|
|
11
|
+
messagePrefix: string
|
|
12
|
+
amount: string
|
|
13
|
+
messageSuffix: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const warningDetail: WarningDetail = {
|
|
17
|
+
title: '净借记限额预警',
|
|
18
|
+
status: '有效',
|
|
19
|
+
id: '167749751028801',
|
|
20
|
+
time: '2026-07-07 17:33:50',
|
|
21
|
+
type: '净借记限额预警',
|
|
22
|
+
source: '头寸预警',
|
|
23
|
+
messagePrefix: '小额可用净借记限额余额低于',
|
|
24
|
+
amount: '60000亿元',
|
|
25
|
+
messageSuffix: '!',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const detailRows = [
|
|
29
|
+
{ label: '预警ID', value: warningDetail.id, icon: 'description' },
|
|
30
|
+
{ label: '预警时间', value: warningDetail.time, icon: 'clock-o' },
|
|
31
|
+
{ label: '预警类型', value: warningDetail.type, icon: 'apps-o' },
|
|
32
|
+
]
|
|
33
|
+
</script>
|
|
34
|
+
|
|
1
35
|
<template>
|
|
2
|
-
<main class="page">
|
|
3
|
-
<section class="
|
|
4
|
-
<
|
|
36
|
+
<main class="warning-page">
|
|
37
|
+
<section class="warning-hero" aria-label="预警信息查询">
|
|
38
|
+
<div class="hero-alert" aria-hidden="true">
|
|
39
|
+
<span class="shield-mark shield-mark--large"></span>
|
|
40
|
+
<span class="alert-underline"></span>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div class="hero-copy">
|
|
44
|
+
<h1>预警信息查询</h1>
|
|
45
|
+
<p>及时掌握头寸预警信息,防范资金风险</p>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div class="hero-scene" aria-hidden="true">
|
|
49
|
+
<span class="bank-roof"></span>
|
|
50
|
+
<span class="bank-base"></span>
|
|
51
|
+
<span class="bank-step"></span>
|
|
52
|
+
<span class="bank-column bank-column--1"></span>
|
|
53
|
+
<span class="bank-column bank-column--2"></span>
|
|
54
|
+
<span class="bank-column bank-column--3"></span>
|
|
55
|
+
<span class="bank-column bank-column--4"></span>
|
|
56
|
+
<span class="shield-mark shield-mark--small"></span>
|
|
57
|
+
</div>
|
|
5
58
|
</section>
|
|
59
|
+
|
|
60
|
+
<section class="warning-detail" aria-labelledby="warning-detail-title">
|
|
61
|
+
<div class="section-title">
|
|
62
|
+
<span class="section-title-mark"></span>
|
|
63
|
+
<h2 id="warning-detail-title">预警详情</h2>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<article class="warning-card" aria-label="净借记限额预警详情">
|
|
67
|
+
<header class="warning-card-header">
|
|
68
|
+
<span class="bank-badge" aria-hidden="true">
|
|
69
|
+
<van-icon name="hotel-o" />
|
|
70
|
+
</span>
|
|
71
|
+
<h3>{{ warningDetail.title }}</h3>
|
|
72
|
+
<span class="status-badge">{{ warningDetail.status }}</span>
|
|
73
|
+
</header>
|
|
74
|
+
|
|
75
|
+
<dl class="warning-fields">
|
|
76
|
+
<div v-for="row in detailRows" :key="row.label" class="warning-field-row">
|
|
77
|
+
<dt>
|
|
78
|
+
<span class="field-icon" aria-hidden="true">
|
|
79
|
+
<van-icon :name="row.icon" />
|
|
80
|
+
</span>
|
|
81
|
+
<span>{{ row.label }}</span>
|
|
82
|
+
</dt>
|
|
83
|
+
<dd>{{ row.value }}</dd>
|
|
84
|
+
</div>
|
|
85
|
+
</dl>
|
|
86
|
+
|
|
87
|
+
<section class="warning-message" aria-label="预警内容">
|
|
88
|
+
<span class="message-icon" aria-hidden="true">
|
|
89
|
+
<van-icon name="warning-o" />
|
|
90
|
+
</span>
|
|
91
|
+
<div class="message-copy">
|
|
92
|
+
<strong>{{ warningDetail.source }}</strong>
|
|
93
|
+
<p>
|
|
94
|
+
{{ warningDetail.messagePrefix }}<em>{{ warningDetail.amount }}</em
|
|
95
|
+
>{{ warningDetail.messageSuffix }}
|
|
96
|
+
</p>
|
|
97
|
+
</div>
|
|
98
|
+
</section>
|
|
99
|
+
</article>
|
|
100
|
+
</section>
|
|
101
|
+
|
|
102
|
+
<p class="end-text" aria-label="列表结束">
|
|
103
|
+
<span></span>
|
|
104
|
+
已到底,没有更多数据
|
|
105
|
+
<span></span>
|
|
106
|
+
</p>
|
|
6
107
|
</main>
|
|
7
108
|
</template>
|
|
8
109
|
|
|
9
110
|
<style scoped>
|
|
10
|
-
.page {
|
|
111
|
+
.warning-page {
|
|
112
|
+
width: 100%;
|
|
11
113
|
max-width: 750px;
|
|
12
114
|
min-height: var(--app-page-min-height, 100vh);
|
|
13
115
|
margin: 0 auto;
|
|
14
|
-
padding:
|
|
15
|
-
|
|
116
|
+
padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 40px);
|
|
117
|
+
color: #171b20;
|
|
118
|
+
background:
|
|
119
|
+
radial-gradient(circle at 86% 8%, rgba(255, 223, 226, 0.8), transparent 94px),
|
|
120
|
+
linear-gradient(180deg, #fff6f6 0, #ffffff 178px, #ffffff 100%);
|
|
121
|
+
box-sizing: border-box;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.warning-hero {
|
|
125
|
+
position: relative;
|
|
126
|
+
display: grid;
|
|
127
|
+
grid-template-columns: 78px minmax(0, 1fr);
|
|
128
|
+
min-height: 120px;
|
|
129
|
+
overflow: hidden;
|
|
130
|
+
padding: 27px 17px 18px;
|
|
131
|
+
border-top: 1px solid #ffe1e2;
|
|
132
|
+
background:
|
|
133
|
+
linear-gradient(160deg, rgba(255, 255, 255, 0.92) 0%, rgba(255, 242, 243, 0.55) 100%),
|
|
134
|
+
linear-gradient(
|
|
135
|
+
100deg,
|
|
136
|
+
rgba(255, 255, 255, 0) 0 68%,
|
|
137
|
+
rgba(255, 85, 94, 0.08) 68% 76%,
|
|
138
|
+
transparent 76%
|
|
139
|
+
);
|
|
140
|
+
box-sizing: border-box;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.warning-hero::after {
|
|
144
|
+
position: absolute;
|
|
145
|
+
right: -28px;
|
|
146
|
+
top: 11px;
|
|
147
|
+
width: 160px;
|
|
148
|
+
height: 98px;
|
|
149
|
+
background:
|
|
150
|
+
linear-gradient(155deg, transparent 0 42%, rgba(255, 92, 99, 0.08) 42% 54%, transparent 54%),
|
|
151
|
+
linear-gradient(24deg, transparent 0 38%, rgba(255, 92, 99, 0.08) 38% 50%, transparent 50%);
|
|
152
|
+
content: '';
|
|
153
|
+
pointer-events: none;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.hero-alert {
|
|
157
|
+
position: relative;
|
|
158
|
+
z-index: 1;
|
|
159
|
+
min-width: 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.shield-mark {
|
|
163
|
+
position: relative;
|
|
164
|
+
display: block;
|
|
165
|
+
clip-path: polygon(50% 0, 94% 18%, 91% 67%, 50% 100%, 9% 67%, 6% 18%);
|
|
166
|
+
background: linear-gradient(145deg, #ff5e64 0%, #ee1f2b 64%, #f7434b 100%);
|
|
167
|
+
box-shadow: 0 8px 18px rgba(238, 31, 43, 0.18);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.shield-mark::before {
|
|
171
|
+
position: absolute;
|
|
172
|
+
left: 50%;
|
|
173
|
+
top: 23%;
|
|
174
|
+
width: 5px;
|
|
175
|
+
height: 21px;
|
|
176
|
+
border-radius: 999px;
|
|
177
|
+
background: #ffffff;
|
|
178
|
+
content: '';
|
|
179
|
+
transform: translateX(-50%);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.shield-mark::after {
|
|
183
|
+
position: absolute;
|
|
184
|
+
left: 50%;
|
|
185
|
+
bottom: 22%;
|
|
186
|
+
width: 7px;
|
|
187
|
+
height: 7px;
|
|
188
|
+
border-radius: 50%;
|
|
189
|
+
background: #ffffff;
|
|
190
|
+
content: '';
|
|
191
|
+
transform: translateX(-50%);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.shield-mark--large {
|
|
195
|
+
width: 34px;
|
|
196
|
+
height: 40px;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.alert-underline {
|
|
200
|
+
display: block;
|
|
201
|
+
width: 36px;
|
|
202
|
+
height: 4px;
|
|
203
|
+
margin-top: 18px;
|
|
204
|
+
border-radius: 999px;
|
|
205
|
+
background: linear-gradient(90deg, #ff202b 0%, #ff696e 100%);
|
|
206
|
+
box-shadow: 0 5px 12px rgba(255, 42, 50, 0.18);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.hero-copy {
|
|
210
|
+
position: relative;
|
|
211
|
+
z-index: 1;
|
|
212
|
+
min-width: 0;
|
|
213
|
+
padding-top: 2px;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.hero-copy h1 {
|
|
217
|
+
margin: 0;
|
|
218
|
+
color: #181d22;
|
|
219
|
+
font-size: 24px;
|
|
220
|
+
font-weight: 800;
|
|
221
|
+
line-height: 1.18;
|
|
222
|
+
letter-spacing: 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.hero-copy p {
|
|
226
|
+
margin: 9px 0 0;
|
|
227
|
+
color: #73777f;
|
|
228
|
+
font-size: 13px;
|
|
229
|
+
font-weight: 500;
|
|
230
|
+
line-height: 1.45;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.hero-scene {
|
|
234
|
+
position: absolute;
|
|
235
|
+
right: 18px;
|
|
236
|
+
top: 26px;
|
|
237
|
+
z-index: 0;
|
|
238
|
+
width: 92px;
|
|
239
|
+
height: 68px;
|
|
240
|
+
opacity: 0.62;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.bank-roof {
|
|
244
|
+
position: absolute;
|
|
245
|
+
left: 16px;
|
|
246
|
+
top: 0;
|
|
247
|
+
width: 67px;
|
|
248
|
+
height: 20px;
|
|
249
|
+
clip-path: polygon(50% 0, 100% 72%, 100% 100%, 0 100%, 0 72%);
|
|
250
|
+
background: linear-gradient(180deg, rgba(250, 85, 94, 0.32), rgba(250, 85, 94, 0.14));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.bank-base,
|
|
254
|
+
.bank-step {
|
|
255
|
+
position: absolute;
|
|
256
|
+
right: 0;
|
|
257
|
+
border-radius: 3px;
|
|
258
|
+
background: rgba(247, 86, 94, 0.2);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.bank-base {
|
|
262
|
+
bottom: 12px;
|
|
263
|
+
width: 78px;
|
|
264
|
+
height: 8px;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.bank-step {
|
|
268
|
+
bottom: 3px;
|
|
269
|
+
width: 92px;
|
|
270
|
+
height: 6px;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.bank-column {
|
|
274
|
+
position: absolute;
|
|
275
|
+
top: 25px;
|
|
276
|
+
width: 11px;
|
|
277
|
+
height: 31px;
|
|
278
|
+
border-radius: 3px 3px 0 0;
|
|
279
|
+
background: rgba(247, 86, 94, 0.18);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.bank-column--1 {
|
|
283
|
+
left: 25px;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.bank-column--2 {
|
|
287
|
+
left: 42px;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.bank-column--3 {
|
|
291
|
+
left: 59px;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.bank-column--4 {
|
|
295
|
+
left: 76px;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.shield-mark--small {
|
|
299
|
+
position: absolute;
|
|
300
|
+
left: 0;
|
|
301
|
+
bottom: 0;
|
|
302
|
+
width: 31px;
|
|
303
|
+
height: 36px;
|
|
304
|
+
opacity: 0.78;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.shield-mark--small::before {
|
|
308
|
+
width: 4px;
|
|
309
|
+
height: 17px;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.shield-mark--small::after {
|
|
313
|
+
width: 6px;
|
|
314
|
+
height: 6px;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.warning-detail {
|
|
318
|
+
padding: 22px 17px 0;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.section-title {
|
|
322
|
+
display: flex;
|
|
323
|
+
align-items: center;
|
|
324
|
+
gap: 8px;
|
|
325
|
+
height: 24px;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.section-title-mark {
|
|
329
|
+
width: 4px;
|
|
330
|
+
height: 17px;
|
|
331
|
+
border-radius: 999px;
|
|
332
|
+
background: linear-gradient(180deg, #ff1f2b 0%, #ff4c53 100%);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.section-title h2 {
|
|
336
|
+
margin: 0;
|
|
337
|
+
color: #151a20;
|
|
338
|
+
font-size: 17px;
|
|
339
|
+
font-weight: 800;
|
|
340
|
+
line-height: 1;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.warning-card {
|
|
344
|
+
margin-top: 18px;
|
|
345
|
+
padding: 18px 13px 15px;
|
|
346
|
+
border: 1px solid #eceef2;
|
|
347
|
+
border-radius: 9px;
|
|
348
|
+
background: rgba(255, 255, 255, 0.98);
|
|
349
|
+
box-shadow:
|
|
350
|
+
0 13px 24px rgba(31, 37, 51, 0.08),
|
|
351
|
+
0 2px 6px rgba(31, 37, 51, 0.04);
|
|
352
|
+
box-sizing: border-box;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.warning-card-header {
|
|
356
|
+
display: grid;
|
|
357
|
+
grid-template-columns: 46px minmax(0, 1fr) auto;
|
|
358
|
+
align-items: center;
|
|
359
|
+
gap: 12px;
|
|
360
|
+
padding-bottom: 17px;
|
|
361
|
+
border-bottom: 1px dashed #e4e5e9;
|
|
16
362
|
}
|
|
17
|
-
|
|
18
|
-
|
|
363
|
+
|
|
364
|
+
.bank-badge {
|
|
365
|
+
display: inline-flex;
|
|
366
|
+
align-items: center;
|
|
367
|
+
justify-content: center;
|
|
368
|
+
width: 40px;
|
|
369
|
+
height: 40px;
|
|
370
|
+
border-radius: 50%;
|
|
371
|
+
color: #f23a41;
|
|
372
|
+
font-size: 25px;
|
|
373
|
+
background: #ffe7e9;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.warning-card-header h3 {
|
|
377
|
+
min-width: 0;
|
|
378
|
+
margin: 0;
|
|
379
|
+
color: #161b21;
|
|
380
|
+
font-size: 16px;
|
|
381
|
+
font-weight: 800;
|
|
382
|
+
line-height: 1.2;
|
|
383
|
+
overflow-wrap: anywhere;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.status-badge {
|
|
387
|
+
display: inline-flex;
|
|
388
|
+
align-items: center;
|
|
389
|
+
justify-content: center;
|
|
390
|
+
min-width: 47px;
|
|
391
|
+
min-height: 31px;
|
|
392
|
+
padding: 0 9px;
|
|
393
|
+
border: 1px solid #ffb8bb;
|
|
394
|
+
border-radius: 4px;
|
|
395
|
+
color: #ed2832;
|
|
396
|
+
font-size: 16px;
|
|
397
|
+
font-weight: 500;
|
|
398
|
+
line-height: 1;
|
|
399
|
+
background: #fff0f1;
|
|
400
|
+
box-sizing: border-box;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.warning-fields {
|
|
404
|
+
margin: 0;
|
|
405
|
+
padding: 14px 0 7px;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.warning-field-row {
|
|
409
|
+
display: grid;
|
|
410
|
+
grid-template-columns: minmax(0, 112px) minmax(0, 1fr);
|
|
411
|
+
align-items: center;
|
|
412
|
+
min-height: 46px;
|
|
413
|
+
column-gap: 12px;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.warning-field-row + .warning-field-row {
|
|
417
|
+
border-top: 1px solid #edf0f2;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.warning-field-row dt {
|
|
421
|
+
display: flex;
|
|
422
|
+
align-items: center;
|
|
423
|
+
min-width: 0;
|
|
424
|
+
margin: 0;
|
|
425
|
+
color: #666b73;
|
|
426
|
+
font-size: 15px;
|
|
427
|
+
font-weight: 500;
|
|
428
|
+
line-height: 1.2;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.field-icon {
|
|
432
|
+
display: inline-flex;
|
|
433
|
+
align-items: center;
|
|
434
|
+
justify-content: center;
|
|
435
|
+
width: 28px;
|
|
436
|
+
height: 28px;
|
|
437
|
+
flex: 0 0 28px;
|
|
438
|
+
margin-right: 12px;
|
|
439
|
+
border-radius: 8px;
|
|
440
|
+
color: #ffffff;
|
|
441
|
+
font-size: 17px;
|
|
442
|
+
background: linear-gradient(145deg, #aeb3bd, #858b96);
|
|
19
443
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
444
|
+
|
|
445
|
+
.warning-field-row dd {
|
|
446
|
+
min-width: 0;
|
|
447
|
+
margin: 0;
|
|
448
|
+
color: #131820;
|
|
449
|
+
font-size: 15px;
|
|
450
|
+
font-weight: 500;
|
|
451
|
+
line-height: 1.25;
|
|
452
|
+
text-align: right;
|
|
453
|
+
overflow-wrap: anywhere;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.warning-message {
|
|
457
|
+
display: grid;
|
|
458
|
+
grid-template-columns: 39px minmax(0, 1fr);
|
|
459
|
+
gap: 10px;
|
|
460
|
+
min-height: 68px;
|
|
461
|
+
margin-top: 4px;
|
|
462
|
+
padding: 14px 12px 12px;
|
|
463
|
+
border: 1px solid #ffbdc1;
|
|
464
|
+
border-radius: 7px;
|
|
465
|
+
background:
|
|
466
|
+
radial-gradient(circle at 95% 82%, rgba(255, 229, 231, 0.74), transparent 72px),
|
|
467
|
+
linear-gradient(135deg, #fffafa 0%, #fff4f4 100%);
|
|
468
|
+
box-sizing: border-box;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.message-icon {
|
|
472
|
+
display: inline-flex;
|
|
473
|
+
align-items: center;
|
|
474
|
+
justify-content: center;
|
|
475
|
+
width: 32px;
|
|
476
|
+
height: 42px;
|
|
477
|
+
color: #f23a41;
|
|
478
|
+
font-size: 29px;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.message-copy {
|
|
482
|
+
min-width: 0;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.message-copy strong {
|
|
486
|
+
display: block;
|
|
487
|
+
color: #e23239;
|
|
488
|
+
font-size: 15px;
|
|
489
|
+
font-weight: 740;
|
|
490
|
+
line-height: 1.25;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.message-copy p {
|
|
494
|
+
margin: 9px 0 0;
|
|
495
|
+
color: #131820;
|
|
496
|
+
font-size: 16px;
|
|
497
|
+
font-weight: 600;
|
|
498
|
+
line-height: 1.4;
|
|
499
|
+
overflow-wrap: anywhere;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.message-copy em {
|
|
503
|
+
color: #e42d36;
|
|
504
|
+
font-style: normal;
|
|
505
|
+
font-weight: 800;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
.end-text {
|
|
509
|
+
display: flex;
|
|
510
|
+
align-items: center;
|
|
511
|
+
justify-content: center;
|
|
512
|
+
gap: 10px;
|
|
513
|
+
margin: 37px 0 0;
|
|
514
|
+
color: #888d96;
|
|
23
515
|
font-size: 14px;
|
|
24
|
-
|
|
516
|
+
font-weight: 500;
|
|
517
|
+
line-height: 1;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.end-text span {
|
|
521
|
+
width: 23px;
|
|
522
|
+
height: 1px;
|
|
523
|
+
background: #c8cbd1;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
@media (max-width: 350px) {
|
|
527
|
+
.warning-hero {
|
|
528
|
+
grid-template-columns: 58px minmax(0, 1fr);
|
|
529
|
+
padding-right: 13px;
|
|
530
|
+
padding-left: 13px;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
.hero-copy h1 {
|
|
534
|
+
font-size: 22px;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
.hero-scene {
|
|
538
|
+
right: 6px;
|
|
539
|
+
opacity: 0.38;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.warning-detail {
|
|
543
|
+
padding-right: 13px;
|
|
544
|
+
padding-left: 13px;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.warning-card {
|
|
548
|
+
padding-right: 11px;
|
|
549
|
+
padding-left: 11px;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
.warning-card-header {
|
|
553
|
+
grid-template-columns: 40px minmax(0, 1fr) auto;
|
|
554
|
+
gap: 9px;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.bank-badge {
|
|
558
|
+
width: 36px;
|
|
559
|
+
height: 36px;
|
|
560
|
+
font-size: 22px;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.warning-field-row {
|
|
564
|
+
grid-template-columns: minmax(0, 104px) minmax(0, 1fr);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.warning-field-row dt,
|
|
568
|
+
.warning-field-row dd {
|
|
569
|
+
font-size: 14px;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
.field-icon {
|
|
573
|
+
margin-right: 9px;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.message-copy p {
|
|
577
|
+
font-size: 15px;
|
|
578
|
+
}
|
|
25
579
|
}
|
|
26
580
|
</style>
|
package/vite.config.ts
CHANGED
|
@@ -14,6 +14,8 @@ export default defineConfig(({ command, mode }) => {
|
|
|
14
14
|
const env = loadEnv(mode, process.cwd(), '')
|
|
15
15
|
const apiBaseUrl = env.VITE_API_BASE_URL || '/api'
|
|
16
16
|
const appVersion = getAppVersion(mode, command)
|
|
17
|
+
const enableVueDevTools =
|
|
18
|
+
command === 'serve' && env.VITE_ENABLE_VUE_DEVTOOLS !== 'false'
|
|
17
19
|
|
|
18
20
|
return {
|
|
19
21
|
base: './',
|
|
@@ -22,7 +24,7 @@ export default defineConfig(({ command, mode }) => {
|
|
|
22
24
|
},
|
|
23
25
|
plugins: [
|
|
24
26
|
vue(),
|
|
25
|
-
|
|
27
|
+
enableVueDevTools && vueDevTools(),
|
|
26
28
|
|
|
27
29
|
// 自动导入 Vue / Vue Router / Pinia / VueUse / Vant 的 API,省去手动 import
|
|
28
30
|
AutoImport({
|