n20-common-lib 3.1.21 → 3.2.1

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": "n20-common-lib",
3
- "version": "3.1.21",
3
+ "version": "3.2.1",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "serve": "vue-cli-service serve",
@@ -171,6 +171,12 @@ export default {
171
171
  bussId: {
172
172
  type: String,
173
173
  default: ''
174
+ },
175
+ // 开启后,自动把 filterList 中有非空值但不在 GroupData 的项追加到可见列表
176
+ // 默认 false —— 完全向后兼容,仅在 ProFilterView 透传或业务方显式开启时生效
177
+ autoShowActiveFilters: {
178
+ type: Boolean,
179
+ default: false
174
180
  }
175
181
  },
176
182
  data() {
@@ -288,6 +294,65 @@ export default {
288
294
  this.optionsMap = {}
289
295
  },
290
296
  methods: {
297
+ /**
298
+ * 判断某个 filterList 项在当前 model 中是否有非空值
299
+ * 支持 daterange / datetimerange / monthrange / numberrange / slotFields / value 四种形态
300
+ * 非空定义与 activeClassMap 内 hasValue 一致:null/undefined/''/空数组 均为空
301
+ * @param {Object} item - filterList 中的某一项
302
+ * @returns {boolean}
303
+ */
304
+ isFilterActive(item) {
305
+ const hasValue = (val) => {
306
+ if (Array.isArray(val)) return val.length > 0
307
+ return val !== null && val !== undefined && val !== ''
308
+ }
309
+ // 区间型字段:起止任一非空即视为有值
310
+ const rangeTypes = ['daterange', 'datetimerange', 'monthrange']
311
+ if (rangeTypes.includes(item.type)) {
312
+ return hasValue(this.model[item.startDate]) || hasValue(this.model[item.endDate])
313
+ }
314
+ // 数值区间
315
+ if (item.type === 'numberrange') {
316
+ return hasValue(this.model[item.startValue]) || hasValue(this.model[item.endValue])
317
+ }
318
+ // slot 多值绑定:slotFields 中任一字段非空即视为有值
319
+ if (item.slotFields && item.slotFields.length) {
320
+ return item.slotFields.some((field) => hasValue(this.model[field]))
321
+ }
322
+ // 默认单值字段
323
+ if (item.value) {
324
+ return hasValue(this.model[item.value])
325
+ }
326
+ return false
327
+ },
328
+ /**
329
+ * 当 autoShowActiveFilters 开启时,自动把 filterList 中有非空值但不在 GroupData
330
+ * (用户隐藏的列表)的项追加到可见列表,并同步 checkList
331
+ * - 跳过 static 项(由 mackData 单独处理)
332
+ * - 跳过已存在于 GroupData 的项
333
+ * - 以 { ...item } 浅拷贝追加,避免外部 filterList 引用被改
334
+ */
335
+ ensureActiveFiltersVisible() {
336
+ if (!this.autoShowActiveFilters) return
337
+
338
+ // 当前 GroupData 中已有的 id 集合
339
+ const existingIds = new Set(this.GroupData.map((item) => item[this.onlyKey]))
340
+
341
+ // 筛选出:有值 & 不在 GroupData & 非 static 的项
342
+ const missingActiveItems = this.filterList.filter((item) => {
343
+ if (existingIds.has(item[this.onlyKey])) return false
344
+ if (item.static) return false
345
+ return this.isFilterActive(item)
346
+ })
347
+
348
+ if (missingActiveItems.length > 0) {
349
+ // 浅拷贝追加到 GroupData 末尾
350
+ const newItems = missingActiveItems.map((item) => ({ ...item }))
351
+ this.GroupData = [...this.GroupData, ...newItems]
352
+ // 同步 checkList(按 onlyKey 取值)
353
+ this.checkList = this.GroupData.map((res) => res[this.onlyKey])
354
+ }
355
+ },
291
356
  // 保存视图
292
357
  saveSt() {
293
358
  this.$refs.stform.validate(async (valid) => {
@@ -673,6 +738,10 @@ export default {
673
738
  })
674
739
 
675
740
  console.log(this.filterList)
741
+
742
+ // 双保险:在 mackData 末尾兜底执行一次可见性追加
743
+ // 覆盖"接口异步返回隐藏列表后再次渲染"的场景,与 ProFilterView.$nextTick 调用形成双钩子
744
+ this.ensureActiveFiltersVisible()
676
745
  }
677
746
  }
678
747
  }
@@ -14,11 +14,17 @@ export default {
14
14
  column: {
15
15
  type: Number,
16
16
  default: 2
17
+ },
18
+ autoResize: {
19
+ type: Boolean,
20
+ default: true
17
21
  }
18
22
  },
19
23
  created() {
20
- this.resize()
21
- window.addEventListener('resize', this.resize)
24
+ if (this.autoResize) {
25
+ this.resize()
26
+ window.addEventListener('resize', this.resize)
27
+ }
22
28
  },
23
29
  beforeDestroy() {
24
30
  window.removeEventListener('resize', this.resize)
@@ -0,0 +1,248 @@
1
+ <template>
2
+ <cl-page class="CustomFormView">
3
+ <slot name="top"></slot>
4
+ <cl-expandable-pane v-for="(item, index) in formOptions" :key="index" :title="item.tabName">
5
+ <template v-if="item.slotName">
6
+ <slot :name="item.slotName"></slot>
7
+ </template>
8
+ <cl-descriptions>
9
+ <template v-for="(formItem, key) in item.list">
10
+ <!-- 字段标识:数组模式用 formItem.key,对象模式用 v-for 的 key -->
11
+ <el-descriptions-item v-if="formItem.type" :key="formItem.key || key" :label="$l(formItem.label)">
12
+ <template slot="label">
13
+ {{ formItem.label }}
14
+ </template>
15
+ <!-- 插槽 -->
16
+ <template v-if="formItem.type === 'slot'">
17
+ <div>
18
+ <slot :name="formItem.key || key"></slot>
19
+ </div>
20
+ </template>
21
+ <!-- 期限 -->
22
+ <template v-else-if="(formItem.key || key) === 'contractDTO_loanTerm' && formItem.type === 'input'">
23
+ <cl-diff
24
+ v-if="diff"
25
+ :value="`${formValue['contractDTO_loanTerm']} ${
26
+ formValue['contractDTO_termUnit'] === 'DAY'
27
+ ? $l('天')
28
+ : formValue['contractDTO_termUnit'] === 'MONTH'
29
+ ? $l('月')
30
+ : formValue['contractDTO_termUnit'] === 'YEAR'
31
+ ? $l('年')
32
+ : ''
33
+ }`"
34
+ :old-value="`${oldFormValue['contractDTO_loanTerm']} ${
35
+ oldFormValue['contractDTO_termUnit'] === 'DAY'
36
+ ? $l('天')
37
+ : oldFormValue['contractDTO_termUnit'] === 'MONTH'
38
+ ? $l('月')
39
+ : oldFormValue['contractDTO_termUnit'] === 'YEAR'
40
+ ? $l('年')
41
+ : ''
42
+ }`"
43
+ :tooltip="true"
44
+ />
45
+ <div v-else>
46
+ {{ formValue['contractDTO_loanTerm'] }}
47
+ {{
48
+ formValue['contractDTO_termUnit'] === 'DAY'
49
+ ? $l('天')
50
+ : formValue['contractDTO_termUnit'] === 'MONTH'
51
+ ? $l('月')
52
+ : formValue['contractDTO_termUnit'] === 'YEAR'
53
+ ? $l('年')
54
+ : ''
55
+ }}
56
+ </div>
57
+ </template>
58
+ <!-- 结构化/费用编号 -->
59
+ <template
60
+ v-else-if="
61
+ $l(formItem.label) === $l('是否结构化') ||
62
+ ((formItem.key || key) === 'exInfoDTO_feeNo' && (formItem.key || key) !== 'mainFeeCurrencyCode')
63
+ "
64
+ >
65
+ <cl-diff
66
+ v-if="diff"
67
+ :value="(formValue[formItem.key || key] && formValue[formItem.key || key].name) || '--'"
68
+ :old-value="(oldFormValue[formItem.key || key] && oldFormValue[formItem.key || key].name) || '--'"
69
+ :tooltip="true"
70
+ />
71
+ <div v-else>{{ (formValue[formItem.key || key] && formValue[formItem.key || key].name) || '--' }}</div>
72
+ </template>
73
+ <!-- 地区信息 -->
74
+ <template v-else-if="(formItem.key || key) === 'recInfoDTO_areaInfo'">
75
+ <cl-diff
76
+ v-if="diff"
77
+ :value="formValue['recInfoDTO_areaInfoName'] || '--'"
78
+ :old-value="oldFormValue['recInfoDTO_areaInfoName'] || '--'"
79
+ :tooltip="true"
80
+ />
81
+ <div v-else>{{ formValue['recInfoDTO_areaInfoName'] || '--' }}</div>
82
+ </template>
83
+ <!-- 借/贷方 -->
84
+ <template v-else-if="(formItem.key || key) === 'contractDTO_debtorCode' || (formItem.key || key) === 'contractDTO_creditorCode'">
85
+ <cl-diff
86
+ v-if="diff"
87
+ :value="(formValue[formItem.key || key] && formValue[formItem.key || key].name) || '--'"
88
+ :old-value="(oldFormValue[formItem.key || key] && oldFormValue[formItem.key || key].name) || '--'"
89
+ :tooltip="true"
90
+ />
91
+ <div v-else>{{ (formValue[formItem.key || key] && formValue[formItem.key || key].name) || '--' }}</div>
92
+ </template>
93
+ <!-- 金额 -->
94
+ <template v-else-if="formItem.type === 'input-number'">
95
+ <div
96
+ v-if="
97
+ [$l('利率'), $l('合同利率'), $l('利率(%)'), $l('合同利率(%)'), $l('中间价')].includes(
98
+ $l(formItem.label)
99
+ )
100
+ "
101
+ >
102
+ <cl-diff
103
+ v-if="diff"
104
+ :value="(formValue[formItem.key || key] && numerify(formValue[formItem.key || key], '0.000000')) || '--'"
105
+ :old-value="(oldFormValue[formItem.key || key] && numerify(oldFormValue[formItem.key || key], '0.000000')) || '--'"
106
+ :tooltip="true"
107
+ />
108
+ <span v-else>{{ formValue[formItem.key || key] ? numerify(formValue[formItem.key || key], '0.000000') : '--' }}</span>
109
+ </div>
110
+ <div v-else>
111
+ <cl-diff
112
+ v-if="diff"
113
+ :value="(formValue[formItem.key || key] && numerify(formValue[formItem.key || key], '0,0.00')) || '--'"
114
+ :old-value="(oldFormValue[formItem.key || key] && numerify(oldFormValue[formItem.key || key], '0,0.00')) || '--'"
115
+ :tooltip="true"
116
+ />
117
+ <span v-else>{{ formValue[formItem.key || key] ? numerify(formValue[formItem.key || key], '0,0.00') : '--' }}</span>
118
+ </div>
119
+ </template>
120
+ <!-- 来款国家(地区) -->
121
+ <template v-else-if="$l(formItem.label) === $l('来款国家(地区)')">
122
+ <cl-diff
123
+ v-if="diff"
124
+ :value="formValue['applyInfoDTO_areaInfoName'] || '--'"
125
+ :old-value="oldFormValue['applyInfoDTO_areaInfoName'] || '--'"
126
+ :tooltip="true"
127
+ />
128
+ <div v-else>{{ formValue['applyInfoDTO_areaInfoName'] || '--' }}</div>
129
+ </template>
130
+ <!-- 下拉枚举 -->
131
+ <template v-else-if="formItem.type === 'select'">
132
+ <div class="flex-box flex-v">
133
+ <div v-if="formValue[formItem.key || key] && formValue[formItem.key || key].name">
134
+ <cl-diff
135
+ v-if="diff"
136
+ :value="(formValue[formItem.key || key] && formValue[formItem.key || key].name) || '--'"
137
+ :old-value="(oldFormValue[formItem.key || key] && oldFormValue[formItem.key || key].name) || '--'"
138
+ :tooltip="true"
139
+ />
140
+ <span v-else>{{ (formValue[formItem.key || key] && formValue[formItem.key || key].name) || '--' }}</span>
141
+ </div>
142
+ <div v-else-if="formItem.label === '结售汇性质'">
143
+ <cl-diff
144
+ v-if="diff"
145
+ :value="formValue[formItem.key || key] || '--'"
146
+ :old-value="oldFormValue[formItem.key || key] || '--'"
147
+ :tooltip="true"
148
+ />
149
+ <span v-else>{{ formValue[formItem.key || key] || '--' }}</span>
150
+ </div>
151
+ <div v-else>
152
+ <template v-if="diff">
153
+ <cl-diff
154
+ :value="getEnumName(formItem.enumList, formValue[formItem.key || key])"
155
+ :old-value="getEnumName(formItem.enumList, oldFormValue[formItem.key || key])"
156
+ :tooltip="true"
157
+ />
158
+ </template>
159
+ <span v-else>{{ getEnumName(formItem.enumList, formValue[formItem.key || key]) }}</span>
160
+ </div>
161
+ </div>
162
+ </template>
163
+ <!-- 其他类型(含 text 类型,自动支持超长文本 Tooltip) -->
164
+ <template v-else>
165
+ <div>
166
+ <cl-diff
167
+ v-if="diff"
168
+ :value="formValue[formItem.key || key] || '--'"
169
+ :old-value="oldFormValue[formItem.key || key] || '--'"
170
+ :tooltip="true"
171
+ />
172
+ <div v-else>
173
+ <el-tooltip
174
+ v-if="formValue[formItem.key || key] && formValue[formItem.key || key].length > 50"
175
+ class="item"
176
+ effect="dark"
177
+ :content="formValue[formItem.key || key]"
178
+ placement="top"
179
+ >
180
+ <span>{{ formValue[formItem.key || key].slice(0, 50) + '...' }}</span>
181
+ </el-tooltip>
182
+ <span v-else>{{ formValue[formItem.key || key] || '--' }}</span>
183
+ </div>
184
+ </div>
185
+ </template>
186
+ </el-descriptions-item>
187
+ </template>
188
+ </cl-descriptions>
189
+ </cl-expandable-pane>
190
+ <slot name="bottom"></slot>
191
+ </cl-page>
192
+ </template>
193
+
194
+ <script>
195
+ import numerify from 'numerify'
196
+
197
+ import ClDescriptions from '../Descriptions/index.vue'
198
+ import ClDiff from '../Diff/index.vue'
199
+ import ClExpandablePane from '../Expandable/index.vue'
200
+ import ClPage from '../PageLayout/page.vue'
201
+
202
+ export default {
203
+ name: 'DynamicFormView',
204
+ components: { ClPage, ClExpandablePane, ClDescriptions, ClDiff },
205
+ props: {
206
+ // 表单配置
207
+ formOptions: {
208
+ type: Array,
209
+ default: () => {
210
+ return []
211
+ }
212
+ },
213
+ formValue: {
214
+ type: Object,
215
+ default: () => {
216
+ return {}
217
+ }
218
+ },
219
+ diff: {
220
+ type: Boolean,
221
+ default: false
222
+ },
223
+ oldFormValue: {
224
+ type: Object,
225
+ default: () => {
226
+ return {}
227
+ }
228
+ }
229
+ },
230
+ data() {
231
+ return {
232
+ numerify
233
+ }
234
+ },
235
+ methods: {
236
+ getEnumName(enumList, code) {
237
+ if (!enumList?.length) return
238
+ const enumData = Array.isArray(enumList) ? enumList : JSON.parse(enumList)
239
+ return enumData?.find((item) => (item.code || item.value) === code)?.name || '--'
240
+ }
241
+ }
242
+ }
243
+ </script>
244
+ <style scoped>
245
+ .CustomFormView {
246
+ font-size: 14px;
247
+ }
248
+ </style>
@@ -82,6 +82,7 @@
82
82
  :filter-id="filterId"
83
83
  :buss-id="bussId"
84
84
  :model="searchValue"
85
+ :auto-show-active-filters="autoExpandWithValues"
85
86
  only-key="id"
86
87
  :filter-list="filterList"
87
88
  v-bind="$attrs"
@@ -249,10 +250,16 @@ export default {
249
250
  type: Object,
250
251
  default: () => ({})
251
252
  },
252
- // 筛选器默认展开/收起状态
253
+ // 筛选器默认展开/收起状态(与 data 中 filterExpanded 初始值对齐,无新功能)
253
254
  defaultExpanded: {
254
255
  type: Boolean,
255
256
  default: false
257
+ },
258
+ // 当筛选条件有非空值时,是否自动展开筛选区域
259
+ // 默认 false —— 完全向后兼容,业务方按页面粒度显式开启
260
+ autoExpandWithValues: {
261
+ type: Boolean,
262
+ default: false
256
263
  }
257
264
  },
258
265
  data() {
@@ -318,20 +325,32 @@ export default {
318
325
  return obj
319
326
  },
320
327
  filterObj() {
321
- // 合并 slot 类型筛选器的值(来自 initialValue)到 searchValue 中
322
- const slotValues = {}
323
- this.filterList
324
- .filter((item) => item.type === 'slot' && item.value)
325
- .forEach((item) => {
326
- if (this.initialValue && this.initialValue[item.value] !== undefined) {
327
- slotValues[item.value] = this.initialValue[item.value]
328
- }
328
+ const filterKeys = new Set()
329
+ this.filterList.forEach((item) => {
330
+ ;[
331
+ item.value,
332
+ item.startDate,
333
+ item.endDate,
334
+ item.startValue,
335
+ item.endValue,
336
+ ...(item.slotFields || [])
337
+ ].forEach((key) => {
338
+ if (key) filterKeys.add(key)
329
339
  })
340
+ })
341
+
342
+ const extraInitialValues = {}
343
+ Object.keys(this.initialValue || {}).forEach((key) => {
344
+ if (!filterKeys.has(key)) {
345
+ extraInitialValues[key] = this.initialValue[key]
346
+ }
347
+ })
348
+
330
349
  return {
331
350
  // 高级查询组合
332
351
  conditionGroups: this.conditionGroups,
333
- // 筛选条件(合并 slot 类型的值)
334
- searchValue: { ...this.searchValue, ...slotValues },
352
+ // 筛选条件:保留 initialValue 中的额外业务参数,但不覆盖视图自身的筛选字段
353
+ searchValue: { ...extraInitialValues, ...this.searchValue },
335
354
  // 视图id
336
355
  viewId: this.selectItem ? this.selectItem.viewId : null,
337
356
  // 视图类型
@@ -346,6 +365,8 @@ export default {
346
365
 
347
366
  // 智能合并:保留用户已手动修改的值
348
367
  this.searchValue = newVal
368
+ // 父组件更新 initialValue 后,重新判定是否需要自动展开
369
+ this.checkAndAutoExpand()
349
370
  },
350
371
  deep: true,
351
372
  immediate: false
@@ -368,8 +389,57 @@ export default {
368
389
  }
369
390
  // 合并初始筛选值:slot 字段使用默认值,其他字段使用 initialValue 传入的值
370
391
  this.searchValue = { ...this.getInitialSearchValue, ...this.initialValue }
392
+ // 初始挂载后根据筛选值情况决定是否自动展开
393
+ this.checkAndAutoExpand()
371
394
  },
372
395
  methods: {
396
+ /**
397
+ * 判断 searchValue 中是否有非空值
398
+ * 仅扫描 filterList 中声明的字段,忽略 searchValue 中其它无关 key
399
+ * 支持 daterange / numberrange / slotFields / value 四种 field 形态
400
+ * @returns {boolean}
401
+ */
402
+ hasActiveFilters() {
403
+ // 非空判定:null/undefined/''/空数组 均为空
404
+ const hasValue = (val) => {
405
+ if (Array.isArray(val)) return val.length > 0
406
+ return val !== null && val !== undefined && val !== ''
407
+ }
408
+
409
+ return this.filterList.some((item) => {
410
+ // 区间型字段(日期/日期时间/月份):起止任一非空即视为有值
411
+ if (item.type === 'daterange' || item.type === 'datetimerange' || item.type === 'monthrange') {
412
+ return hasValue(this.searchValue[item.startDate]) || hasValue(this.searchValue[item.endDate])
413
+ }
414
+ // 数值区间
415
+ if (item.type === 'numberrange') {
416
+ return hasValue(this.searchValue[item.startValue]) || hasValue(this.searchValue[item.endValue])
417
+ }
418
+ // slot 多值绑定
419
+ if (item.slotFields && item.slotFields.length) {
420
+ return item.slotFields.some((field) => hasValue(this.searchValue[field]))
421
+ }
422
+ // 默认单值字段
423
+ if (item.value) {
424
+ return hasValue(this.searchValue[item.value])
425
+ }
426
+ return false
427
+ })
428
+ },
429
+ /**
430
+ * 当 autoExpandWithValues 开启且当前存在有效筛选值时,自动展开筛选区域
431
+ * 并在下一帧通知 AdvancedFilter 把"有值但被用户隐藏"的筛选项重新拉回可见列表
432
+ */
433
+ checkAndAutoExpand() {
434
+ if (!this.autoExpandWithValues) return
435
+ if (this.hasActiveFilters()) {
436
+ this.filterExpanded = true
437
+ // $nextTick 等 AdvancedFilter 的 mackData 把 GroupData 填充好后再追加
438
+ this.$nextTick(() => {
439
+ this.$refs.filter?.ensureActiveFiltersVisible()
440
+ })
441
+ }
442
+ },
373
443
  // 处理 slot 类型字段的输入事件,同时更新 searchValue 和 initialValue
374
444
  handleSlotInput(fieldName, val) {
375
445
  // 防御性检查:如果 fieldName 无效,不执行任何操作
@@ -648,13 +718,13 @@ export default {
648
718
  this.selectedItem = item.viewName
649
719
  if (item.viewName === '无视图') {
650
720
  this.selectItem = ''
651
- this.searchValue = {}
721
+ this.searchValue = { ...this.getInitialSearchValue, ...this.initialValue }
652
722
  this.conditionGroups = []
653
723
  }
654
724
  if (item.viewType === VIEW_TYPE.ADVANCED) {
655
725
  const config = this.safeParse(item.viewConfig)
656
726
  this.searchForm = config
657
- this.searchValue = config
727
+ this.searchValue = { ...this.getInitialSearchValue, ...config }
658
728
  } else if (item.viewType === VIEW_TYPE.BASIC) {
659
729
  this.conditionGroups = this.safeParse(item.viewConfig).conditionGroups || []
660
730
  this.searchValue = { ...this.getInitialSearchValue }
@@ -494,6 +494,7 @@ export default {
494
494
  // sessionStorage 不可用时忽略
495
495
  }
496
496
  const row = {
497
+ ...item,
497
498
  [this.keys.type]: item.type || item.attno,
498
499
  [this.keys.user]: uname
499
500
  }
package/src/index.js CHANGED
@@ -66,6 +66,7 @@ import DragList from './components/DragList/index.vue'
66
66
  // 动态表单
67
67
  import DynamicField from './components/DynamicField/DynamicField.vue'
68
68
  import DynamicFieldOptions from './components/DynamicField/DynamicFieldOptions.vue'
69
+ import DynamicFormView from './components/DynamicField/DynamicFormView.vue'
69
70
  import DynamicFieldTable from './components/DynamicField/DynamicTable.vue'
70
71
  import ElectronicArchive from './components/ElectronicArchive/index.vue'
71
72
  import Empty from './components/Empty/index.vue'
@@ -192,6 +193,7 @@ const components = [
192
193
  DynamicFieldTable,
193
194
  DynamicField,
194
195
  DynamicFieldOptions,
196
+ DynamicFormView,
195
197
  ContentLoading,
196
198
  ContentNull,
197
199
  NavMenu,