@nstc-business/imes 1.1.7 → 1.1.10

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": "@nstc-business/imes",
3
- "version": "1.1.7",
3
+ "version": "1.1.10",
4
4
  "private": false,
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -0,0 +1,84 @@
1
+ /**
2
+ * story=2206036 首次测算调整项假 0 不应覆盖指标计算结果
3
+ */
4
+ const fs = require('fs')
5
+ const path = require('path')
6
+
7
+ const helpersSrc = fs.readFileSync(path.join(__dirname, 'helpers.js'), 'utf8')
8
+ // 仅抽取假 0 / 调整值回显相关纯函数,避免拉起 Vue/N 依赖
9
+ const start = helpersSrc.indexOf('function hasEchoValue')
10
+ const end = helpersSrc.indexOf('export function shouldApplyModifiedVal')
11
+ if (start < 0 || end < 0) {
12
+ throw new Error('无法定位假 0 相关函数片段')
13
+ }
14
+ const snippet = helpersSrc
15
+ .slice(start, end)
16
+ .replace(/export function resolveAdjustInputValue/g, 'function resolveAdjustInputValue')
17
+ .replace(/export function resolveCalcEchoValue/g, 'function resolveCalcEchoValue')
18
+ // resolveCalcEchoValue 依赖 isQualIndicator,测试中用桩
19
+ .replace(
20
+ /if \(target\.sourceType === 3 && !isQualIndicator\(target\)\)/,
21
+ 'if (target.sourceType === 3 && !false)'
22
+ )
23
+
24
+ const exported = {}
25
+ new Function(
26
+ 'exports',
27
+ 'function isQualIndicator(){return false}\n' +
28
+ snippet +
29
+ '\nexports.resolveAdjustInputValue = resolveAdjustInputValue' +
30
+ '\nexports.resolveCalcEchoValue = resolveCalcEchoValue'
31
+ )(exported)
32
+
33
+ const { resolveAdjustInputValue, resolveCalcEchoValue } = exported
34
+
35
+ function assert(cond, msg) {
36
+ if (!cond) throw new Error(msg)
37
+ }
38
+
39
+ // 首次测算:后端 modifiedVal=0、result=0、有指标原值 → 假 0,不采用
40
+ assert(
41
+ resolveAdjustInputValue({
42
+ modifyRank: 2,
43
+ modifiedVal: 0,
44
+ indicatorResult: 2120912863.01,
45
+ result: 0,
46
+ calcVal: null
47
+ }) === undefined,
48
+ '有原值+result=0 的 modifiedVal=0 应视为假 0'
49
+ )
50
+
51
+ // 尚无原值时的占位 0 → 假 0
52
+ assert(
53
+ resolveAdjustInputValue({
54
+ modifyRank: 2,
55
+ modifiedVal: 0,
56
+ indicatorResult: null,
57
+ result: null,
58
+ calcVal: null
59
+ }) === undefined,
60
+ '无原值时的 modifiedVal=0 应视为假 0'
61
+ )
62
+
63
+ // 用户真实调成 0:calcVal 落库为 0
64
+ assert(
65
+ resolveAdjustInputValue({
66
+ modifyRank: 2,
67
+ modifiedVal: 0,
68
+ indicatorResult: 2120912863.01,
69
+ result: 0,
70
+ calcVal: 0
71
+ }) === 0,
72
+ 'calcVal=0 时应认定真实调整为 0'
73
+ )
74
+
75
+ // 测算回写:假 0 应回退到 indicatorResult
76
+ assert(
77
+ resolveCalcEchoValue(
78
+ { sourceType: 1, itemType: 21, modifyRank: 2 },
79
+ { modifiedVal: 0, indicatorResult: 353098.98, result: 0, calcVal: null }
80
+ ) === 353098.98,
81
+ '假 0 回写应回退 indicatorResult'
82
+ )
83
+
84
+ console.log('helpers.fakeZero.test.js passed')
@@ -680,14 +680,19 @@ function hasEchoValue(val) {
680
680
 
681
681
  /**
682
682
  * 是否为后端「未调整」时写入的假 0(null 被格式化成 0)。
683
- * 真实「调整为 0」时 result/calcVal 会为 0,且与 indicatorResult 原值不同,不能当成假 0。
684
- * 时间:2026-09-11;人员:AI && 徐红飞;入参:modifiedVal/indicatorResult/result/calcVal;出参:boolean;方法内容:区分假 0 与真实调整为 0
683
+ * 仅用于「调整指标值」(modifyRank=2):得分 result 在授信金额类模型中常为 0,不能当作真实调成 0 的证据。
684
+ * 时间:2026-10-26 16:26:37
685
+ * 人员:AI && 徐红飞
686
+ * 入参:modifiedVal/indicatorResult/result/calcVal
687
+ * 出参:boolean
688
+ * 方法内容:无原值时的 0、或有原值但 calcVal 未按 0 落库的 0,均视为未调整假 0(story 2206036)
685
689
  */
686
690
  function isFakeUnadjustedZero(modifiedVal, indicatorResult, result, calcVal) {
687
691
  if (Number(modifiedVal) !== 0) return false
688
- if (!hasEchoValue(indicatorResult) || Number(indicatorResult) === 0) return false
689
- // 测算结果或 calcVal 已按 0 落库 → 用户真实把指标值调成 0
690
- if (hasEchoValue(result) && Number(result) === 0) return false
692
+ // 尚无指标原值:后端未调整占位 0,不能回显到输入框
693
+ if (!hasEchoValue(indicatorResult)) return true
694
+ if (Number(indicatorResult) === 0) return false
695
+ // 真实把指标值调成 0 后重算,calcVal 会按 0 落库;得分 result 对调整指标值场景不可靠(常为 0)
691
696
  if (hasEchoValue(calcVal) && Number(calcVal) === 0) return false
692
697
  return true
693
698
  }
@@ -727,13 +732,17 @@ export function resolveCalcEchoValue(target, source) {
727
732
  return undefined
728
733
  }
729
734
  if (target.itemType !== 22 && Number(target.modifyRank) === 2) {
730
- return resolveAdjustInputValue({
735
+ const adjustVal = resolveAdjustInputValue({
731
736
  modifyRank: 2,
732
737
  modifiedVal: source.modifiedVal,
733
738
  indicatorResult: source.indicatorResult,
734
739
  result: source.result,
735
740
  calcVal: source.calcVal
736
741
  })
742
+ if (adjustVal !== undefined) return adjustVal
743
+ // 假 0 / 未调整:回退最新测算指标值,避免首次测算后输入框停在 0(story 2206036)
744
+ if (hasEchoValue(source.indicatorResult)) return source.indicatorResult
745
+ return undefined
737
746
  }
738
747
  return undefined
739
748
  }
@@ -368,6 +368,14 @@ export default {
368
368
  type: Object,
369
369
  default: () => ({})
370
370
  },
371
+ /**
372
+ * 打印/另存为文件名(可不含 .pdf)
373
+ * 业务侧传入如「XX公司授信测算详情」;未传时回退 model.code / 测算结果
374
+ */
375
+ downloadFileName: {
376
+ type: String,
377
+ default: ''
378
+ },
371
379
  /**
372
380
  * 是否默认收起分组/分区
373
381
  * false(默认):模型加载后全部展开
@@ -1186,11 +1194,9 @@ export default {
1186
1194
  return
1187
1195
  }
1188
1196
  if (action === 'print') {
1189
- printJS({
1190
- printable: res.data,
1191
- type: 'pdf',
1192
- base64: true
1193
- })
1197
+ // PDF blob 直接 printJS 时,Edge「将打印输出另存为」文件名常为空;
1198
+ // 改为带 <title> 的 HTML 壳嵌入 PDF 再打印,便于另存为预填文件名
1199
+ this.printPdfWithFileName(res.data, this.resolvePrintFileBaseName())
1194
1200
  } else {
1195
1201
  this.downloadPdfBase64(res.data)
1196
1202
  }
@@ -1201,6 +1207,84 @@ export default {
1201
1207
  this.printLoading = false
1202
1208
  }
1203
1209
  },
1210
+ /**
1211
+ * 解析打印/下载文件名(不含 .pdf)
1212
+ * 时间:2026-10-29 17:27:04
1213
+ * 人员:AI && 徐红飞
1214
+ * 入参:无
1215
+ * 出参:文件名基名
1216
+ * 方法内容:优先 downloadFileName,否则 model.code,再否则「测算结果」
1217
+ */
1218
+ resolvePrintFileBaseName() {
1219
+ const raw = this.downloadFileName || (this.model && this.model.code) || '测算结果'
1220
+ return String(raw).replace(/\.pdf$/i, '')
1221
+ },
1222
+ /**
1223
+ * 带文件名打印 PDF(base64)
1224
+ * 时间:2026-10-29 18:16:00
1225
+ * 人员:AI && 徐红飞
1226
+ * 入参:base64 - PDF 内容;fileBaseName - 另存为预填名(不含 .pdf)
1227
+ * 出参:无
1228
+ * 方法内容:用 HTML iframe + title + embed 打印,规避 PDF blob 直接打印时另存为文件名为空
1229
+ */
1230
+ printPdfWithFileName(base64, fileBaseName) {
1231
+ const title = String(fileBaseName || '测算结果').replace(/[\\/:*?"<>|]/g, '_')
1232
+ const pure = String(base64 || '').replace(/^data:application\/pdf;base64,/, '')
1233
+ const binary = atob(pure)
1234
+ const len = binary.length
1235
+ const bytes = new Uint8Array(len)
1236
+ for (let i = 0; i < len; i++) {
1237
+ bytes[i] = binary.charCodeAt(i)
1238
+ }
1239
+ const blob = new Blob([bytes], { type: 'application/pdf' })
1240
+ const pdfUrl = URL.createObjectURL(blob)
1241
+ const iframe = document.createElement('iframe')
1242
+ iframe.setAttribute(
1243
+ 'style',
1244
+ 'position:fixed;right:0;bottom:0;width:0;height:0;border:0;visibility:hidden'
1245
+ )
1246
+ document.body.appendChild(iframe)
1247
+ const frameWin = iframe.contentWindow
1248
+ const frameDoc = frameWin.document
1249
+ const safeTitle = title
1250
+ .replace(/&/g, '&amp;')
1251
+ .replace(/</g, '&lt;')
1252
+ .replace(/>/g, '&gt;')
1253
+ .replace(/"/g, '&quot;')
1254
+ frameDoc.open()
1255
+ frameDoc.write(
1256
+ `<!DOCTYPE html><html><head><meta charset="utf-8"><title>${safeTitle}</title></head>` +
1257
+ `<body style="margin:0"><embed src="${pdfUrl}" type="application/pdf" width="100%" height="100%" /></body></html>`
1258
+ )
1259
+ frameDoc.close()
1260
+ const cleanup = () => {
1261
+ try {
1262
+ if (iframe.parentNode) {
1263
+ iframe.parentNode.removeChild(iframe)
1264
+ }
1265
+ } catch (e) {
1266
+ /* ignore */
1267
+ }
1268
+ URL.revokeObjectURL(pdfUrl)
1269
+ }
1270
+ const doPrint = () => {
1271
+ try {
1272
+ frameWin.focus()
1273
+ frameWin.print()
1274
+ } catch (e) {
1275
+ // 回退 printJS(可能仍无文件名,但保证能打)
1276
+ printJS({
1277
+ printable: pure,
1278
+ type: 'pdf',
1279
+ base64: true,
1280
+ documentTitle: title
1281
+ })
1282
+ }
1283
+ setTimeout(cleanup, 2000)
1284
+ }
1285
+ // embed 加载需要一点时间;onload 在部分浏览器对 srcdoc/write 不可靠,用延时兜底
1286
+ setTimeout(doPrint, 400)
1287
+ },
1204
1288
  downloadPdfBase64(base64) {
1205
1289
  const pure = String(base64 || '').replace(/^data:application\/pdf;base64,/, '')
1206
1290
  const binary = atob(pure)
@@ -1213,7 +1297,7 @@ export default {
1213
1297
  const url = URL.createObjectURL(blob)
1214
1298
  const a = document.createElement('a')
1215
1299
  a.href = url
1216
- a.download = `${(this.model && this.model.code) || '测算结果'}.pdf`
1300
+ a.download = `${this.resolvePrintFileBaseName()}.pdf`
1217
1301
  document.body.appendChild(a)
1218
1302
  a.click()
1219
1303
  document.body.removeChild(a)