n20-common-lib 3.2.3 → 3.2.5

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.
@@ -4,43 +4,94 @@ const obj = {
4
4
  this.setRules()
5
5
  },
6
6
  data() {
7
- return {}
7
+ return {
8
+ // batch 批处理内部标志:withBatch 包裹期间 _batchDepth>0,
9
+ // 期间 setType 的 requiredFlag 变更只标记 dirty,不立即 setRules,由批次末尾合并触发一次
10
+ _batchDepth: 0,
11
+ _batchRulesDirty: false
12
+ }
8
13
  },
9
14
  methods: {
15
+ // 批处理包裹器:可重入。一次用户动作(如 valueChange/setView)内连续多次 setType,
16
+ // 合并成末尾一次 setRules,避免 O(全表) rules 重建风暴。批次外(含异步逸出的 setType)保持原即时语义。
17
+ withBatch(fn) {
18
+ this._batchDepth++
19
+ try {
20
+ return fn()
21
+ } finally {
22
+ this._batchDepth--
23
+ if (this._batchDepth === 0 && this._batchRulesDirty) {
24
+ this._batchRulesDirty = false
25
+ this.setRules()
26
+ }
27
+ }
28
+ },
10
29
  // 对相关方法进行设置
11
30
  setType(name, value) {
12
31
  this.form.forEach((item) => {
32
+ for (const key in item.list) {
33
+ if (item.list && item.list[key].type === 'group-container') {
34
+ const index = item.list[key].fields.findIndex((id) => {
35
+ return id.fieldCode === name
36
+ })
37
+ if (index !== -1) {
38
+ item.list[key].fields[index] = { ...item.list[key].fields[index], ...value }
39
+ return false
40
+ }
41
+ }
42
+ }
13
43
  if (item.list && item.list[name]) {
14
- console.log(value)
15
44
  item.list[name] = { ...item.list[name], ...value }
16
- console.log(item.list[name])
17
45
  }
18
46
  })
19
- // 更新rules
47
+ // 更新rules:批次内只标记 dirty,批次外立即触发
20
48
  if (value.hasOwnProperty('requiredFlag')) {
21
- this.setRules()
49
+ if (this._batchDepth > 0) {
50
+ this._batchRulesDirty = true
51
+ } else {
52
+ this.setRules()
53
+ }
22
54
  }
23
55
  },
24
56
  // 生成校验rules
25
57
  setRules() {
26
58
  const rules = {}
27
59
  this.form.forEach((item) => {
60
+ let msg = ''
28
61
  for (const key in item.list) {
29
- let msg = ''
30
- if (item.list[key].requiredMessage) {
31
- msg = item.list[key].requiredMessage
32
- } else if (['select', 'date-picker', 'searchSelect', 'daterange'].includes(item.list[key].type)) {
33
- msg = $lc('请选择') + item.list[key].label
62
+ if (item.list && item.list[key].type === 'group-container') {
63
+ item.list[key].fields.forEach((item2) => {
64
+ if (item2.requiredMessage) {
65
+ msg = item2.requiredMessage
66
+ } else if (['select', 'date-picker', 'searchSelect', 'daterange'].includes(item2.type)) {
67
+ msg = $lc('请选择') + item2.label
68
+ } else {
69
+ msg = $lc('请输入') + item2.label
70
+ }
71
+ rules[item2.fieldCode] = [
72
+ {
73
+ required: item2.requiredFlag && item2.type,
74
+ message: msg,
75
+ trigger: ['submit', 'change']
76
+ }
77
+ ]
78
+ })
34
79
  } else {
35
- msg = $lc('请输入') + item.list[key].label
36
- }
37
- rules[key] = [
38
- {
39
- required: item.list[key].requiredFlag && item.list[key].type,
40
- message: msg,
41
- trigger: ['submit', 'change']
80
+ if (item.list[key].requiredMessage) {
81
+ msg = item.list[key].requiredMessage
82
+ } else if (['select', 'date-picker', 'searchSelect', 'daterange'].includes(item.list[key].type)) {
83
+ msg = $lc('请选择') + item.list[key].label
84
+ } else {
85
+ msg = $lc('请输入') + item.list[key].label
42
86
  }
43
- ]
87
+ rules[key] = [
88
+ {
89
+ required: item.list[key].requiredFlag && item.list[key].type,
90
+ message: msg,
91
+ trigger: ['submit', 'change']
92
+ }
93
+ ]
94
+ }
44
95
  }
45
96
  })
46
97
  this.rules = { ...rules, ...this.staticRules }
@@ -81,7 +81,12 @@
81
81
  <div v-else>{{ formValue['recInfoDTO_areaInfoName'] || '--' }}</div>
82
82
  </template>
83
83
  <!-- 借/贷方 -->
84
- <template v-else-if="(formItem.key || key) === 'contractDTO_debtorCode' || (formItem.key || key) === 'contractDTO_creditorCode'">
84
+ <template
85
+ v-else-if="
86
+ (formItem.key || key) === 'contractDTO_debtorCode' ||
87
+ (formItem.key || key) === 'contractDTO_creditorCode'
88
+ "
89
+ >
85
90
  <cl-diff
86
91
  v-if="diff"
87
92
  :value="(formValue[formItem.key || key] && formValue[formItem.key || key].name) || '--'"
@@ -101,20 +106,33 @@
101
106
  >
102
107
  <cl-diff
103
108
  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')) || '--'"
109
+ :value="
110
+ (formValue[formItem.key || key] && numerify(formValue[formItem.key || key], '0.000000')) || '--'
111
+ "
112
+ :old-value="
113
+ (oldFormValue[formItem.key || key] && numerify(oldFormValue[formItem.key || key], '0.000000')) ||
114
+ '--'
115
+ "
106
116
  :tooltip="true"
107
117
  />
108
- <span v-else>{{ formValue[formItem.key || key] ? numerify(formValue[formItem.key || key], '0.000000') : '--' }}</span>
118
+ <span v-else>{{
119
+ formValue[formItem.key || key] ? numerify(formValue[formItem.key || key], '0.000000') : '--'
120
+ }}</span>
109
121
  </div>
110
122
  <div v-else>
111
123
  <cl-diff
112
124
  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')) || '--'"
125
+ :value="
126
+ (formValue[formItem.key || key] && numerify(formValue[formItem.key || key], '0,0.00')) || '--'
127
+ "
128
+ :old-value="
129
+ (oldFormValue[formItem.key || key] && numerify(oldFormValue[formItem.key || key], '0,0.00')) || '--'
130
+ "
115
131
  :tooltip="true"
116
132
  />
117
- <span v-else>{{ formValue[formItem.key || key] ? numerify(formValue[formItem.key || key], '0,0.00') : '--' }}</span>
133
+ <span v-else>{{
134
+ formValue[formItem.key || key] ? numerify(formValue[formItem.key || key], '0,0.00') : '--'
135
+ }}</span>
118
136
  </div>
119
137
  </template>
120
138
  <!-- 来款国家(地区) -->
@@ -137,7 +155,9 @@
137
155
  :old-value="(oldFormValue[formItem.key || key] && oldFormValue[formItem.key || key].name) || '--'"
138
156
  :tooltip="true"
139
157
  />
140
- <span v-else>{{ (formValue[formItem.key || key] && formValue[formItem.key || key].name) || '--' }}</span>
158
+ <span v-else>{{
159
+ (formValue[formItem.key || key] && formValue[formItem.key || key].name) || '--'
160
+ }}</span>
141
161
  </div>
142
162
  <div v-else-if="formItem.label === '结售汇性质'">
143
163
  <cl-diff
@@ -241,8 +261,3 @@ export default {
241
261
  }
242
262
  }
243
263
  </script>
244
- <style scoped>
245
- .CustomFormView {
246
- font-size: 14px;
247
- }
248
- </style>
@@ -103,8 +103,3 @@ export default {
103
103
  }
104
104
  }
105
105
  </script>
106
- <style scoped>
107
- .contentPop {
108
- font-size: 14px;
109
- }
110
- </style>
@@ -136,8 +136,3 @@ export default {
136
136
  }
137
137
  }
