n20-common-lib 3.2.4 → 3.2.6

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,83 @@
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>
@@ -1540,8 +1540,4 @@ export default {
1540
1540
  }
1541
1541
  }
1542
1542
  }
1543
- </script>
1544
-
1545
- <style lang="scss">
1546
- @import '../../assets/css/pivot.scss';
1547
- </style>
1543
+ </script>
@@ -618,7 +618,7 @@ export default {
618
618
  },
619
619
  // 更新操作符对应的值
620
620
  updateOperatorOptions(field, condition) {
621
- if (condition.valueType === 'slot') return
621
+ // if (condition.valueType === 'slot') return
622
622
  this.$set(condition, 'valueType', field.type)
623
623
  this.$set(condition, 'value', '')
624
624
  if (field.type === 'dateRangePicker' || field.type === 'inputNumberRange') {
@@ -48,7 +48,9 @@ export default {
48
48
  data() {
49
49
  return {
50
50
  // 自适应模式下指示器的测量数据
51
- measuredIndicator: { width: 0, height: 0, translateX: 0 }
51
+ measuredIndicator: { width: 0, height: 0, translateX: 0 },
52
+ indicatorResizeObserver: null,
53
+ indicatorRaf: null
52
54
  }
53
55
  },
54
56
  computed: {
@@ -85,8 +87,8 @@ export default {
85
87
  // 自适应模式:基于 DOM 测量定位
86
88
  const { width, height, translateX } = this.measuredIndicator
87
89
  return {
88
- width: `${width - 2 * gap}px`,
89
- height: `${height - 2 * gap}px`,
90
+ width: `${Math.max(width - 2 * gap, 0)}px`,
91
+ height: `${Math.max(height - 2 * gap, 0)}px`,
90
92
  top: `${gap}px`,
91
93
  left: `${gap}px`,
92
94
  transform: `translateX(${translateX}px)`
@@ -95,17 +97,33 @@ export default {
95
97
  },
96
98
  watch: {
97
99
  value() {
98
- if (!this.isFixedWidth) this.$nextTick(this.updateIndicator)
100
+ if (!this.isFixedWidth) this.$nextTick(this.syncIndicatorObserver)
99
101
  },
100
102
  options: {
101
103
  deep: true,
102
104
  handler() {
103
- if (!this.isFixedWidth) this.$nextTick(this.updateIndicator)
105
+ if (!this.isFixedWidth) this.$nextTick(this.syncIndicatorObserver)
104
106
  }
107
+ },
108
+ itemWidth() {
109
+ this.$nextTick(this.syncIndicatorObserver)
110
+ },
111
+ itemHeight() {
112
+ if (!this.isFixedWidth) this.$nextTick(this.scheduleIndicatorUpdate)
105
113
  }
106
114
  },
107
115
  mounted() {
108
- if (!this.isFixedWidth) this.updateIndicator()
116
+ this.syncIndicatorObserver()
117
+ },
118
+ activated() {
119
+ if (!this.isFixedWidth) this.scheduleIndicatorUpdate()
120
+ },
121
+ beforeDestroy() {
122
+ this.destroyIndicatorObserver()
123
+ if (this.indicatorRaf) {
124
+ this.cancelIndicatorFrame(this.indicatorRaf)
125
+ this.indicatorRaf = null
126
+ }
109
127
  },
110
128
  methods: {
111
129
  // 统一尺寸格式化
@@ -113,6 +131,56 @@ export default {
113
131
  if (typeof val === 'number') return `${val}px`
114
132
  return isNaN(val) ? val : `${val}px`
115
133
  },
134
+ syncIndicatorObserver() {
135
+ this.destroyIndicatorObserver()
136
+ if (this.isFixedWidth) return
137
+ this.scheduleIndicatorUpdate()
138
+
139
+ if (typeof window === 'undefined' || typeof window.ResizeObserver === 'undefined') {
140
+ if (typeof window !== 'undefined') {
141
+ window.addEventListener('resize', this.scheduleIndicatorUpdate)
142
+ }
143
+ return
144
+ }
145
+
146
+ this.indicatorResizeObserver = new window.ResizeObserver(() => {
147
+ this.scheduleIndicatorUpdate()
148
+ })
149
+ const container = this.$refs.container
150
+ const items = this.$refs.items || []
151
+ const activeIndex = this.options.findIndex((item) => item.value === this.value)
152
+ if (container) this.indicatorResizeObserver.observe(container)
153
+ if (items[activeIndex]) this.indicatorResizeObserver.observe(items[activeIndex])
154
+ },
155
+ destroyIndicatorObserver() {
156
+ if (this.indicatorResizeObserver) {
157
+ this.indicatorResizeObserver.disconnect()
158
+ this.indicatorResizeObserver = null
159
+ }
160
+ if (typeof window !== 'undefined') {
161
+ window.removeEventListener('resize', this.scheduleIndicatorUpdate)
162
+ }
163
+ },
164
+ requestIndicatorFrame(callback) {
165
+ if (typeof window !== 'undefined' && typeof window.requestAnimationFrame === 'function') {
166
+ return window.requestAnimationFrame(callback)
167
+ }
168
+ return setTimeout(callback, 16)
169
+ },
170
+ cancelIndicatorFrame(frameId) {
171
+ if (typeof window !== 'undefined' && typeof window.cancelAnimationFrame === 'function') {
172
+ window.cancelAnimationFrame(frameId)
173
+ return
174
+ }
175
+ clearTimeout(frameId)
176
+ },
177
+ scheduleIndicatorUpdate() {
178
+ if (this.indicatorRaf) this.cancelIndicatorFrame(this.indicatorRaf)
179
+ this.indicatorRaf = this.requestIndicatorFrame(() => {
180
+ this.indicatorRaf = null
181
+ this.updateIndicator()
182
+ })
183
+ },
116
184
  // 自适应模式下测量激活项的位置
117
185
  updateIndicator() {
118
186
  const items = this.$refs.items
package/src/index.js CHANGED
@@ -137,6 +137,7 @@ import tableProV3 from './components/v3/TablePro/index.vue'
137
137
  import FileUploadTableV3 from './components/v3/UploadList/index.vue'
138
138
  import ViewToggle from './components/ViewToggle/index.vue'
139
139
  import WorkCard from './components/WorkCard/index.vue'
140
+ import InfoCard from './components/InfoCard/index.vue'
140
141
  import WornPagination from './components/WornPagination/index.vue'
141
142
  import VTableLoading from './directives/loading/index.js'
142
143
 
@@ -266,6 +267,7 @@ const components = [
266
267
  BusiDatePicker,
267
268
  TableTransfer,
268
269
  WorkCard,
270
+ InfoCard,
269
271
  UploadMsg,
270
272
  Tree,
271
273
  DateChoose,
@@ -436,6 +438,7 @@ export {
436
438
  Upload,
437
439
  UploadMsg,
438
440
  WorkCard,
441
+ InfoCard,
439
442
  WornPagination,
440
443
  /* v3组件*/
441
444
  tableProV3,