minitest2.0 0.0.4 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minitest2.0",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "repository": {
package/src/App.vue CHANGED
@@ -22,7 +22,7 @@ const tabItems = computed(() =>
22
22
  }),
23
23
  )
24
24
 
25
- const showTabbar = computed(() => route.meta.hideTabbar !== true)
25
+ const showTabbar = computed(() => route.meta.showTabbar === true)
26
26
  const noBackRouteNames = new Set(['Home', 'Login', 'Dashboard', 'Articles', 'Mine'])
27
27
  const headerTitle = computed(() => String(route.meta.title || ''))
28
28
  const showHeaderBack = computed(() => !noBackRouteNames.has(String(route.name || '')))
@@ -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,66 @@
1
+ import {
2
+ buildTamRequestPayload,
3
+ tamRequest,
4
+ type TamRequestPayload,
5
+ type TamResponse,
6
+ } from '@/api/tam'
7
+
8
+ export type ClearingDetailQueryForm = {
9
+ /** 清算日期,页面日期控件为 YYYY-MM-DD,接口入参为 YYYYMMDD。 */
10
+ queryDate: string
11
+ }
12
+
13
+ export type ClearingDetailPayload = TamRequestPayload<ClearingDetailQueryBody>
14
+
15
+ export type ClearingDetailResult = TamResponse<ClearingDetailResponseBody>
16
+
17
+ export type ClearingDetailRecord = {
18
+ /** 时间 */
19
+ msgSendDt?: string
20
+ /** 进款 */
21
+ secreditLineAmt?: string
22
+ /** 出款 */
23
+ sedebtLineAmt?: string
24
+ /** 轧差 */
25
+ txnetgBalance?: string
26
+ /** 轧差方向 */
27
+ txnetgBalanceDirection?: string
28
+ /** 轧差日期 */
29
+ txnetgDt?: string
30
+ /** 场次 */
31
+ txnetgRnd?: string
32
+ /** 清算报文编号 */
33
+ txnetgType?: string
34
+ }
35
+
36
+ type ClearingDetailQueryBody = {
37
+ /** 清算日期,格式 YYYYMMDD。 */
38
+ queryDate: string
39
+ }
40
+
41
+ type ClearingDetailResponseBody = {
42
+ result?: ClearingDetailRecord[]
43
+ }
44
+
45
+ const QUERY_TRAN_CODE = '/TAM/0001/140110006'
46
+
47
+ export function buildClearingDetailPayload(form: ClearingDetailQueryForm): ClearingDetailPayload {
48
+ return buildTamRequestPayload({
49
+ tranCode: QUERY_TRAN_CODE,
50
+ body: {
51
+ queryDate: normalizeDate(form.queryDate),
52
+ },
53
+ })
54
+ }
55
+
56
+ export async function queryClearingDetail(
57
+ payload: ClearingDetailPayload,
58
+ ): Promise<ClearingDetailResult> {
59
+ return tamRequest<ClearingDetailQueryBody, ClearingDetailResponseBody>(payload)
60
+ }
61
+
62
+ function normalizeDate(value: string) {
63
+ if (/^\d{8}$/.test(value)) return value
64
+
65
+ return value.replaceAll('-', '')
66
+ }
@@ -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
+ }
@@ -0,0 +1,90 @@
1
+ import {
2
+ buildTamRequestPayload,
3
+ tamRequest,
4
+ type TamRequestPayload,
5
+ type TamResponse,
6
+ } from '@/api/tam'
7
+
8
+ type NetDebitEmptyBody = Record<string, never>
9
+
10
+ export type NetDebitSendPayload = TamRequestPayload<NetDebitEmptyBody>
11
+
12
+ export type NetDebitQueryPayload = TamRequestPayload<NetDebitQueryBody>
13
+
14
+ export type NetDebitSendResult = TamResponse<NetDebitSendBody>
15
+
16
+ export type NetDebitQueryResult = TamResponse<NetDebitQuotaDetail>
17
+
18
+ export type NetDebitQuotaDetail = {
19
+ /** 净借记限额 */
20
+ availableDrAmt?: string
21
+ /** 查询处理状态 */
22
+ busiStatus?: string
23
+ /** 委托日期 */
24
+ consignDate?: string
25
+ /** 授信额度 */
26
+ creditAmt?: string
27
+ /** 可用净借记限额 */
28
+ currentAvailableDrAmt?: string
29
+ /** 圈存资金 */
30
+ encloseAmt?: string
31
+ /** 质押额度 */
32
+ impawnLimit?: string
33
+ /** 报文标识号 */
34
+ msgRefNo?: string
35
+ /** 被查询行行号 */
36
+ rcvBankCode?: string
37
+ /** 平台流水号 */
38
+ refNo?: string
39
+ /** 应答委托日期 */
40
+ responseDate?: string
41
+ /** 应答报文序号 */
42
+ responseNo?: string
43
+ /** 被查询清算行行号 */
44
+ settleBankCode?: string
45
+ /** 业务处理状态 */
46
+ status?: string
47
+ /** 发送交易返回的平台流水号,页面状态流转使用。 */
48
+ requestRefNo?: string
49
+ }
50
+
51
+ type NetDebitSendBody = {
52
+ /** 平台流水号,后续查询交易 body.msgId 使用该值。 */
53
+ refNo?: string
54
+ }
55
+
56
+ type NetDebitQueryBody = {
57
+ /** 发送交易返回的平台流水号。 */
58
+ msgId: string
59
+ }
60
+
61
+ const SEND_TRAN_CODE = '/TAM/0001/140110003'
62
+ const QUERY_TRAN_CODE = '/TAM/0001/140110004'
63
+
64
+ export function buildNetDebitSendPayload(): NetDebitSendPayload {
65
+ return buildTamRequestPayload({
66
+ tranCode: SEND_TRAN_CODE,
67
+ body: {},
68
+ })
69
+ }
70
+
71
+ export async function sendNetDebitRequest(
72
+ payload: NetDebitSendPayload,
73
+ ): Promise<NetDebitSendResult> {
74
+ return tamRequest<NetDebitEmptyBody, NetDebitSendBody>(payload)
75
+ }
76
+
77
+ export function buildNetDebitQueryPayload(msgId: string): NetDebitQueryPayload {
78
+ return buildTamRequestPayload({
79
+ tranCode: QUERY_TRAN_CODE,
80
+ body: {
81
+ msgId,
82
+ },
83
+ })
84
+ }
85
+
86
+ export async function queryNetDebitQuota(
87
+ payload: NetDebitQueryPayload,
88
+ ): Promise<NetDebitQueryResult> {
89
+ return tamRequest<NetDebitQueryBody, NetDebitQuotaDetail>(payload)
90
+ }
@@ -0,0 +1,42 @@
1
+ import {
2
+ buildTamRequestPayload,
3
+ tamRequest,
4
+ type TamRequestPayload,
5
+ type TamResponse,
6
+ } from '@/api/tam'
7
+
8
+ type OpeningReserveEstimateEmptyBody = Record<string, never>
9
+
10
+ export type OpeningReserveEstimateBody = {
11
+ /** 法定准备金 */
12
+ fzAmt?: string
13
+ /** 总分行铺底资金之和 */
14
+ pdAmt?: string
15
+ /** 日初备款 */
16
+ preAmt?: string
17
+ /** 人行时点头寸 */
18
+ rhAmt?: string
19
+ /** 预报未核销收入金额 */
20
+ unverifyAmtIn?: string
21
+ /** 预报未核销支出金额 */
22
+ unverifyAmtOut?: string
23
+ }
24
+
25
+ export type OpeningReserveEstimatePayload = TamRequestPayload<OpeningReserveEstimateEmptyBody>
26
+
27
+ export type OpeningReserveEstimateResult = TamResponse<OpeningReserveEstimateBody>
28
+
29
+ const QUERY_TRAN_CODE = '/TAM/0001/140103004'
30
+
31
+ export function buildOpeningReserveEstimatePayload(): OpeningReserveEstimatePayload {
32
+ return buildTamRequestPayload({
33
+ tranCode: QUERY_TRAN_CODE,
34
+ body: {},
35
+ })
36
+ }
37
+
38
+ export async function queryOpeningReserveEstimate(
39
+ payload: OpeningReserveEstimatePayload,
40
+ ): Promise<OpeningReserveEstimateResult> {
41
+ return tamRequest<OpeningReserveEstimateEmptyBody, OpeningReserveEstimateBody>(payload)
42
+ }