138
138
  </script>
139
- <style scoped>
140
- .contentPop {
141
- font-size: 14px;
142
- }
143
- </style>
@@ -25,6 +25,7 @@
25
25
  </div>
26
26
  </div>
27
27
  <el-table
28
+ ref="elTable"
28
29
  :key="tableKey"
29
30
  :data="tableData"
30
31
  :row-key="keys.rowKey"
@@ -35,6 +36,11 @@
35
36
  <slot name="selection-column">
36
37
  <el-table-column type="selection" width="50" align="center" />
37
38
  </slot>
39
+ <el-table-column :label="'排序' | $lc" width="50" align="center">
40
+ <template slot-scope="{ row }">
41
+ <i class="n20-icon-tuodong file-upload-table__drag-handle" :data-row-key="row[keys.rowKey]"></i>
42
+ </template>
43
+ </el-table-column>
38
44
  <template v-if="dataPorp.slotHeader">
39
45
  <el-table-column
40
46
  v-for="item in dataPorp.slotHeader"
@@ -316,6 +322,7 @@ import aiCheckDialog from './aiCheckDialog.vue'
316
322
  import getJsonc from '../../assets/getJsonc.js'
317
323
  import _axios from 'axios'
318
324
  import dayjs from 'dayjs'
325
+ import Sortable from 'sortablejs'
319
326
  import XEUtils from 'xe-utils'
