minitest2.0 0.0.5 → 0.0.7
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/public/images/dashboard/01-rmb-great-wall-bg.jpg +0 -0
- package/public/images/dashboard/02-city-clearing-skyline-bg.jpg +0 -0
- package/public/images/dashboard/03-cross-border-rmb-world-map-bg.jpg +0 -0
- package/public/images/dashboard/04-digital-currency-fintech-bg.jpg +0 -0
- package/public/images/dashboard/05-usd-liberty-bg.jpg +0 -0
- package/public/images/dashboard/06-eur-europe-architecture-bg.jpg +0 -0
- package/public/images/dashboard/07-hkd-hong-kong-skyline-bg.jpg +0 -0
- package/public/images/dashboard/08-jpy-mount-fuji-bg.jpg +0 -0
- package/src/api/announcement.ts +153 -0
- package/src/api/foreign-position.ts +213 -0
- package/src/router/index.ts +14 -2
- package/src/stores/user.ts +1 -1
- package/src/utils/foreign-forecast.ts +134 -0
- package/src/views/article-detail/index.vue +44 -1
- package/src/views/article-edit/index.vue +9 -0
- package/src/views/dashboard/index.vue +410 -2
- package/src/views/foreign-position/index.vue +1208 -11
- package/src/views/foreign-position-create/index.vue +363 -0
- package/src/views/foreign-position-detail/index.vue +390 -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
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
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,213 @@
|
|
|
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
|
+
export type ForeignForecastDetailPayload = {
|
|
71
|
+
appHead: TamRequestPayload<ForeignForecastDetailBody>['appHead']
|
|
72
|
+
body: ForeignForecastDetailBody
|
|
73
|
+
head: TamRequestPayload<ForeignForecastDetailBody>['head']
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type ForeignForecastDetailResult = {
|
|
77
|
+
body: ForeignForecastDetailRecord
|
|
78
|
+
head: TamResponse<ForeignForecastDetailRecord>['head']
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
type ForeignForecastQueryBody = {
|
|
82
|
+
busiType: string
|
|
83
|
+
clientName: string
|
|
84
|
+
competName: string
|
|
85
|
+
endTransAmt: string
|
|
86
|
+
groundBranch: string
|
|
87
|
+
payChannel: string
|
|
88
|
+
payDirec: string
|
|
89
|
+
predStatus: string
|
|
90
|
+
sortRule: string
|
|
91
|
+
startTransAmt: string
|
|
92
|
+
tranDate: string
|
|
93
|
+
verifyStatus: string
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
type ForeignForecastQueryResultBody = {
|
|
97
|
+
resultList: ForeignForecastRecord[]
|
|
98
|
+
totalCount: string
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
type ForeignForecastDetailBody = {
|
|
102
|
+
/** 预报流水号,详情查询主键。 */
|
|
103
|
+
predRefNo: string
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type ForeignForecastDetailRecord = ForeignForecastRecord & {
|
|
107
|
+
/** 出票日 */
|
|
108
|
+
billDate?: string
|
|
109
|
+
/** 票号 */
|
|
110
|
+
billNo?: string
|
|
111
|
+
/** 交易对手账号 */
|
|
112
|
+
competAcct?: string
|
|
113
|
+
/** 额度标志 */
|
|
114
|
+
eduFlag?: string
|
|
115
|
+
/** 落地机构代码 */
|
|
116
|
+
groundBranchCode?: string
|
|
117
|
+
/** 联系人 */
|
|
118
|
+
linkMan?: string
|
|
119
|
+
/** 支付渠道 */
|
|
120
|
+
payChannel?: string
|
|
121
|
+
/** 收支方向 */
|
|
122
|
+
payDirec?: string
|
|
123
|
+
/** 联系电话 */
|
|
124
|
+
phoneNo?: string
|
|
125
|
+
/** 预报机构 */
|
|
126
|
+
predBranch?: string
|
|
127
|
+
/** 预报机构名称 */
|
|
128
|
+
predBranchName?: string
|
|
129
|
+
/** 预报人员编号 */
|
|
130
|
+
predCode?: string
|
|
131
|
+
/** 预报日期 */
|
|
132
|
+
predDate?: string
|
|
133
|
+
/** 预报人员姓名 */
|
|
134
|
+
predName?: string
|
|
135
|
+
/** 用途 */
|
|
136
|
+
purpose?: string
|
|
137
|
+
/** 备注 */
|
|
138
|
+
remark?: string
|
|
139
|
+
/** 标准名称 */
|
|
140
|
+
standardName?: string
|
|
141
|
+
/** 已核销金额 */
|
|
142
|
+
verifyAmt?: string
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
type QueryOptions = {
|
|
146
|
+
form: ForeignForecastQueryForm
|
|
147
|
+
pageIndex: number
|
|
148
|
+
pageSize: number
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const QUERY_TRAN_CODE = '/TAM/0001/140101001'
|
|
152
|
+
const DETAIL_TRAN_CODE = '/TAM/0001/140101002'
|
|
153
|
+
|
|
154
|
+
export function buildForeignForecastQueryPayload({
|
|
155
|
+
form,
|
|
156
|
+
pageIndex,
|
|
157
|
+
pageSize,
|
|
158
|
+
}: QueryOptions): ForeignForecastQueryPayload {
|
|
159
|
+
const tranDate = normalizeDate(form.tranDate || useAppStore().systemWorkDate)
|
|
160
|
+
|
|
161
|
+
return buildTamRequestPayload({
|
|
162
|
+
tranCode: QUERY_TRAN_CODE,
|
|
163
|
+
pageIndex,
|
|
164
|
+
pageSize,
|
|
165
|
+
body: {
|
|
166
|
+
busiType: form.busiType || '',
|
|
167
|
+
clientName: '',
|
|
168
|
+
competName: '',
|
|
169
|
+
endTransAmt: toRequestAmount(form.endTranAmt),
|
|
170
|
+
groundBranch: form.groundBranch || '',
|
|
171
|
+
payChannel: '',
|
|
172
|
+
payDirec: '',
|
|
173
|
+
predStatus: form.predStatus || '',
|
|
174
|
+
sortRule: '',
|
|
175
|
+
startTransAmt: toRequestAmount(form.startTranAmt),
|
|
176
|
+
tranDate,
|
|
177
|
+
verifyStatus: '',
|
|
178
|
+
},
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export async function queryForeignForecastPage(
|
|
183
|
+
payload: ForeignForecastQueryPayload,
|
|
184
|
+
): Promise<ForeignForecastQueryResult> {
|
|
185
|
+
return tamRequest<ForeignForecastQueryBody, ForeignForecastQueryResultBody>(payload)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function buildForeignForecastDetailPayload(predRefNo: string): ForeignForecastDetailPayload {
|
|
189
|
+
return buildTamRequestPayload({
|
|
190
|
+
tranCode: DETAIL_TRAN_CODE,
|
|
191
|
+
body: {
|
|
192
|
+
predRefNo,
|
|
193
|
+
},
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export async function queryForeignForecastDetail(
|
|
198
|
+
payload: ForeignForecastDetailPayload,
|
|
199
|
+
): Promise<ForeignForecastDetailResult> {
|
|
200
|
+
return tamRequest<ForeignForecastDetailBody, ForeignForecastDetailRecord>(payload)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function normalizeDate(value: string) {
|
|
204
|
+
if (/^\d{8}$/.test(value)) return value
|
|
205
|
+
return value.replaceAll('-', '')
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function toRequestAmount(value?: string) {
|
|
209
|
+
if (!value?.trim()) return ''
|
|
210
|
+
|
|
211
|
+
const amount = Number(value)
|
|
212
|
+
return Number.isFinite(amount) ? String(Math.round(amount * 10000)) : ''
|
|
213
|
+
}
|
package/src/router/index.ts
CHANGED
|
@@ -45,7 +45,19 @@ const router = createRouter({
|
|
|
45
45
|
path: '/foreign-position',
|
|
46
46
|
name: 'ForeignPosition',
|
|
47
47
|
component: () => import('@/views/foreign-position/index.vue'),
|
|
48
|
-
meta: { requiresAuth: true, title: '
|
|
48
|
+
meta: { requiresAuth: true, title: '外币预报信息查询' },
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
path: '/foreign-position/new',
|
|
52
|
+
name: 'ForeignPositionCreate',
|
|
53
|
+
component: () => import('@/views/foreign-position-create/index.vue'),
|
|
54
|
+
meta: { requiresAuth: true, title: '新增外币预报' },
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
path: '/foreign-position/:predRefNo',
|
|
58
|
+
name: 'ForeignPositionDetail',
|
|
59
|
+
component: () => import('@/views/foreign-position-detail/index.vue'),
|
|
60
|
+
meta: { requiresAuth: true, title: '外币预报详情' },
|
|
49
61
|
},
|
|
50
62
|
{
|
|
51
63
|
path: '/pbc-position',
|
|
@@ -87,7 +99,7 @@ const router = createRouter({
|
|
|
87
99
|
path: '/warning',
|
|
88
100
|
name: 'Warning',
|
|
89
101
|
component: () => import('@/views/warning/index.vue'),
|
|
90
|
-
meta: { requiresAuth: true, title: '
|
|
102
|
+
meta: { requiresAuth: true, title: '头寸管理平台' },
|
|
91
103
|
},
|
|
92
104
|
{
|
|
93
105
|
path: '/dashboard',
|
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,134 @@
|
|
|
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 allForeignCurrencyOption: ForeignCurrencyOption = {
|
|
18
|
+
label: '全部币种',
|
|
19
|
+
shortLabel: '全部币种',
|
|
20
|
+
value: '',
|
|
21
|
+
unit: '万元',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const foreignCurrencyOptions: ForeignCurrencyOption[] = [
|
|
25
|
+
allForeignCurrencyOption,
|
|
26
|
+
{ label: '美元', shortLabel: '美元', value: 'USD', unit: '万美元' },
|
|
27
|
+
{ label: '欧元', shortLabel: '欧元', value: 'EUR', unit: '万欧元' },
|
|
28
|
+
{ label: '港币', shortLabel: '港币', value: 'HKD', unit: '万港币' },
|
|
29
|
+
{ label: '日元', shortLabel: '日元', value: 'JPY', unit: '万日元' },
|
|
30
|
+
{ label: '英镑', shortLabel: '英镑', value: 'GBP', unit: '万英镑' },
|
|
31
|
+
{ label: '加拿大元', shortLabel: '加元', value: 'CAD', unit: '万加元' },
|
|
32
|
+
{ label: '澳大利亚元', shortLabel: '澳元', value: 'AUD', unit: '万澳元' },
|
|
33
|
+
{ label: '新加坡元', shortLabel: '新元', value: 'SGD', unit: '万新元' },
|
|
34
|
+
{ label: '瑞士法郎', shortLabel: '瑞郎', value: 'CHF', unit: '万瑞郎' },
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
export const allForeignBusinessOption: ForeignOption = { label: '全部业务类型', value: '' }
|
|
38
|
+
export const defaultForeignBusinessOption: ForeignOption = { label: '普通汇款', value: '1' }
|
|
39
|
+
|
|
40
|
+
export const foreignBusinessOptions: ForeignOption[] = [
|
|
41
|
+
allForeignBusinessOption,
|
|
42
|
+
defaultForeignBusinessOption,
|
|
43
|
+
{ label: '外汇买卖', value: '2' },
|
|
44
|
+
{ label: '结售汇', value: '3' },
|
|
45
|
+
{ label: '资金调拨', value: '4' },
|
|
46
|
+
{ label: '同业业务', value: '5' },
|
|
47
|
+
{ label: '贸易融资', value: '6' },
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
export const allForeignStatusOption: ForeignStatusOption = {
|
|
51
|
+
label: '全部预报状态',
|
|
52
|
+
value: '',
|
|
53
|
+
className: 'status-tag-unknown',
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const foreignStatusOptions: ForeignStatusOption[] = [
|
|
57
|
+
allForeignStatusOption,
|
|
58
|
+
{ label: '已确认', value: '1', className: 'status-tag-confirmed' },
|
|
59
|
+
{ label: '待确认', value: '2', className: 'status-tag-pending' },
|
|
60
|
+
{ label: '已撤销', value: '3', className: 'status-tag-cancelled' },
|
|
61
|
+
{ label: '已核销', value: '4', className: 'status-tag-verified' },
|
|
62
|
+
{ label: '已驳回', value: '5', className: 'status-tag-rejected' },
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
export const unknownForeignStatus: ForeignStatusOption = {
|
|
66
|
+
label: '未知状态',
|
|
67
|
+
value: '',
|
|
68
|
+
className: 'status-tag-unknown',
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function resolveForeignCurrency(value?: string) {
|
|
72
|
+
const normalizedValue = value?.trim().toUpperCase()
|
|
73
|
+
|
|
74
|
+
if (!normalizedValue) {
|
|
75
|
+
return allForeignCurrencyOption
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
foreignCurrencyOptions.find((item) => item.value === normalizedValue) || {
|
|
80
|
+
label: normalizedValue,
|
|
81
|
+
shortLabel: normalizedValue,
|
|
82
|
+
value: normalizedValue,
|
|
83
|
+
unit: `万${normalizedValue}`,
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function resolveForeignBusinessType(value?: string) {
|
|
89
|
+
if (!value?.trim()) return '普通汇款'
|
|
90
|
+
|
|
91
|
+
return foreignBusinessOptions.find((item) => item.value === value)?.label || value
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function resolveForeignStatus(value?: string) {
|
|
95
|
+
if (!value?.trim()) {
|
|
96
|
+
return unknownForeignStatus
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return foreignStatusOptions.find((item) => item.value === value) || unknownForeignStatus
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function formatForeignForecastDate(value?: string) {
|
|
103
|
+
if (value && /^\d{8}$/.test(value)) {
|
|
104
|
+
return `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}`
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return value || '-'
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function toTenThousandAmount(value?: string) {
|
|
111
|
+
const amount = Number(value?.replaceAll(',', '') || 0)
|
|
112
|
+
return Number.isFinite(amount) ? amount / 10000 : 0
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function formatForeignForecastAmount(value: number) {
|
|
116
|
+
const sign = value >= 0 ? '+' : '-'
|
|
117
|
+
return `${sign}${Math.abs(value).toFixed(2)}`
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function formatForeignForecastAmountText(value?: string, currencyValue?: string) {
|
|
121
|
+
const currency = resolveForeignCurrency(currencyValue)
|
|
122
|
+
return `${formatForeignForecastAmount(toTenThousandAmount(value))}${currency.unit}`
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function formatForeignForecastAmountYuanText(value?: string) {
|
|
126
|
+
const amount = Number(value?.replaceAll(',', '') || 0)
|
|
127
|
+
|
|
128
|
+
if (!Number.isFinite(amount)) return '-'
|
|
129
|
+
|
|
130
|
+
return `${amount.toLocaleString('zh-CN', {
|
|
131
|
+
maximumFractionDigits: 2,
|
|
132
|
+
minimumFractionDigits: 2,
|
|
133
|
+
})} 元`
|
|
134
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { showImagePreview } from 'vant'
|
|
2
3
|
import { highlightArticleHtml } from '@/utils/code-highlight'
|
|
3
4
|
import { copyText } from '@/utils/copy'
|
|
4
5
|
import { formatArticleDate, getArticleDetail, type Article } from '@/api/announcement'
|
|
@@ -81,6 +82,38 @@ function articleContentToText(html: string) {
|
|
|
81
82
|
|
|
82
83
|
return root.textContent?.replace(/\n{3,}/g, '\n\n').trim() || ''
|
|
83
84
|
}
|
|
85
|
+
|
|
86
|
+
function previewArticleImage(event: MouseEvent) {
|
|
87
|
+
if (!(event.target instanceof HTMLImageElement)) {
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const container = event.currentTarget
|
|
92
|
+
|
|
93
|
+
if (!(container instanceof HTMLElement)) {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const images = Array.from(container.querySelectorAll('img'))
|
|
98
|
+
.map((image) => image.currentSrc || image.src || image.getAttribute('src') || '')
|
|
99
|
+
.filter(Boolean)
|
|
100
|
+
const currentSrc =
|
|
101
|
+
event.target.currentSrc || event.target.src || event.target.getAttribute('src') || ''
|
|
102
|
+
const startPosition = Math.max(images.findIndex((image) => image === currentSrc), 0)
|
|
103
|
+
|
|
104
|
+
if (!images.length || !currentSrc) {
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
event.preventDefault()
|
|
109
|
+
event.stopPropagation()
|
|
110
|
+
|
|
111
|
+
showImagePreview({
|
|
112
|
+
images,
|
|
113
|
+
startPosition,
|
|
114
|
+
closeable: true,
|
|
115
|
+
})
|
|
116
|
+
}
|
|
84
117
|
</script>
|
|
85
118
|
|
|
86
119
|
<template>
|
|
@@ -98,7 +131,7 @@ function articleContentToText(html: string) {
|
|
|
98
131
|
</div>
|
|
99
132
|
</div>
|
|
100
133
|
|
|
101
|
-
<div class="article-content" v-html="highlightedContent"></div>
|
|
134
|
+
<div class="article-content" @click="previewArticleImage" v-html="highlightedContent"></div>
|
|
102
135
|
|
|
103
136
|
<footer class="detail-actions">
|
|
104
137
|
<van-button block plain type="danger" icon="description-o" @click="copyArticleContent">
|
|
@@ -219,6 +252,16 @@ function articleContentToText(html: string) {
|
|
|
219
252
|
text-decoration: none;
|
|
220
253
|
}
|
|
221
254
|
|
|
255
|
+
.article-content :deep(img) {
|
|
256
|
+
display: block;
|
|
257
|
+
max-width: 100%;
|
|
258
|
+
height: auto;
|
|
259
|
+
margin: 8px auto 14px;
|
|
260
|
+
border-radius: 6px;
|
|
261
|
+
cursor: zoom-in;
|
|
262
|
+
object-fit: contain;
|
|
263
|
+
}
|
|
264
|
+
|
|
222
265
|
.article-content :deep(pre) {
|
|
223
266
|
position: relative;
|
|
224
267
|
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;
|