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.
- package/.editorconfig +8 -0
- package/.env.development +3 -0
- package/.env.production +3 -0
- package/.env.test +3 -0
- package/.gitattributes +1 -0
- package/.oxlintrc.json +10 -0
- package/.prettierrc.json +6 -0
- package/.vscode/extensions.json +9 -0
- package/.vscode/settings.json +13 -0
- package/README.md +179 -0
- package/auto-imports.d.ts +629 -0
- package/components.d.ts +21 -0
- package/design-qa.md +36 -0
- package/docs/MX_API.md +244 -0
- package/docs/REQUEST.md +217 -0
- package/docs/jsapi.js +515 -0
- package/docs/package-json-guide.md +132 -0
- package/env.d.ts +15 -0
- package/eslint.config.ts +26 -0
- package/index.html +16 -0
- package/package.json +83 -0
- package/plugins/bump-version.ts +61 -0
- package/postcss.config.ts +15 -0
- package/public/favicon.ico +0 -0
- package/public/images/12a73787-86a9-4891-a65f-66104746f6a8.png +0 -0
- package/public/images/5798d7aa-ba8b-4605-8079-58b35495ac55.png +0 -0
- package/public/images/73fef1e4-0fd0-4a1a-9b8b-a70a5b6acbbc.png +0 -0
- package/public/images/bc685b4c-0cca-4a79-924c-a8ee10e6f8eb.png +0 -0
- package/public/images/c3dbbd9d-be56-490e-b9f4-6ee17ebefffc.png +0 -0
- package/public/images/ea745a10-42aa-4f44-8d7f-3ab02cc0adcd.png +0 -0
- package/public/images/f5876785-b927-4347-ba19-999114240649.png +0 -0
- package/public/images/img.png +0 -0
- package/public/images/opening-reserve-estimate.png +0 -0
- package/public/images/position-estimate-report.png +0 -0
- package/src/App.vue +131 -0
- package/src/api/announcement.ts +405 -0
- package/src/api/health.ts +13 -0
- package/src/api/pbc-position.ts +178 -0
- package/src/api/rmb-position.ts +233 -0
- package/src/api/tam.ts +173 -0
- package/src/api/user.ts +92 -0
- package/src/auto-imports.d.ts +633 -0
- package/src/components/AppTitleBar.vue +376 -0
- package/src/components.d.ts +33 -0
- package/src/config/config.properties +3 -0
- package/src/config/env.ts +6 -0
- package/src/config/plugin.properties.pro +6 -0
- package/src/config/plugin.properties.test +6 -0
- package/src/core/mxApi/index.ts +451 -0
- package/src/core/request/index.ts +135 -0
- package/src/main.ts +40 -0
- package/src/router/index.ts +144 -0
- package/src/stores/app.ts +103 -0
- package/src/stores/counter.ts +12 -0
- package/src/stores/user.ts +137 -0
- package/src/styles/nprogress.css +22 -0
- package/src/styles/vant-overrides.css +42 -0
- package/src/types/api.d.ts +14 -0
- package/src/types/nprogress.d.ts +8 -0
- package/src/utils/auth-token.ts +241 -0
- package/src/utils/code-highlight.ts +165 -0
- package/src/utils/copy.ts +36 -0
- package/src/utils/query.ts +27 -0
- package/src/utils/request.ts +238 -0
- package/src/utils/rmb-forecast.ts +84 -0
- package/src/utils/toast.ts +61 -0
- package/src/views/article-detail/index.vue +289 -0
- package/src/views/article-edit/index.vue +600 -0
- package/src/views/articles/index.vue +293 -0
- package/src/views/clearing-detail/index.vue +26 -0
- package/src/views/dashboard/index.vue +18 -0
- package/src/views/foreign-position/index.vue +26 -0
- package/src/views/home/index.vue +213 -0
- package/src/views/login/index.vue +275 -0
- package/src/views/mine/index.vue +147 -0
- package/src/views/net-debit/index.vue +26 -0
- package/src/views/opening-reserve-estimate/index.vue +28 -0
- package/src/views/pbc-position/index.vue +357 -0
- package/src/views/position-estimate/index.vue +67 -0
- package/src/views/position-estimate-report/index.vue +28 -0
- package/src/views/rmb-position/index.vue +1013 -0
- package/src/views/rmb-position-create/index.vue +221 -0
- package/src/views/rmb-position-detail/index.vue +355 -0
- package/src/views/settings/index.vue +67 -0
- package/src/views/warning/index.vue +26 -0
- package/tsconfig.app.json +18 -0
- package/tsconfig.json +11 -0
- package/tsconfig.node.json +28 -0
- package/vite.config.ts +100 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { request } from '@/core/request'
|
|
2
|
+
import type { ApiResponse } from '@/types/api'
|
|
3
|
+
|
|
4
|
+
export interface Article {
|
|
5
|
+
id: string
|
|
6
|
+
title: string
|
|
7
|
+
content: string
|
|
8
|
+
author: string
|
|
9
|
+
createdAt: string
|
|
10
|
+
updatedAt: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ArticlePayload {
|
|
14
|
+
title: string
|
|
15
|
+
content: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type RawRecord = Record<string, unknown>
|
|
19
|
+
|
|
20
|
+
const LIST_URL = '/uaa/announcement/get/restricted'
|
|
21
|
+
const DETAIL_URL = '/uaa/announcement/get/id'
|
|
22
|
+
const ADD_URL = '/uaa/announcement/add'
|
|
23
|
+
const UPDATE_URL = '/uaa/announcement/update'
|
|
24
|
+
const DEFAULT_ANNOUNCEMENT_FIELDS = {
|
|
25
|
+
annouZrFteLag: '0',
|
|
26
|
+
annouSts: '0',
|
|
27
|
+
department: '100011',
|
|
28
|
+
efDt: '2026-07-01 00:00:00',
|
|
29
|
+
expireDt: '2026-07-31 00:00:00',
|
|
30
|
+
orgList: [],
|
|
31
|
+
orgNo: '25001',
|
|
32
|
+
roleList: [],
|
|
33
|
+
topFlag: '0',
|
|
34
|
+
userList: [],
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const ALLOWED_TAGS = new Set([
|
|
38
|
+
'A',
|
|
39
|
+
'B',
|
|
40
|
+
'BLOCKQUOTE',
|
|
41
|
+
'BR',
|
|
42
|
+
'DIV',
|
|
43
|
+
'EM',
|
|
44
|
+
'H2',
|
|
45
|
+
'H3',
|
|
46
|
+
'I',
|
|
47
|
+
'LI',
|
|
48
|
+
'OL',
|
|
49
|
+
'P',
|
|
50
|
+
'PRE',
|
|
51
|
+
'S',
|
|
52
|
+
'SPAN',
|
|
53
|
+
'STRONG',
|
|
54
|
+
'U',
|
|
55
|
+
'UL',
|
|
56
|
+
'CODE',
|
|
57
|
+
])
|
|
58
|
+
|
|
59
|
+
export async function queryArticles() {
|
|
60
|
+
const response = await request<unknown>({
|
|
61
|
+
url: LIST_URL,
|
|
62
|
+
method: 'GET',
|
|
63
|
+
})
|
|
64
|
+
const data = unwrapResponse(response)
|
|
65
|
+
|
|
66
|
+
return extractRecords(data).map((record) => mapAnnouncementRecord(record))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function getArticleDetail(annouId: string) {
|
|
70
|
+
const response = await request<unknown>({
|
|
71
|
+
url: DETAIL_URL,
|
|
72
|
+
method: 'GET',
|
|
73
|
+
params: { annouId },
|
|
74
|
+
})
|
|
75
|
+
const data = unwrapResponse(response)
|
|
76
|
+
const record = extractDetailRecord(data)
|
|
77
|
+
|
|
78
|
+
return mapAnnouncementRecord(record, annouId)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function createArticle(payload: ArticlePayload) {
|
|
82
|
+
const content = sanitizeArticleHtml(payload.content)
|
|
83
|
+
const response = await request<unknown, RawRecord>({
|
|
84
|
+
url: ADD_URL,
|
|
85
|
+
method: 'POST',
|
|
86
|
+
data: buildAnnouncementPayload({ ...payload, content }),
|
|
87
|
+
})
|
|
88
|
+
const data = unwrapResponse(response)
|
|
89
|
+
const record = extractDetailRecord(data)
|
|
90
|
+
|
|
91
|
+
return mapAnnouncementRecord(record, readString(record, ['annouId', 'id']))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function updateArticle(annouId: string, payload: ArticlePayload) {
|
|
95
|
+
const content = sanitizeArticleHtml(payload.content)
|
|
96
|
+
const response = await request<unknown, RawRecord>({
|
|
97
|
+
url: UPDATE_URL,
|
|
98
|
+
method: 'POST',
|
|
99
|
+
data: buildAnnouncementPayload({ ...payload, content }, annouId),
|
|
100
|
+
})
|
|
101
|
+
const data = unwrapResponse(response)
|
|
102
|
+
const record = extractDetailRecord(data)
|
|
103
|
+
|
|
104
|
+
return mapAnnouncementRecord(record, annouId)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function formatArticleDate(value: string) {
|
|
108
|
+
const date = new Date(value)
|
|
109
|
+
|
|
110
|
+
if (Number.isNaN(date.getTime())) {
|
|
111
|
+
return ''
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return new Intl.DateTimeFormat('zh-CN', {
|
|
115
|
+
year: 'numeric',
|
|
116
|
+
month: '2-digit',
|
|
117
|
+
day: '2-digit',
|
|
118
|
+
hour: '2-digit',
|
|
119
|
+
minute: '2-digit',
|
|
120
|
+
}).format(date)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function extractTextFromHtml(html: string) {
|
|
124
|
+
if (!html.trim()) {
|
|
125
|
+
return ''
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const doc = new DOMParser().parseFromString(html, 'text/html')
|
|
129
|
+
|
|
130
|
+
return doc.body.textContent?.replace(/\s+/g, ' ').trim() || ''
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function sanitizeArticleHtml(html: string) {
|
|
134
|
+
if (!html.trim()) {
|
|
135
|
+
return ''
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const doc = new DOMParser().parseFromString(`<article>${html}</article>`, 'text/html')
|
|
139
|
+
const root = doc.body.firstElementChild
|
|
140
|
+
|
|
141
|
+
if (!root) {
|
|
142
|
+
return ''
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
sanitizeChildren(root, doc)
|
|
146
|
+
|
|
147
|
+
return root.innerHTML.trim()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function buildAnnouncementPayload(payload: ArticlePayload, annouId = '') {
|
|
151
|
+
return {
|
|
152
|
+
annouTitle: payload.title.trim(),
|
|
153
|
+
annouConte: payload.content,
|
|
154
|
+
annouId,
|
|
155
|
+
...DEFAULT_ANNOUNCEMENT_FIELDS,
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function unwrapResponse(response: ApiResponse<unknown> | unknown) {
|
|
160
|
+
if (!isRecord(response)) {
|
|
161
|
+
return response
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if ('code' in response && 'data' in response) {
|
|
165
|
+
return response.data
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return response
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function extractRecords(data: unknown): RawRecord[] {
|
|
172
|
+
const candidates = [
|
|
173
|
+
data,
|
|
174
|
+
getValue(data, ['list']),
|
|
175
|
+
getValue(data, ['records']),
|
|
176
|
+
getValue(data, ['rows']),
|
|
177
|
+
getValue(data, ['resultList']),
|
|
178
|
+
getValue(data, ['content']),
|
|
179
|
+
getValue(data, ['body', 'list']),
|
|
180
|
+
getValue(data, ['body', 'records']),
|
|
181
|
+
getValue(data, ['body', 'rows']),
|
|
182
|
+
getValue(data, ['body', 'resultList']),
|
|
183
|
+
]
|
|
184
|
+
|
|
185
|
+
for (const candidate of candidates) {
|
|
186
|
+
if (Array.isArray(candidate)) {
|
|
187
|
+
return candidate.filter(isRecord)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return []
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function extractDetailRecord(data: unknown): RawRecord {
|
|
195
|
+
const records = extractRecords(data)
|
|
196
|
+
|
|
197
|
+
if (records.length > 0) {
|
|
198
|
+
return records[0]!
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const candidates = [
|
|
202
|
+
data,
|
|
203
|
+
getValue(data, ['announcement']),
|
|
204
|
+
getValue(data, ['announcementInfo']),
|
|
205
|
+
getValue(data, ['article']),
|
|
206
|
+
getValue(data, ['detail']),
|
|
207
|
+
getValue(data, ['info']),
|
|
208
|
+
getValue(data, ['record']),
|
|
209
|
+
getValue(data, ['body']),
|
|
210
|
+
getValue(data, ['body', 'announcement']),
|
|
211
|
+
getValue(data, ['body', 'detail']),
|
|
212
|
+
getValue(data, ['body', 'info']),
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
return candidates.find(isRecord) || {}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function mapAnnouncementRecord(record: RawRecord, fallbackId = ''): Article {
|
|
219
|
+
const now = new Date().toISOString()
|
|
220
|
+
const id =
|
|
221
|
+
readString(record, ['annouId', 'announcementId', 'articleId', 'id', 'uuid']) ||
|
|
222
|
+
fallbackId
|
|
223
|
+
const title = readString(record, [
|
|
224
|
+
'annouTitle',
|
|
225
|
+
'announTitle',
|
|
226
|
+
'announcementTitle',
|
|
227
|
+
'title',
|
|
228
|
+
'name',
|
|
229
|
+
'subject',
|
|
230
|
+
])
|
|
231
|
+
const content = readString(record, [
|
|
232
|
+
'annouConte',
|
|
233
|
+
'announContent',
|
|
234
|
+
'announcementContent',
|
|
235
|
+
'articleContent',
|
|
236
|
+
'contentHtml',
|
|
237
|
+
'htmlContent',
|
|
238
|
+
'content',
|
|
239
|
+
])
|
|
240
|
+
const createdAt =
|
|
241
|
+
readString(record, ['createTime', 'createdAt', 'publishTime', 'pubTime', 'gmtCreate']) || now
|
|
242
|
+
const updatedAt =
|
|
243
|
+
readString(record, ['updateTime', 'updatedAt', 'modifyTime', 'gmtModified']) || createdAt
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
id,
|
|
247
|
+
title: title || '未命名公告',
|
|
248
|
+
content: sanitizeArticleHtml(content),
|
|
249
|
+
author:
|
|
250
|
+
readString(record, ['author', 'publisherName', 'pubUserName', 'creatorName', 'createBy']) ||
|
|
251
|
+
'管理员',
|
|
252
|
+
createdAt: normalizeDate(createdAt),
|
|
253
|
+
updatedAt: normalizeDate(updatedAt),
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function readString(record: RawRecord, keys: string[]) {
|
|
258
|
+
for (const key of keys) {
|
|
259
|
+
const value = record[key]
|
|
260
|
+
|
|
261
|
+
if (typeof value === 'string' && value.trim()) {
|
|
262
|
+
return value.trim()
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (typeof value === 'number') {
|
|
266
|
+
return String(value)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return ''
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function getValue(value: unknown, path: string[]) {
|
|
274
|
+
return path.reduce<unknown>((current, key) => {
|
|
275
|
+
if (!isRecord(current)) {
|
|
276
|
+
return undefined
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return current[key]
|
|
280
|
+
}, value)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function normalizeDate(value: string) {
|
|
284
|
+
if (/^\d{4}-\d{2}-\d{2}/.test(value)) {
|
|
285
|
+
return value
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (/^\d{14}$/.test(value)) {
|
|
289
|
+
return `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}T${value.slice(8, 10)}:${value.slice(10, 12)}:${value.slice(12, 14)}`
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (/^\d{8}$/.test(value)) {
|
|
293
|
+
return `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}T00:00:00`
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return value
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function isRecord(value: unknown): value is RawRecord {
|
|
300
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function sanitizeChildren(parent: ParentNode, doc: Document) {
|
|
304
|
+
Array.from(parent.childNodes).forEach((child) => {
|
|
305
|
+
if (child.nodeType === Node.COMMENT_NODE) {
|
|
306
|
+
child.remove()
|
|
307
|
+
return
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (child.nodeType !== Node.ELEMENT_NODE) {
|
|
311
|
+
return
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const element = child as HTMLElement
|
|
315
|
+
|
|
316
|
+
if (!ALLOWED_TAGS.has(element.tagName)) {
|
|
317
|
+
const fragment = doc.createDocumentFragment()
|
|
318
|
+
|
|
319
|
+
while (element.firstChild) {
|
|
320
|
+
fragment.appendChild(element.firstChild)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
element.replaceWith(fragment)
|
|
324
|
+
sanitizeChildren(parent, doc)
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
sanitizeAttributes(element)
|
|
329
|
+
sanitizeChildren(element, doc)
|
|
330
|
+
})
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function sanitizeAttributes(element: HTMLElement) {
|
|
334
|
+
Array.from(element.attributes).forEach((attribute) => {
|
|
335
|
+
const name = attribute.name.toLowerCase()
|
|
336
|
+
|
|
337
|
+
if (element.tagName === 'A' && name === 'href') {
|
|
338
|
+
if (!isSafeHref(attribute.value)) {
|
|
339
|
+
element.removeAttribute(attribute.name)
|
|
340
|
+
return
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
element.setAttribute('target', '_blank')
|
|
344
|
+
element.setAttribute('rel', 'noopener noreferrer')
|
|
345
|
+
return
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (element.tagName === 'A' && (name === 'target' || name === 'rel')) {
|
|
349
|
+
return
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (element.tagName === 'CODE' && name === 'class') {
|
|
353
|
+
const languageClass = normalizeCodeLanguageClass(attribute.value)
|
|
354
|
+
|
|
355
|
+
if (languageClass) {
|
|
356
|
+
element.setAttribute('class', languageClass)
|
|
357
|
+
return
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (element.tagName === 'CODE' && name === 'data-language') {
|
|
362
|
+
const language = normalizeCodeLanguage(attribute.value)
|
|
363
|
+
|
|
364
|
+
if (language) {
|
|
365
|
+
element.setAttribute('data-language', language)
|
|
366
|
+
return
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
element.removeAttribute(attribute.name)
|
|
371
|
+
})
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function normalizeCodeLanguageClass(value: string) {
|
|
375
|
+
const language = value
|
|
376
|
+
.split(/\s+/)
|
|
377
|
+
.find((className) => className.startsWith('language-'))
|
|
378
|
+
?.replace(/^language-/, '')
|
|
379
|
+
|
|
380
|
+
if (!language) {
|
|
381
|
+
return ''
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return `language-${normalizeCodeLanguage(language)}`
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function normalizeCodeLanguage(value: string) {
|
|
388
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9_-]/g, '').slice(0, 24)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function isSafeHref(value: string) {
|
|
392
|
+
const href = value.trim()
|
|
393
|
+
|
|
394
|
+
if (href.startsWith('#')) {
|
|
395
|
+
return true
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
try {
|
|
399
|
+
const url = new URL(href, window.location.origin)
|
|
400
|
+
|
|
401
|
+
return ['http:', 'https:', 'mailto:', 'tel:'].includes(url.protocol)
|
|
402
|
+
} catch {
|
|
403
|
+
return false
|
|
404
|
+
}
|
|
405
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { request } from '@/core/request'
|
|
2
|
+
|
|
3
|
+
export interface HealthCheckResult {
|
|
4
|
+
status?: string
|
|
5
|
+
timestamp?: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function healthCheckApi() {
|
|
9
|
+
return request<HealthCheckResult>({
|
|
10
|
+
url: '/uaa/announcement/get/restricted',
|
|
11
|
+
method: 'GET',
|
|
12
|
+
})
|
|
13
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildTamRequestPayload,
|
|
3
|
+
tamRequest,
|
|
4
|
+
type TamRequestPayload,
|
|
5
|
+
type TamResponse,
|
|
6
|
+
} from '@/api/tam'
|
|
7
|
+
|
|
8
|
+
export type PbcPositionSendPayload = {
|
|
9
|
+
appHead: TamRequestPayload<PbcPositionEmptyBody>['appHead']
|
|
10
|
+
body: PbcPositionEmptyBody
|
|
11
|
+
head: TamRequestPayload<PbcPositionEmptyBody>['head']
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type PbcPositionQueryPayload = {
|
|
15
|
+
appHead: TamRequestPayload<PbcPositionEmptyBody>['appHead']
|
|
16
|
+
body: PbcPositionEmptyBody
|
|
17
|
+
head: TamRequestPayload<PbcPositionEmptyBody>['head']
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type PbcPositionDetailPayload = {
|
|
21
|
+
appHead: TamRequestPayload<PbcPositionDetailRequestBody>['appHead']
|
|
22
|
+
body: PbcPositionDetailRequestBody
|
|
23
|
+
head: TamRequestPayload<PbcPositionDetailRequestBody>['head']
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type PbcPositionSendResult = {
|
|
27
|
+
body: PbcPositionSendBody
|
|
28
|
+
head: TamResponse<PbcPositionSendBody>['head']
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type PbcPositionQueryResult = {
|
|
32
|
+
body: PbcPositionQueryBody
|
|
33
|
+
head: TamResponse<PbcPositionQueryBody>['head']
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type PbcPositionDetailResult = {
|
|
37
|
+
body: PbcPositionDetailBody
|
|
38
|
+
head: TamResponse<PbcPositionDetailBody>['head']
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type PbcPositionRecord = {
|
|
42
|
+
/** 应答平台流水号,查询明细入参。 */
|
|
43
|
+
refNo?: string
|
|
44
|
+
/** 清算账户行号。 */
|
|
45
|
+
settleBankNo?: string
|
|
46
|
+
/** 清算账户余额,接口按元返回。 */
|
|
47
|
+
settleBal?: string
|
|
48
|
+
/** 可用头寸,接口按元返回。 */
|
|
49
|
+
avaAmt?: string
|
|
50
|
+
/** 预期头寸,接口按元返回。 */
|
|
51
|
+
expectAmt?: string
|
|
52
|
+
/** 更新时间。 */
|
|
53
|
+
updateTime?: string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type PbcPositionDetailBody = PbcPositionRecord & {
|
|
57
|
+
/** 申请平台流水号。 */
|
|
58
|
+
refNo?: string
|
|
59
|
+
/** 应答平台流水号。 */
|
|
60
|
+
resRefNo?: string
|
|
61
|
+
/** 排队借记总金额,接口按元返回。 */
|
|
62
|
+
lineDrAmt?: string
|
|
63
|
+
/** 排队贷记总金额,接口按元返回。 */
|
|
64
|
+
lineCrAmt?: string
|
|
65
|
+
/** 所辖非清算账户数。 */
|
|
66
|
+
subAcctNumber?: string
|
|
67
|
+
/** 所辖非清算总余额,接口按元返回。 */
|
|
68
|
+
subAcctAmt?: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type PbcPositionBalance = PbcPositionRecord &
|
|
72
|
+
PbcPositionDetailBody & {
|
|
73
|
+
/** 发送交易返回的申请平台流水号。 */
|
|
74
|
+
requestRefNo?: string
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
type PbcPositionEmptyBody = Record<string, never>
|
|
78
|
+
|
|
79
|
+
type PbcPositionSendBody = {
|
|
80
|
+
refNo?: string
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
type PbcPositionQueryBody =
|
|
84
|
+
| PbcPositionRecord[]
|
|
85
|
+
| {
|
|
86
|
+
resultList?: PbcPositionRecord[]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
type PbcPositionDetailRequestBody = {
|
|
90
|
+
refNo: string
|
|
91
|
+
settleBankNo: string
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const SEND_TRAN_CODE = '/TAM/0001/140104009'
|
|
95
|
+
const QUERY_TRAN_CODE = '/TAM/0001/140104010'
|
|
96
|
+
const DETAIL_TRAN_CODE = '/TAM/0001/140104011'
|
|
97
|
+
|
|
98
|
+
export function buildPbcPositionSendPayload(): PbcPositionSendPayload {
|
|
99
|
+
return buildTamRequestPayload({
|
|
100
|
+
tranCode: SEND_TRAN_CODE,
|
|
101
|
+
body: {},
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function sendPbcPositionRequest(
|
|
106
|
+
payload: PbcPositionSendPayload,
|
|
107
|
+
): Promise<PbcPositionSendResult> {
|
|
108
|
+
return tamRequest<PbcPositionEmptyBody, PbcPositionSendBody>(payload)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function buildPbcPositionQueryPayload(): PbcPositionQueryPayload {
|
|
112
|
+
return buildTamRequestPayload({
|
|
113
|
+
tranCode: QUERY_TRAN_CODE,
|
|
114
|
+
body: {},
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function queryPbcPositionList(
|
|
119
|
+
payload: PbcPositionQueryPayload,
|
|
120
|
+
): Promise<PbcPositionQueryResult> {
|
|
121
|
+
return tamRequest<PbcPositionEmptyBody, PbcPositionQueryBody>(payload)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function buildPbcPositionDetailPayload(
|
|
125
|
+
record: Pick<PbcPositionRecord, 'refNo' | 'settleBankNo'>,
|
|
126
|
+
): PbcPositionDetailPayload {
|
|
127
|
+
return buildTamRequestPayload({
|
|
128
|
+
tranCode: DETAIL_TRAN_CODE,
|
|
129
|
+
body: {
|
|
130
|
+
refNo: record.refNo || '',
|
|
131
|
+
settleBankNo: record.settleBankNo || '',
|
|
132
|
+
},
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function queryPbcPositionDetail(
|
|
137
|
+
payload: PbcPositionDetailPayload,
|
|
138
|
+
): Promise<PbcPositionDetailResult> {
|
|
139
|
+
return tamRequest<PbcPositionDetailRequestBody, PbcPositionDetailBody>(payload)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function queryPbcPositionBalance(requestRefNo: string): Promise<PbcPositionBalance> {
|
|
143
|
+
const queryPayload = buildPbcPositionQueryPayload()
|
|
144
|
+
console.log('[人行头寸余额查询1入参]', queryPayload)
|
|
145
|
+
|
|
146
|
+
const queryResponse = await queryPbcPositionList(queryPayload)
|
|
147
|
+
const records = getPbcPositionRecords(queryResponse.body)
|
|
148
|
+
|
|
149
|
+
if (records.length === 0) {
|
|
150
|
+
throw new Error('人行头寸余额查询结果为空')
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
for (const record of records) {
|
|
154
|
+
if (!record.refNo || !record.settleBankNo) continue
|
|
155
|
+
|
|
156
|
+
const detailPayload = buildPbcPositionDetailPayload(record)
|
|
157
|
+
console.log('[人行头寸余额查询2入参]', detailPayload)
|
|
158
|
+
|
|
159
|
+
const detailResponse = await queryPbcPositionDetail(detailPayload)
|
|
160
|
+
|
|
161
|
+
if (detailResponse.body.refNo !== requestRefNo) continue
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
...record,
|
|
165
|
+
...detailResponse.body,
|
|
166
|
+
requestRefNo,
|
|
167
|
+
resRefNo: detailResponse.body.resRefNo || record.refNo,
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
throw new Error(`未查询到编号 ${requestRefNo} 对应的余额结果`)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function getPbcPositionRecords(body: PbcPositionQueryBody) {
|
|
175
|
+
if (Array.isArray(body)) return body
|
|
176
|
+
|
|
177
|
+
return body.resultList || []
|
|
178
|
+
}
|