320
327
 
321
328
  import 'viewerjs/dist/viewer.css'
@@ -495,7 +502,8 @@ export default {
495
502
  previewName: undefined,
496
503
  previewSameOrg: false,
497
504
  seeRow: {},
498
- officeStatus: false
505
+ officeStatus: false,
506
+ sortable: null
499
507
  }
500
508
  },
501
509
  computed: {
@@ -538,12 +546,81 @@ export default {
538
546
  },
539
547
  immediate: true,
540
548
  deep: true
549
+ },
550
+ tableKey() {
551
+ this.initSortable()
541
552
  }
542
553
  },
543
554
  mounted() {
544
555
  this.getConfiguration()
556
+ this.initSortable()
557
+ },
558
+ beforeDestroy() {
559
+ this.destroySortable()
545
560
  },
546
561
  methods: {
562
+ initSortable() {
563
+ this.$nextTick(() => {
564
+ this.destroySortable()
565
+
566
+ const $tbody = this.$refs.elTable?.$el?.querySelector('.el-table__body-wrapper > table > tbody')
567
+ if (!$tbody) {
568
+ return
569
+ }
570
+
571
+ this.sortable = Sortable.create($tbody, {
572
+ handle: '.file-upload-table__drag-handle',
573
+ ghostClass: 'file-upload-table__sortable-ghost',
574
+ setData(dataTransfer) {
575
+ dataTransfer.setData('Text', '')
576
+ },
577
+ onEnd: ({ oldIndex, newIndex }) => {
578
+ if (
579
+ oldIndex === newIndex ||
580
+ oldIndex === undefined ||
581
+ newIndex === undefined ||
582
+ oldIndex < 0 ||
583
+ newIndex < 0
584
+ ) {
585
+ return
586
+ }
587
+
588
+ const targetRow = this.tableData.splice(oldIndex, 1)[0]
589
+ if (!targetRow) {
590
+ return
591
+ }
592
+ this.tableData.splice(newIndex, 0, targetRow)
593
+ this.$emit('sort', this.tableData.slice())
594
+ this.saveFileSort().catch(() => {
595
+ this.$message.error($lc('附件排序保存失败'))
596
+ })
597
+ }
598
+ })
599
+ })
600
+ },
601
+ destroySortable() {
602
+ if (this.sortable) {
603
+ this.sortable.destroy()
604
+ this.sortable = null
605
+ }
606
+ },
607
+ async saveFileSort() {
608
+ const sortList = this.tableData
609
+ .map((row, index) => ({
610
+ beid: row.beid || row[this.keys.rowKey],
611
+ fileSort: String(index + 1)
612
+ }))
613
+ .filter((item) => item.beid)
614
+
615
+ if (!sortList.length) {
616
+ return
617
+ }
618
+
619
+ await axios.post(
620
+ this.apiPrefix ? `${this.apiPrefix}/neams/eamsbaserecord/fileSort` : '/neams/eamsbaserecord/fileSort',
621
+ sortList
622
+ )
623
+ },
547
624
  // ai校验取消
548
625
  aiCheckCancel(beid) {
549
626
  this.tableData.forEach((item) => {
@@ -999,4 +1076,21 @@ export default {
999
1076
  right: 46px;
1000
1077
  top: 10px;
1001
1078
  }
1079
+
1080
+ .file-upload-table__drag-handle {
1081
+ display: inline-block;
1082
+ color: #909399;
1083
+ font-size: 16px;
1084
+ cursor: move;
1085
+ }
1086
+
1087
+ .file-upload-table__drag-handle:hover {
1088
+ color: #606266;
1089
+ }
1090
+
1091
+ .file-upload-table__sortable-ghost {
1092
+ opacity: 0.8;
1093
+ color: #fff !important;
1094
+ background: #42b983 !important;
1095
+ }
1002
1096
  </style>
@@ -0,0 +1,249 @@
1
+ <template>
2
+ <div class="info-card" :style="{ backgroundColor: backgroundColor }">
3
+ <!-- 右下角装饰logo -->
4
+ <img v-if="cornerLogo" v-show="isExpanded" :src="cornerLogo" class="corner-logo" alt="corner logo" />
5
+
6
+ <!-- 顶部标题区域 -->
7
+ <div class="info-card-header">
8
+ <div class="header-left">
9
+ <img v-if="logo" :src="logo" class="header-logo" alt="logo" />
10
+ <i v-if="icon" :class="icon"></i>
11
+ <span class="header-title">{{ title }}</span>
12
+ </div>
13
+
14
+ <!-- 右上角展开/收起按钮 -->
15
+ <div v-if="collapsible" class="header-right" @click="toggleCollapse">
16
+ <span class="collapse-text">{{ isExpanded ? $lc('收起') : $lc('展开') }}</span>
17
+ <i class="collapse-arrow" :class="{ expanded: isExpanded }"></i>
18
+ </div>
19
+ </div>
20
+
21
+ <!-- 表单内容插槽 -->
22
+ <transition name="collapse">
23
+ <div v-show="isExpanded || !collapsible" class="info-card-content">
24
+ <slot></slot>
25
+ </div>
26
+ </transition>
27
+ </div>
28
+ </template>
29
+
30
+ <script>
31
+ export default {
32
+ name: 'InfoCard',
33
+ props: {
34
+ // icon
35
+ icon: {
36
+ type: String,
37
+ default: ''
38
+ },
39
+ // 顶部图标
40
+ logo: {
41
+ type: String,
42
+ default: ''
43
+ },
44
+ // 标题文本
45
+ title: {
46
+ type: String,
47
+ default: '',
48
+ required: true
49
+ },
50
+ // 背景色
51
+ backgroundColor: {
52
+ type: String,
53
+ default: '#e8f7ff'
54
+ },
55
+ // 右下角装饰logo
56
+ cornerLogo: {
57
+ type: String,
58
+ default: ''
59
+ },
60
+ // 是否可折叠
61
+ collapsible: {
62
+ type: Boolean,
63
+ default: false
64
+ },
65
+ // 默认是否展开
66
+ defaultExpanded: {
67
+ type: Boolean,
68
+ default: true
69
+ }
70
+ },
71
+ data() {
72
+ return {
73
+ isExpanded: this.defaultExpanded
74
+ }
75
+ },
76
+ methods: {
77
+ toggleCollapse() {
78
+ this.isExpanded = !this.isExpanded
79
+ this.$emit('collapse-change', this.isExpanded)
80
+ }
81
+ }
82
+ }
83
+ </script>
84
+
85
+ <style lang="scss" scoped>
86
+ .info-card + .info-card {
87
+ margin-top: 8px;
88
+ }
89
+ .info-card {
90
+ display: flex;
91
+ flex-direction: column;
92
+ align-items: flex-start;
93
+ position: relative;
94
+ width: 100%;
95
+ height: 100%;
96
+ padding: 12px 16px;
97
+ border-radius: 4px;
98
+ box-sizing: border-box;
99
+
100
+ // 右下角装饰logo - 相对于整个卡片定位
101
+ .corner-logo {
102
+ position: absolute;
103
+ bottom: 0;
104
+ right: -16px;
105
+ width: 120px;
106
+ height: 120px;
107
+ pointer-events: none;
108
+ z-index: 0;
109
+ }
110
+
111
+ .info-card-header {
112
+ display: flex;
113
+ flex-direction: column;
114
+ gap: 12px;
115
+ align-items: flex-start;
116
+ position: relative;
117
+ width: 100%;
118
+ flex-shrink: 0;
119
+ z-index: 1;
120
+
121
+ .header-left {
122
+ display: flex;
123
+ align-items: center;
124
+ gap: 4px;
125
+ position: relative;
126
+ flex-shrink: 0;
127
+
128
+ .header-logo {
129
+ width: 16px;
130
+ height: 16px;
131
+ flex-shrink: 0;
132
+ position: relative;
133
+ }
134
+
135
+ .header-title {
136
+ font-family: 'PingFang SC', 'Medium', sans-serif;
137
+ font-size: 14px;
138
+ font-weight: 500;
139
+ line-height: 22px;
140
+ color: #1d2129;
141
+ white-space: nowrap;
142
+ position: relative;
143
+ flex-shrink: 0;
144
+ }
145
+ }
146
+
147
+ .header-right {
148
+ position: absolute;
149
+ top: 0;
150
+ right: 0;
151
+ display: flex;
152
+ align-items: center;
153
+ gap: 4px;
154
+ padding: 4px;
155
+ cursor: pointer;
156
+ border-radius: 2px;
157
+ overflow: hidden;
158
+ transition: background-color 0.2s;
159
+
160
+ &:hover {
161
+ background-color: rgba(0, 122, 255, 0.06);
162
+ }
163
+
164
+ .collapse-text {
165
+ font-family: 'PingFang SC', 'Regular', sans-serif;
166
+ font-size: 14px;
167
+ font-weight: 400;
168
+ line-height: 22px;
169
+ color: #007aff;
170
+ white-space: nowrap;
171
+ }
172
+
173
+ .collapse-arrow {
174
+ width: 14px;
175
+ height: 14px;
176
+ position: relative;
177
+ flex-shrink: 0;
178
+ transition: transform 0.3s ease;
179
+
180
+ &::before,
181
+ &::after {
182
+ content: '';
183
+ position: absolute;
184
+ width: 8px;
185
+ height: 2px;
186
+ background-color: #007aff;
187
+ border-radius: 1px;
188
+ top: 50%;
189
+ left: 50%;
190
+ transition: all 0.3s ease;
191
+ }
192
+
193
+ &::before {
194
+ transform: translate(-50%, -50%) rotate(45deg);
195
+ margin-left: -2px;
196
+ }
197
+
198
+ &::after {
199
+ transform: translate(-50%, -50%) rotate(-45deg);
200
+ margin-left: 2px;
201
+ }
202
+
203
+ &.expanded {
204
+ &::before {
205
+ transform: translate(-50%, -50%) rotate(-45deg);
206
+ margin-left: -3px;
207
+ }
208
+
209
+ &::after {
210
+ transform: translate(-50%, -50%) rotate(45deg);
211
+ margin-left: 3px;
212
+ }
213
+ }
214
+ }
215
+ }
216
+ }
217
+
218
+ .info-card-content {
219
+ display: flex;
220
+ flex-wrap: wrap;
221
+ align-items: flex-start;
222
+ position: relative;
223
+ width: 100%;
224
+ flex: 1;
225
+ z-index: 1;
226
+ }
227
+
228
+ // 折叠动画(Vue 2.6 transition 类名:无 -from/-to 后缀)
229
+ .collapse-enter-active,
230
+ .collapse-leave-active {
231
+ transition: all 0.3s ease;
232
+ overflow: hidden;
233
+ }
234
+
235
+ .collapse-enter,
236
+ .collapse-leave-to {
237
+ max-height: 0;
238
+ opacity: 0;
239
+ transform: translateY(-10px);
240
+ }
241
+
242
+ .collapse-enter-to,
243
+ .collapse-leave {
244
+ max-height: 2000px;
245
+ opacity: 1;
246
+ transform: translateY(0);
247
+ }
248
+ }
249
+ </style>
@@ -555,7 +555,7 @@ export default {
555
555
  updatefield(field, row) {
556
556
  this.$set(row, 'advancedQueryType', field.advancedQueryType)
557
557
  if (field.advancedQueryType === 4) {
558
- field.options = field.options.map((item) => {
558
+ field.options = field?.options?.map((item) => {
559
559
  if (field.props?.labelKey) {
560
560
  item.label = item[field.props.labelKey]
561
561
  }
@@ -564,7 +564,7 @@ export default {
564
564
  }
565
565
  return item
566
566
  })
567
- this.$set(row, 'options', field.options)
567
+ this.$set(row, 'options', field?.options)
568
568
  }
569
569
  // slot 类型特殊处理
570
570
  if (field.type === 'slot') {
@@ -614,42 +614,16 @@ export default {
614
614
  // 获取字段选项
615
615
  getFieldOptions(field) {
616
616
  const fieldConfig = this.fieldOptions.find((f) => f.value === field)
617
- return fieldConfig ? fieldConfig.options : []
617
+ return fieldConfig ? fieldConfig?.options : []
618
618
  },
619
619
  // 更新操作符对应的值
620
620
  updateOperatorOptions(field, condition) {
621
+ // if (condition.valueType === 'slot') return
621
622
  this.$set(condition, 'valueType', field.type)
622
623
  this.$set(condition, 'value', '')
623
624
  if (field.type === 'dateRangePicker' || field.type === 'inputNumberRange') {
624
625
  this.$set(condition, 'value', {})
625
626
  }
626
-
627
- /* if (condition.operator === 'between') {
628
- const fieldType = this.getFieldType(condition.field)
629
- if (fieldType === 'number') {
630
- condition.value = { start: undefined, end: undefined }
631
- } else if (fieldType === 'date' || fieldType === 'daterange') {
632
- condition.value = []
633
- } else {
634
- condition.value = { start: '', end: '' }
635
- }
636
- } else {
637
- const fieldType = this.getFieldType(condition.field)
638
- if (fieldType === 'select') {
639
- condition.value = []
640
- } else {
641
- condition.value = ''
642
- }
643
- } */
644
- },
645
- // 提交表单
646
- submitForm() {
647
- /* const formData = {
648
- value: this.value
649
- }
650
- this.$emit('confirm', formData)
651
- this.$emit('close') */
652
- console.log(this.value, 'this.value')
653
627
  }
654
628
  }
655
629
  }