@nstc-business/imes 1.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/package.json +71 -0
- package/src/components/common.js +419 -0
- package/src/components/model-calculate/index.vue +150 -0
- package/src/components/model-calculate/input-number.vue +294 -0
- package/src/components/model-calculate/modelCalcTest.vue +1222 -0
- package/src/components/model-calculate/tree.vue +135 -0
- package/src/index.js +24 -0
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nstc-business/imes",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"serve": "vue-cli-service serve",
|
|
8
|
+
"test": "vue-cli-service serve --test",
|
|
9
|
+
"deadcode": "vue-cli-service serve --seecode",
|
|
10
|
+
"analy": "vue-cli-service build --analy",
|
|
11
|
+
"getI18n": "node ./node_modules/nstc-get-i18n",
|
|
12
|
+
"format": "prettier --write \"./**/*.{html,vue,ts,js,json,md}\"",
|
|
13
|
+
"build": "vue-cli-service build"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"axios": "^0.21.4",
|
|
20
|
+
"core-js": "^3.6.5",
|
|
21
|
+
"dayjs": "^1.10.7",
|
|
22
|
+
"echarts": "^5.3.3",
|
|
23
|
+
"el-table-draggable": "^1.4.4",
|
|
24
|
+
"element-ui": "^2.15.6",
|
|
25
|
+
"jquery": "^3.6.0",
|
|
26
|
+
"moment": "^2.30.1",
|
|
27
|
+
"n20-common-lib": "2.5.18",
|
|
28
|
+
"qrcode": "^1.5.0",
|
|
29
|
+
"resize-detector": "^0.3.0",
|
|
30
|
+
"swiper": "^11.1.14",
|
|
31
|
+
"vue": "^2.6.11",
|
|
32
|
+
"vue-router": "^3.5.2",
|
|
33
|
+
"vuedraggable": "^2.24.3",
|
|
34
|
+
"vuex": "^3.6.2",
|
|
35
|
+
"vxe-table": "3.6.17",
|
|
36
|
+
"xe-utils": "^3.5.11"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"src/components",
|
|
40
|
+
"src/index.js"
|
|
41
|
+
],
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@babel/plugin-proposal-optional-chaining": "^7.14.5",
|
|
44
|
+
"@babel/plugin-transform-flow-comments": "^7.14.5",
|
|
45
|
+
"@vue/cli-plugin-babel": "~4.5.0",
|
|
46
|
+
"@vue/cli-plugin-eslint": "~4.5.0",
|
|
47
|
+
"@vue/cli-service": "~4.5.0",
|
|
48
|
+
"@vue/compiler-sfc": "^3.0.0-rc.6",
|
|
49
|
+
"babel-eslint": "^10.1.0",
|
|
50
|
+
"babel-plugin-component": "^1.1.1",
|
|
51
|
+
"compression-webpack-plugin": "^3.1.0",
|
|
52
|
+
"copy-webpack-plugin": "^6.4.1",
|
|
53
|
+
"eslint": "^6.7.2",
|
|
54
|
+
"prettier": "^3.0.3",
|
|
55
|
+
"husky": "^8.0.0",
|
|
56
|
+
"eslint-plugin-vue": "^6.2.2",
|
|
57
|
+
"less": "^3.12.2",
|
|
58
|
+
"nstc-gitinfo": "^0.0.6",
|
|
59
|
+
"nstc-get-i18n": "^0.0.3",
|
|
60
|
+
"less-loader": "^7.0.0",
|
|
61
|
+
"vue-template-compiler": "^2.6.11",
|
|
62
|
+
"webpack": "^4.46.0",
|
|
63
|
+
"webpack-bundle-analyzer": "^3.9.0",
|
|
64
|
+
"webpack-deadcode-plugin": "^0.1.15"
|
|
65
|
+
},
|
|
66
|
+
"browserslist": [
|
|
67
|
+
"> 1%",
|
|
68
|
+
"last 2 versions",
|
|
69
|
+
"not dead"
|
|
70
|
+
]
|
|
71
|
+
}
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { N } from 'n20-common-lib'
|
|
2
|
+
|
|
3
|
+
export function formatDate(date, fmt) {
|
|
4
|
+
date = new Date(date)
|
|
5
|
+
if (/(y+)/.test(fmt)) {
|
|
6
|
+
fmt = fmt.replace(
|
|
7
|
+
RegExp.$1,
|
|
8
|
+
(date.getFullYear() + '').substr(4 - RegExp.$1.length)
|
|
9
|
+
)
|
|
10
|
+
}
|
|
11
|
+
let o = {
|
|
12
|
+
'M+': date.getMonth() + 1,
|
|
13
|
+
'd+': date.getDate(),
|
|
14
|
+
'h+': date.getHours(),
|
|
15
|
+
'm+': date.getMinutes(),
|
|
16
|
+
's+': date.getSeconds()
|
|
17
|
+
}
|
|
18
|
+
for (let k in o) {
|
|
19
|
+
if (new RegExp(`(${k})`).test(fmt)) {
|
|
20
|
+
let str = o[k] + ''
|
|
21
|
+
fmt = fmt.replace(
|
|
22
|
+
RegExp.$1,
|
|
23
|
+
RegExp.$1.length === 1 ? str : padLeftZero(str)
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return fmt
|
|
28
|
+
}
|
|
29
|
+
function padLeftZero(str) {
|
|
30
|
+
return ('00' + str).substr(str.length)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 公式转换,支持复杂运算逻辑
|
|
35
|
+
* @param {array} statisticItems 数据映射数组
|
|
36
|
+
* @param {string} formula 公式
|
|
37
|
+
* @returns {object} codeFormula cod公式,randomFormula 随机数公式
|
|
38
|
+
*/
|
|
39
|
+
export const replaceStatsNameWithCode = (statisticItems, formula) => {
|
|
40
|
+
// 创建statsName到code的映射
|
|
41
|
+
const nameToCodeMap = statisticItems.reduce((map, item) => {
|
|
42
|
+
if (item.formula) {
|
|
43
|
+
map[item.statsName] = {
|
|
44
|
+
formula: item.formula
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
map[item.statsName] = item.code
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return map
|
|
51
|
+
}, {})
|
|
52
|
+
|
|
53
|
+
// 创建statsName到随机数的映射
|
|
54
|
+
const nameToRandomMap = statisticItems.reduce((map, item) => {
|
|
55
|
+
if (item.formula) {
|
|
56
|
+
map[item.statsName] = {
|
|
57
|
+
formula: item.formula
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
map[item.statsName] = Math.random().toFixed(10)
|
|
61
|
+
}
|
|
62
|
+
return map
|
|
63
|
+
}, {})
|
|
64
|
+
|
|
65
|
+
// 增强的正则表达式,支持多层嵌套的复杂表达式
|
|
66
|
+
const complexPattern =
|
|
67
|
+
/(?:\([^()]*\)|[\u4e00-\u9fa5a-zA-Z0-9-]+)(?:\s*[+\-*/]\s*(?:\([^()]*\)|[\u4e00-\u9fa5a-zA-Z0-9-]+))*/g
|
|
68
|
+
|
|
69
|
+
// 生成code公式
|
|
70
|
+
const codeFormula = formula.replace(complexPattern, match => {
|
|
71
|
+
// 处理带括号的表达式
|
|
72
|
+
const hasParentheses = match.startsWith('(') && match.endsWith(')')
|
|
73
|
+
const content = hasParentheses ? match.slice(1, -1) : match
|
|
74
|
+
|
|
75
|
+
// 先替换括号内的内容
|
|
76
|
+
const innerReplaced = content.replace(
|
|
77
|
+
/[\u4e00-\u9fa5a-zA-Z0-9-]+/g,
|
|
78
|
+
part =>
|
|
79
|
+
typeof nameToCodeMap[part] === 'object'
|
|
80
|
+
? nameToCodeMap[part]?.formula
|
|
81
|
+
: nameToCodeMap[part] === undefined
|
|
82
|
+
? part
|
|
83
|
+
: `#${nameToCodeMap[part]}#`
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
// 再处理运算符
|
|
87
|
+
const result = innerReplaced
|
|
88
|
+
.split(/([+\-*/()])/)
|
|
89
|
+
.map(part => {
|
|
90
|
+
const trimmed = part.trim()
|
|
91
|
+
return nameToCodeMap[trimmed] ? `#${nameToCodeMap[trimmed]}#` : part
|
|
92
|
+
})
|
|
93
|
+
.join('')
|
|
94
|
+
|
|
95
|
+
return hasParentheses ? `(${result})` : result
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// 生成随机数公式
|
|
99
|
+
const randomFormula = formula.replace(complexPattern, match => {
|
|
100
|
+
// 处理带括号的表达式
|
|
101
|
+
const hasParentheses = match.startsWith('(') && match.endsWith(')')
|
|
102
|
+
const content = hasParentheses ? match.slice(1, -1) : match
|
|
103
|
+
|
|
104
|
+
// 先替换括号内的内容
|
|
105
|
+
const innerReplaced = content.replace(
|
|
106
|
+
/[\u4e00-\u9fa5a-zA-Z0-9-]+/g,
|
|
107
|
+
part => {
|
|
108
|
+
const random = nameToRandomMap[part]
|
|
109
|
+
return typeof random === 'object'
|
|
110
|
+
? random.formula
|
|
111
|
+
: random === undefined
|
|
112
|
+
? part
|
|
113
|
+
: random
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
// 再处理运算符
|
|
118
|
+
const result = innerReplaced
|
|
119
|
+
.split(/([+\-*/()])/)
|
|
120
|
+
.map(part => {
|
|
121
|
+
const trimmed = part.trim()
|
|
122
|
+
const random = nameToRandomMap[trimmed]
|
|
123
|
+
return random === undefined ? part : random
|
|
124
|
+
})
|
|
125
|
+
.join('')
|
|
126
|
+
|
|
127
|
+
return hasParentheses ? `(${result})` : result
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
codeFormula,
|
|
132
|
+
randomFormula
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 设置表格合并行的公共方法
|
|
138
|
+
* @param {any[]} list 表格数据
|
|
139
|
+
* @param {any[]} columns 表头
|
|
140
|
+
* @param {String[]} merges 要合并的列字段名组成的数组
|
|
141
|
+
* @param {String[]} condition 判断条件的字段名组成的数组
|
|
142
|
+
* @param {String} field 表头取值的字段名
|
|
143
|
+
*/
|
|
144
|
+
|
|
145
|
+
export function setMergeCells({
|
|
146
|
+
list = [],
|
|
147
|
+
columns = [],
|
|
148
|
+
merges = [],
|
|
149
|
+
condition = [],
|
|
150
|
+
field = 'field'
|
|
151
|
+
}) {
|
|
152
|
+
const validMap = new Map() // 有效合并配置项
|
|
153
|
+
for (let i = 0; i < list.length; i++) {
|
|
154
|
+
// 根据判断条件取出当前行对应的表格数据,生成唯一标识
|
|
155
|
+
const hashKey = condition
|
|
156
|
+
.map(key => {
|
|
157
|
+
let rowStr = ''
|
|
158
|
+
if (
|
|
159
|
+
Object.prototype.toString.call(list[i][key]) === '[object Object]'
|
|
160
|
+
) {
|
|
161
|
+
rowStr = list[i][key].code
|
|
162
|
+
} else {
|
|
163
|
+
rowStr = list[i][key]
|
|
164
|
+
}
|
|
165
|
+
return rowStr
|
|
166
|
+
})
|
|
167
|
+
.join('&&')
|
|
168
|
+
|
|
169
|
+
let flag = true
|
|
170
|
+
let count = 1
|
|
171
|
+
// 从当前索引i开始,往后找,数据相同的话,count自增,否则跳出while循环
|
|
172
|
+
while (flag && i + count < list.length) {
|
|
173
|
+
// 根据判断条件取出下一行的表格数据,生成唯一标识
|
|
174
|
+
const nextRow = condition
|
|
175
|
+
.map(key => {
|
|
176
|
+
let rowStr = ''
|
|
177
|
+
if (
|
|
178
|
+
Object.prototype.toString.call(list[i + count][key]) ===
|
|
179
|
+
'[object Object]'
|
|
180
|
+
) {
|
|
181
|
+
rowStr = list[i + count][key].code
|
|
182
|
+
} else {
|
|
183
|
+
rowStr = list[i + count][key]
|
|
184
|
+
}
|
|
185
|
+
return rowStr
|
|
186
|
+
})
|
|
187
|
+
.join('&&')
|
|
188
|
+
if (hashKey === nextRow) {
|
|
189
|
+
// 数据相同,count自增
|
|
190
|
+
count++
|
|
191
|
+
} else {
|
|
192
|
+
// 数据不同,跳出while循环
|
|
193
|
+
flag = false
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// 将有效的合并配置项存入validMap
|
|
197
|
+
count > 1 && validMap.set(hashKey + '_' + i, { row: i, rowspan: count })
|
|
198
|
+
// i 跳过已合并的行,减 1 是因为 i++
|
|
199
|
+
i += count - 1
|
|
200
|
+
// 重置 while 循环标识
|
|
201
|
+
flag = true
|
|
202
|
+
}
|
|
203
|
+
// 合并记录转换
|
|
204
|
+
const recordsList = [...validMap.values()]
|
|
205
|
+
if (recordsList.length == 0) {
|
|
206
|
+
return []
|
|
207
|
+
}
|
|
208
|
+
// 预处理列索引
|
|
209
|
+
|
|
210
|
+
const cols = merges.reduce((acc, mergeField) => {
|
|
211
|
+
const idx = columns.findIndex(item => item[field] === mergeField)
|
|
212
|
+
idx !== -1 && acc.push(idx)
|
|
213
|
+
return acc
|
|
214
|
+
}, [])
|
|
215
|
+
|
|
216
|
+
// 遍历列索引,生成合并单元格配置集合
|
|
217
|
+
const mergeCells = recordsList.flatMap(({ row, rowspan }) =>
|
|
218
|
+
cols.map(col => ({ row, col, rowspan, colspan: 1 }))
|
|
219
|
+
)
|
|
220
|
+
return mergeCells
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export const formatFloat2 = (num, digit3 = 2) => {
|
|
224
|
+
return parseFloat(N.subFixed(num, digit3))
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* 校验Table每行字段是否为空
|
|
229
|
+
* @param {Object} requiredFields 字段映射对象,例:{minVal: '最小值', maxVal: '最大值', score: '分数'}
|
|
230
|
+
* @param {Array} data 要验证的数据数组
|
|
231
|
+
* @returns {Error|null} 验证通过返回null,失败返回Error对象
|
|
232
|
+
*/
|
|
233
|
+
export const validateFieldPresence = (requiredFields = {}, data = []) => {
|
|
234
|
+
// 添加参数有效性检查
|
|
235
|
+
if (!Array.isArray(data)) return new Error('数据参数必须为数组')
|
|
236
|
+
|
|
237
|
+
for (const [index, row] of data.entries()) {
|
|
238
|
+
// 遍历字段映射对象的键
|
|
239
|
+
for (const field of Object.keys(requiredFields)) {
|
|
240
|
+
// 添加字段存在性检查
|
|
241
|
+
if (!Object.prototype.hasOwnProperty.call(row, field)) {
|
|
242
|
+
return new Error(`第${index + 1}条规则:当前行缺少必要字段[${field}]`)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// 加强空值判断
|
|
246
|
+
if (row[field] === '' || row[field] == null) {
|
|
247
|
+
return new Error(
|
|
248
|
+
`第${index + 1}条规则:${requiredFields[field] || field}不能为空`
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// 明确返回验证通过状态
|
|
255
|
+
return null
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* 校验数字格式
|
|
260
|
+
* @param {Object} options 配置项
|
|
261
|
+
* @param {Object} options.requiredFields 字段映射对象 {minVal: '最小值', maxVal: '最大值'}
|
|
262
|
+
* @param {Array} options.data 要验证的数据数组
|
|
263
|
+
* @param {RegExp} options.validRegex 验证正则,默认/^-?\d+(?:\.\d{1,6})?$/
|
|
264
|
+
* @returns {Error|null}
|
|
265
|
+
*/
|
|
266
|
+
export const validateNumberFormat = ({
|
|
267
|
+
requiredFields = {},
|
|
268
|
+
data = [],
|
|
269
|
+
validRegex = /^-?\d+(?:\.\d{1,6})?$/
|
|
270
|
+
} = {}) => {
|
|
271
|
+
if (!Array.isArray(data)) return new Error('数据参数必须为数组')
|
|
272
|
+
|
|
273
|
+
for (const [index, row] of data.entries()) {
|
|
274
|
+
for (const [field, fieldName] of Object.entries(requiredFields)) {
|
|
275
|
+
const value = String(row[field] ?? '') // 处理undefined/null
|
|
276
|
+
|
|
277
|
+
if (!validRegex.test(value)) {
|
|
278
|
+
return new Error(`第${index + 1}条规则:${fieldName}格式不正确`)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return null
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* 校验区间重叠
|
|
287
|
+
* @param {Array} data
|
|
288
|
+
* @param {Object} requiredFields = {},
|
|
289
|
+
* @returns
|
|
290
|
+
*/
|
|
291
|
+
export const validateRangeOverlap = (
|
|
292
|
+
data = [],
|
|
293
|
+
requiredFields = {
|
|
294
|
+
minVal: '下限',
|
|
295
|
+
maxVal: '上限'
|
|
296
|
+
}
|
|
297
|
+
) => {
|
|
298
|
+
if (!Array.isArray(data)) return new Error('数据参数必须为数组')
|
|
299
|
+
// 增强参数校验
|
|
300
|
+
const fields = Object.keys(requiredFields)
|
|
301
|
+
if (fields.length !== 2) {
|
|
302
|
+
return new Error('字段映射对象必须且只能包含两个字段[minKey, maxKey]')
|
|
303
|
+
}
|
|
304
|
+
const [minKey, maxKey] = fields
|
|
305
|
+
|
|
306
|
+
for (let i = 0; i < data.length; i++) {
|
|
307
|
+
const current = data[i]
|
|
308
|
+
|
|
309
|
+
// 合并字段存在性检查
|
|
310
|
+
for (const key of [minKey, maxKey]) {
|
|
311
|
+
// 修复属性检查方式,增加对象类型判断
|
|
312
|
+
if (
|
|
313
|
+
!current ||
|
|
314
|
+
typeof current !== 'object' ||
|
|
315
|
+
!Object.prototype.hasOwnProperty.call(current, key)
|
|
316
|
+
) {
|
|
317
|
+
return new Error(`第${i + 1}条规则:缺少${requiredFields[key]}字段`)
|
|
318
|
+
}
|
|
319
|
+
// 空值检查添加到此处
|
|
320
|
+
if (current[key] === '' || current[key] == null) {
|
|
321
|
+
return new Error(`第${i + 1}条规则:${requiredFields[key]}不能为空`)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// 增强数值转换
|
|
325
|
+
const [minVal, maxVal] = [Number(current[minKey]), Number(current[maxKey])]
|
|
326
|
+
if (isNaN(minVal)) {
|
|
327
|
+
return new Error(`第${i + 1}条规则:${requiredFields[minKey]}值无效`)
|
|
328
|
+
}
|
|
329
|
+
if (isNaN(maxVal)) {
|
|
330
|
+
return new Error(`第${i + 1}条规则:${requiredFields[maxKey]}值无效`)
|
|
331
|
+
}
|
|
332
|
+
if (minVal >= maxVal) {
|
|
333
|
+
return new Error(
|
|
334
|
+
`第${i + 1}条规则:${requiredFields[minKey]}不能大于等于${requiredFields[maxKey]}`
|
|
335
|
+
)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// 优化区间检查性能
|
|
339
|
+
for (let j = i + 1; j < data.length; j++) {
|
|
340
|
+
const other = data[j]
|
|
341
|
+
// 添加对other行的校验
|
|
342
|
+
if ([minKey, maxKey].some(k => !Object.prototype.hasOwnProperty.call(other, k))) continue
|
|
343
|
+
|
|
344
|
+
if (isRangeOverlap(current, other, minKey, maxKey)) {
|
|
345
|
+
return new Error(`第${i + 1}条和第${j + 1}条规则存在区间重叠`)
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return null
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export const isRangeOverlap = (a, b, minKey = 'minVal', maxKey = 'maxVal') => {
|
|
353
|
+
const aMin = Number(a[minKey])
|
|
354
|
+
const aMax = Number(a[maxKey])
|
|
355
|
+
const bMin = Number(b[minKey])
|
|
356
|
+
const bMax = Number(b[maxKey])
|
|
357
|
+
|
|
358
|
+
return (
|
|
359
|
+
!isNaN(aMin) &&
|
|
360
|
+
!isNaN(aMax) &&
|
|
361
|
+
!isNaN(bMin) &&
|
|
362
|
+
!isNaN(bMax) &&
|
|
363
|
+
Math.max(aMin, bMin) < Math.min(aMax, bMax)
|
|
364
|
+
)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function toFixedTrunc(num, decimals) {
|
|
368
|
+
if (isNaN(num) || num === undefined || num === null) return num
|
|
369
|
+
|
|
370
|
+
let numStr =
|
|
371
|
+
typeof num !== 'string'
|
|
372
|
+
? N.toString(num).replace(/,/g, '')
|
|
373
|
+
: num.replace(/,/g, '')
|
|
374
|
+
const [integerPart, decimalPart = ''] = numStr.split('.')
|
|
375
|
+
const trimmedDecimal = decimalPart.slice(0, decimals)
|
|
376
|
+
const paddedDecimal = trimmedDecimal.padEnd(decimals, '0')
|
|
377
|
+
return decimals === 0 ? integerPart : `${integerPart}.${paddedDecimal}`
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function addThousands(data) {
|
|
381
|
+
return N.addThousands(data)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* 深度克隆
|
|
386
|
+
* @param target
|
|
387
|
+
* @return {}
|
|
388
|
+
*/
|
|
389
|
+
export const deepClone = (target, map = new Map()) => {
|
|
390
|
+
// 检测数据的类型
|
|
391
|
+
if (typeof target === 'object' && target !== null) {
|
|
392
|
+
// 克隆数据之前, 进行判断, 数据之前是否克隆过
|
|
393
|
+
const cache = map.get(target)
|
|
394
|
+
if (cache) {
|
|
395
|
+
return cache
|
|
396
|
+
}
|
|
397
|
+
// 判断目标数据的类型
|
|
398
|
+
const isArray = Array.isArray(target)
|
|
399
|
+
// 创建一个容器
|
|
400
|
+
const result = isArray ? [] : {}
|
|
401
|
+
// 将新的结果存入到容器中
|
|
402
|
+
map.set(target, result)
|
|
403
|
+
// 如果目标数据为数组
|
|
404
|
+
if (isArray) {
|
|
405
|
+
// forEach 遍历
|
|
406
|
+
target.forEach((item, index) => {
|
|
407
|
+
result[index] = deepClone(item, map)
|
|
408
|
+
})
|
|
409
|
+
} else {
|
|
410
|
+
// 如果是对象, 获取所有的键名, 然后 forEach 遍历
|
|
411
|
+
Object.keys(target).forEach((key) => {
|
|
412
|
+
result[key] = deepClone(target[key], map)
|
|
413
|
+
})
|
|
414
|
+
}
|
|
415
|
+
return result
|
|
416
|
+
} else {
|
|
417
|
+
return target
|
|
418
|
+
}
|
|
419
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<page>
|
|
3
|
+
<template slot="header">
|
|
4
|
+
<expandable-pane title="模型信息">
|
|
5
|
+
<descriptions>
|
|
6
|
+
<el-descriptions-item label="模型编号">
|
|
7
|
+
{{ model.code }}
|
|
8
|
+
</el-descriptions-item>
|
|
9
|
+
<el-descriptions-item label="模型名称">
|
|
10
|
+
{{ model.name }}
|
|
11
|
+
</el-descriptions-item>
|
|
12
|
+
<el-descriptions-item label="模型版本">
|
|
13
|
+
{{ '版本' + model.version }}
|
|
14
|
+
</el-descriptions-item>
|
|
15
|
+
</descriptions>
|
|
16
|
+
</expandable-pane>
|
|
17
|
+
</template>
|
|
18
|
+
<expandable-pane title="模型指标">
|
|
19
|
+
<div class="flex-box">
|
|
20
|
+
<tree disable :modelTree="model.modelTreeVO"/>
|
|
21
|
+
<modelCalcTest
|
|
22
|
+
v-if="(model.type === 1 || model.type === 2) && model.code"
|
|
23
|
+
class="m-l-xl flex-item overflow"
|
|
24
|
+
ref="calcTable"
|
|
25
|
+
@changeLoading="changeLoading"
|
|
26
|
+
@calcResult="calcResult"
|
|
27
|
+
:modelData="model"
|
|
28
|
+
:modelCode="model.code"
|
|
29
|
+
:modelId="model.modelID"
|
|
30
|
+
></modelCalcTest>
|
|
31
|
+
</div>
|
|
32
|
+
</expandable-pane>
|
|
33
|
+
<div slot="footer">
|
|
34
|
+
<expandable-pane title="模型结果">
|
|
35
|
+
<div>测算结果:{{ model.showStyle === 2 ? addThousands(result) : result }}</div>
|
|
36
|
+
</expandable-pane>
|
|
37
|
+
<div class="flex-box flex-c flex-v">
|
|
38
|
+
<el-button plain @click="back">返回</el-button>
|
|
39
|
+
<el-button
|
|
40
|
+
type="primary"
|
|
41
|
+
size="small"
|
|
42
|
+
class="preservations"
|
|
43
|
+
@click="calc"
|
|
44
|
+
>
|
|
45
|
+
测算
|
|
46
|
+
</el-button>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</page>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<script>
|
|
53
|
+
import {N, Page, ExpandablePane, Descriptions} from 'n20-common-lib'
|
|
54
|
+
import tree from './tree.vue'
|
|
55
|
+
import modelCalcTest from './modelCalcTest.vue'
|
|
56
|
+
|
|
57
|
+
export default {
|
|
58
|
+
props: {
|
|
59
|
+
modelId: String
|
|
60
|
+
},
|
|
61
|
+
watch: {
|
|
62
|
+
modelId: {
|
|
63
|
+
handler(val) {
|
|
64
|
+
if (val) {
|
|
65
|
+
this.getModelById()
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
immediate: true
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
components: {
|
|
72
|
+
tree,
|
|
73
|
+
modelCalcTest,
|
|
74
|
+
Page,
|
|
75
|
+
ExpandablePane,
|
|
76
|
+
Descriptions
|
|
77
|
+
},
|
|
78
|
+
data() {
|
|
79
|
+
return {
|
|
80
|
+
indicatorList: [],
|
|
81
|
+
model: {type: 1, name: ''},
|
|
82
|
+
result: '',
|
|
83
|
+
loading: false
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
methods: {
|
|
87
|
+
addThousands(value) {
|
|
88
|
+
return N.addThousands(value)
|
|
89
|
+
},
|
|
90
|
+
changeLoading(flag) {
|
|
91
|
+
this.loading = flag
|
|
92
|
+
},
|
|
93
|
+
calcResult(val) {
|
|
94
|
+
this.result = val
|
|
95
|
+
},
|
|
96
|
+
async getModelById() {
|
|
97
|
+
if (!this.modelId) return
|
|
98
|
+
const {data, code} = await this.$axios.post('/imes/v1/indicatorModel/getModelById', {id: this.modelId})
|
|
99
|
+
if (code === 0) {
|
|
100
|
+
this.model = data
|
|
101
|
+
if (this.model.type == 3) {
|
|
102
|
+
this.$set(this, 'modelTreeVO', [this.model.modelTreeVO])
|
|
103
|
+
this.recursionFn(this.modelTreeVO)
|
|
104
|
+
} else {
|
|
105
|
+
this.recursionFn([this.model.modelTreeVO])
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
/*
|
|
110
|
+
处理叶子节点为2的情况,
|
|
111
|
+
*/
|
|
112
|
+
recursionFn(arr) {
|
|
113
|
+
if (arr.length === 0) return arr
|
|
114
|
+
arr.forEach(item => {
|
|
115
|
+
item.label = item.name
|
|
116
|
+
if (item.isLeaf === 2) {
|
|
117
|
+
this.$set(
|
|
118
|
+
item,
|
|
119
|
+
'indicatorDeepList',
|
|
120
|
+
item.children.map(item2 => {
|
|
121
|
+
return {...item2, label: item.name}
|
|
122
|
+
})
|
|
123
|
+
)
|
|
124
|
+
item.children = []
|
|
125
|
+
} else {
|
|
126
|
+
if (item.children?.length) {
|
|
127
|
+
this.recursionFn(item.children)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
},
|
|
132
|
+
calc() {
|
|
133
|
+
this.$refs.calcTable.calc()
|
|
134
|
+
},
|
|
135
|
+
back() {
|
|
136
|
+
this.$router.go(-1)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
</script>
|
|
141
|
+
|
|
142
|
+
<style scoped lang="less">
|
|
143
|
+
.el-form {
|
|
144
|
+
white-space: nowrap;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.model_input {
|
|
148
|
+
width: 70%;
|
|
149
|
+
}
|
|
150
|
+
</style>
|