vue2-client 1.20.69 → 1.20.71

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": "vue2-client",
3
- "version": "1.20.69",
3
+ "version": "1.20.71",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint",
@@ -31,7 +31,7 @@
31
31
  "@microsoft/fetch-event-source": "^2.0.1",
32
32
  "@vue/babel-preset-jsx": "^1.4.0",
33
33
  "animate.css": "^4.1.1",
34
- "ant-design-vue": "^1.7.8",
34
+ "ant-design-vue": "npm:@afwenming123/ant-design-vue@1.7.10",
35
35
  "axios": "^0.27.2",
36
36
  "clipboard": "^2.0.11",
37
37
  "core-js": "^3.33.0",
@@ -101,7 +101,6 @@ export default {
101
101
  this.$refs.xFormTable.refreshTable(true)
102
102
  },
103
103
  selectRow(selectedRowKeys, selectedRows) {
104
- console.log('selectRow', selectedRowKeys, selectedRows)
105
104
  this.selected = {
106
105
  keys: selectedRowKeys,
107
106
  rows: selectedRows
@@ -318,32 +318,6 @@ import moment from 'moment/moment'
318
318
  import XTableWrapper from './XTableWrapper.vue'
319
319
  import { isDebugUser } from '@vue2-client/utils/common'
320
320
 
321
- /**
322
- * 为表格列补齐唯一 key(只写 col.key,不改 dataIndex),避免 Ant Design Table 表头 ColGroup 因 key/dataIndex 重复触发 Vue duplicate key。
323
- * 冲突时在同一基准名上递增 _2、_3 …
324
- */
325
- function ensureUniqueColumnKeys (columns) {
326
- if (!Array.isArray(columns) || columns.length === 0) return
327
- const used = Object.create(null)
328
- columns.forEach((col, index) => {
329
- const base = String(
330
- col.key != null && col.key !== ''
331
- ? col.key
332
- : (col.dataIndex != null && col.dataIndex !== '')
333
- ? col.dataIndex
334
- : `col_${index}`
335
- )
336
- let unique = base
337
- let n = 2
338
- while (used[unique]) {
339
- unique = `${base}_${n}`
340
- n += 1
341
- }
342
- used[unique] = true
343
- col.key = unique
344
- })
345
- }
346
-
347
321
  export default {
348
322
  name: 'XTable',
349
323
  components: {
@@ -604,21 +578,6 @@ export default {
604
578
  pageSizeArray: {
605
579
  type: Array,
606
580
  default: () => ['10', '20', '30', '40']
607
- },
608
- /** 虚拟表预估行高(px),透传 STable;不传则用 STable 内按 tableSize 推导 */
609
- virtualItemSize: {
610
- type: Number,
611
- default: undefined
612
- },
613
- /** 虚拟表上下缓冲(像素),透传 STable;不传则用 STable 默认 */
614
- virtualBuffer: {
615
- type: Number,
616
- default: undefined
617
- },
618
- /** 虚拟表滚动是否 rAF 合并,透传 STable;不传则用 STable 默认 true */
619
- virtualUseRafScroll: {
620
- type: Boolean,
621
- default: undefined
622
581
  }
623
582
  },
624
583
  computed: {
@@ -970,7 +929,6 @@ export default {
970
929
  this.queryParams = queryParams
971
930
  this.realQueryParams = realQueryParams
972
931
  this.tableColumns = JSON.parse(JSON.stringify(tableColumns))
973
- ensureUniqueColumnKeys(this.tableColumns)
974
932
  if (this.tableColumns.length === 0) {
975
933
  return
976
934
  }
@@ -23,9 +23,6 @@
23
23
  :size="tableContext.tableSize"
24
24
  :components="components"
25
25
  :rowStyleFunction="tableContext.rowStyleFunction"
26
- :virtual-item-size="tableContext.virtualItemSize"
27
- :virtual-buffer="tableContext.virtualBuffer"
28
- :virtual-use-raf-scroll="tableContext.virtualUseRafScroll"
29
26
  @beforeDataChange="beforeDataChange"
30
27
  @expand="onExpand"
31
28
  @rowClick="handleRowClick"
@@ -188,9 +185,6 @@
188
185
  :size="tableContext.tableSize"
189
186
  :components="components"
190
187
  :rowStyleFunction="tableContext.rowStyleFunction"
191
- :virtual-item-size="tableContext.virtualItemSize"
192
- :virtual-buffer="tableContext.virtualBuffer"
193
- :virtual-use-raf-scroll="tableContext.virtualUseRafScroll"
194
188
  @rowClick="handleRowClick"
195
189
  @rowDblClick="handleRowDblClick"
196
190
  @beforeDataChange="beforeDataChange"
@@ -255,8 +249,8 @@
255
249
  <!-- websocket-id (配置名称-字段名称-数据行id) -->
256
250
  <x-web-socket-progress
257
251
  v-if="item.webSocket"
258
- :key="`${tableContext.requestParameters?.pageNo || 1}-${item.key || item.dataIndex}-${record[tableContext.rowKey] || index}`"
259
- :websocket-id="`${queryParamsName}-${item.key || item.dataIndex}-${record[tableContext.rowKey] || index}`"
252
+ :key="`${tableContext.requestParameters?.pageNo || 1}-${item.dataIndex}-${record[tableContext.rowKey] || index}`"
253
+ :websocket-id="`${queryParamsName}-${item.dataIndex}-${record[tableContext.rowKey] || index}`"
260
254
  :initial-value="text || 0"
261
255
  @progress-updated="(data) => handleProgressUpdated(data, record, item.dataIndex)"
262
256
  />
@@ -417,9 +411,10 @@ export default {
417
411
  cell: (h, props, children) => {
418
412
  const { key, ...restProps } = props
419
413
  // 此处的this.realTableColumns 是定义的table的表头属性变量
420
- // key 在去重后唯一;优先按 col.key 匹配(含 s_f_address_2 等),再回退 dataIndex
421
- const col = this.realTableColumns.find((c) => c.key === key) ||
422
- this.realTableColumns.find((c) => (c.dataIndex || c.key) === key)
414
+ const col = this.realTableColumns.find((col) => {
415
+ const k = col.dataIndex || col.key
416
+ return k === key
417
+ })
423
418
  if (!col || !col.width || col.slotType === 'action') {
424
419
  if (children) {
425
420
  return h('th', { ...restProps }, [...children])
@@ -448,7 +443,7 @@ export default {
448
443
  }
449
444
 
450
445
  const dragProps = {
451
- key: col.key || col.dataIndex,
446
+ key: col.dataIndex || col.key,
452
447
  class: 'table-draggable-handle',
453
448
  attrs: {
454
449
  w: 10,
@@ -22,7 +22,7 @@
22
22
  */
23
23
 
24
24
  import { shortcutManager } from '@vue2-client/base-client/utils/shortcutManager'
25
- import { executeStrFunctionByContext } from '@vue2-client/utils/runEvalFunction'
25
+ // import { executeStrFunctionByContext } from '@vue2-client/utils/runEvalFunction'
26
26
 
27
27
  const log = (env => {
28
28
  if (env !== 'development') return () => {}
@@ -356,23 +356,26 @@ export const shortcutMixin = {
356
356
  log('reg', `✅ 注册 ${key} → scope=${scopeId} cellRef=${cellConfig.slotRef || '-'}`)
357
357
  },
358
358
 
359
+ // createShortcutContext(cellConfig, eventConfig, eventIndex, shortcutKey) {
360
+ // return {
361
+ // ...this,
362
+ // getComponent: (ref) => this.shortcutGetComponent(ref),
363
+ // findComponent: (ref) => this.shortcutFindComponent(ref),
364
+ // cellConfig,
365
+ // eventConfig,
366
+ // eventIndex,
367
+ // slotRef: cellConfig.slotRef,
368
+ // shortcutKey, // ✅ 新增:触发的快捷键
369
+ // triggerEvent: (type, ...args) => {
370
+ // const event = cellConfig.events?.find(e => e.type === type)
371
+ // if (event?.customFunction) {
372
+ // executeStrFunctionByContext(this, event.customFunction, args)
373
+ // }
374
+ // }
375
+ // }
376
+ // },
359
377
  createShortcutContext(cellConfig, eventConfig, eventIndex, shortcutKey) {
360
- return {
361
- ...this,
362
- getComponent: (ref) => this.shortcutGetComponent(ref),
363
- findComponent: (ref) => this.shortcutFindComponent(ref),
364
- cellConfig,
365
- eventConfig,
366
- eventIndex,
367
- slotRef: cellConfig.slotRef,
368
- shortcutKey, // ✅ 新增:触发的快捷键
369
- triggerEvent: (type, ...args) => {
370
- const event = cellConfig.events?.find(e => e.type === type)
371
- if (event?.customFunction) {
372
- executeStrFunctionByContext(this, event.customFunction, args)
373
- }
374
- }
375
- }
378
+ return this
376
379
  },
377
380
 
378
381
  // ========== 组件查找 ==========
@@ -757,7 +757,7 @@ class ShortcutManager {
757
757
  if (!normalizedKey) return
758
758
 
759
759
  const entry = {
760
- action, scope, context, cellRef, isActive, instanceId,
760
+ action, scope, context, cellRef, isActive, instanceId, key,
761
761
  registeredAt: Date.now()
762
762
  }
763
763
 
@@ -918,16 +918,11 @@ class ShortcutManager {
918
918
  * @param {KeyboardEvent} keyboardEvent 原始键盘事件
919
919
  */
920
920
  executeEvent(entry, keyboardEvent) {
921
- const { action, context } = entry
921
+ const { action, context, key } = entry
922
922
  if (!action?.customFunction) return
923
923
 
924
924
  try {
925
- // 传递 keyboardEvent 和 shortcutKey
926
- executeStrFunctionByContext(
927
- context,
928
- action.customFunction,
929
- [keyboardEvent, context.shortcutKey || entry.key]
930
- )
925
+ executeStrFunctionByContext(context, action.customFunction, [keyboardEvent, key])
931
926
  } catch (err) {
932
927
  log('error', `❌ 执行失败: ${err.message}`)
933
928
  }
@@ -2,28 +2,8 @@ import T from 'ant-design-vue/es/table/Table'
2
2
  import get from 'lodash.get'
3
3
  import { mapState } from 'vuex'
4
4
  import { executeStrFunctionByContext } from '@vue2-client/utils/runEvalFunction'
5
- import AVirtualTable from '@vue2-client/components/AVirtualTable'
6
-
7
- /** 仅这些键走 AVirtualTable 的声明式 props;其余(scroll、rowKey、rowSelection 等)必须走 attrs 才能进入 $attrs 并透传到内部 a-table */
8
- const VTABLE_OWN_PROP_KEYS = new Set([
9
- 'columns',
10
- 'dataSource',
11
- 'keyProp',
12
- 'itemSize',
13
- 'scrollBox',
14
- 'buffer',
15
- 'throttleTime',
16
- 'dynamic',
17
- 'virtualized',
18
- 'autoVirtualizeThreshold',
19
- 'isTree',
20
- 'useRafScroll'
21
- ])
22
5
 
23
6
  export default {
24
- components: {
25
- AVirtualTable
26
- },
27
7
  data() {
28
8
  return {
29
9
  clickedRowKey: null,
@@ -142,21 +122,6 @@ export default {
142
122
  pageSizeArray: {
143
123
  type: Array,
144
124
  default: () => ['10', '20', '30', '40']
145
- },
146
- /** 虚拟表预估行高(px);不传则 small→48,其余→54(与 Ant Design 行高接近,减轻 offsetMap 累计误差) */
147
- virtualItemSize: {
148
- type: Number,
149
- default: null
150
- },
151
- /** 虚拟表上下缓冲(像素),增大可减少快速滚动露白,略增 DOM */
152
- virtualBuffer: {
153
- type: Number,
154
- default: 500
155
- },
156
- /** 虚拟表滚动用 rAF 合并更新(默认 true) */
157
- virtualUseRafScroll: {
158
- type: Boolean,
159
- default: true
160
125
  }
161
126
  }),
162
127
  computed: {
@@ -657,6 +622,13 @@ export default {
657
622
  })
658
623
  // 取消原有的分页组件 产品设计分页组件和汇总在一行 重新分页逻辑
659
624
  props.pagination = false
625
+ const tableData = Array.isArray(props.dataSource) ? props.dataSource : []
626
+ if (props.scroll && props.scroll.y && tableData.length > 40) {
627
+ props.scroll = {
628
+ ...props.scroll,
629
+ virtual: { itemHeight: 60, overscan: 8 }
630
+ }
631
+ }
660
632
 
661
633
  // 使用 rowClassName 实现响应式行样式
662
634
  props.rowClassName = (record, index) => {
@@ -853,35 +825,16 @@ export default {
853
825
  </a-col>
854
826
  </a-row>
855
827
  )
856
- const tableSize = this.size || 'default'
857
- const resolvedVirtualItemSize =
858
- this.virtualItemSize != null ? this.virtualItemSize : tableSize === 'small' ? 48 : 55
859
- const vtableOwnProps = {
860
- keyProp: '__v_key',
861
- autoVirtualizeThreshold: 40,
862
- itemSize: resolvedVirtualItemSize,
863
- buffer: this.virtualBuffer,
864
- useRafScroll: this.virtualUseRafScroll
865
- }
866
- const vtableAttrs = {}
867
- Object.keys(props).forEach(k => {
868
- if (VTABLE_OWN_PROP_KEYS.has(k)) {
869
- vtableOwnProps[k] = props[k]
870
- } else {
871
- vtableAttrs[k] = props[k]
872
- }
873
- })
874
828
  const table = (
875
- <AVirtualTable
876
- ref="vtable"
877
- {...{ props: vtableOwnProps, attrs: vtableAttrs, scopedSlots: { ...this.$scopedSlots } }}
829
+ <a-table
830
+ {...{ props, scopedSlots: { ...this.$scopedSlots } }}
878
831
  onChange={this.loadData}
879
832
  onExpand={(expanded, record) => this.onExpand(expanded, record)}
880
833
  >
881
834
  {Object.keys(this.$slots).map(name => (
882
835
  <template slot={name}>{this.$slots[name]}</template>
883
836
  ))}
884
- </AVirtualTable>
837
+ </a-table>
885
838
  )
886
839
  return (
887
840
  <div class="table-wrapper">
@@ -16,7 +16,6 @@ import Trend from '@vue2-client/components/Trend'
16
16
  import Ellipsis from '@vue2-client/components/Ellipsis'
17
17
  import NumberInfo from '@vue2-client/components/NumberInfo'
18
18
  import STable from '@vue2-client/components/STable'
19
- import AVirtualTable from '@vue2-client/components/AVirtualTable'
20
19
  import XScrollBox from '@vue2-client/components/xScrollBox'
21
20
 
22
21
  export {
@@ -34,7 +33,6 @@ export {
34
33
  Ellipsis,
35
34
  NumberInfo,
36
35
  STable,
37
- AVirtualTable,
38
36
  Trend,
39
37
  XScrollBox
40
38
  }
@@ -59,7 +59,6 @@ routerResource.example = {
59
59
  // component: () => import('@vue2-client/base-client/components/his/HChart/demo.vue'),
60
60
  // component: () => import('@vue2-client/pages/WorkflowDetail/WorkFlowDemo.vue'),
61
61
  component: () => import('@vue2-client/base-client/components/common/XFormTable/demo.vue')
62
- // component: () => import('@vue2-client/components/AVirtualTable/demo.vue')
63
62
  // component: () => import('@vue2-client/base-client/components/common/AfMap/demo.vue')
64
63
  // component: () => import('@vue2-client/base-client/components/common/ImagePreviewModal/demo.vue'),
65
64
  // component: () => import('@vue2-client/base-client/components/common/XImagePreview/demo.vue'),
package/vue.config.js CHANGED
@@ -45,7 +45,8 @@ module.exports = {
45
45
  changeOrigin: true
46
46
  },
47
47
  '/api/af-revenue': {
48
- target: v3Server,
48
+ pathRewrite: { '^/api/af-revenue/': '/' },
49
+ target: 'http://127.0.0.1:9026',
49
50
  changeOrigin: true
50
51
  },
51
52
  '/revenue-web/api/af-revenue': {
@@ -1,658 +0,0 @@
1
- <script>
2
- import throttle from 'lodash/throttle'
3
- import Checkbox from 'ant-design-vue/lib/checkbox'
4
- import Table from 'ant-design-vue/lib/table'
5
-
6
- function isScroller (el) {
7
- const style = window.getComputedStyle(el, null)
8
- const scrollValues = ['auto', 'scroll']
9
- return scrollValues.includes(style.overflow) || scrollValues.includes(style['overflow-y'])
10
- }
11
-
12
- function getParentScroller (el) {
13
- let parent = el
14
- while (parent) {
15
- if ([window, document, document.documentElement].includes(parent)) {
16
- return window
17
- }
18
- if (isScroller(parent)) {
19
- return parent
20
- }
21
- parent = parent.parentNode
22
- }
23
-
24
- return parent || window
25
- }
26
-
27
- function getScrollTop (el) {
28
- return el === window ? window.pageYOffset : el.scrollTop
29
- }
30
-
31
- function getScrollLeft (el) {
32
- return el === window ? window.pageXOffset : el.scrollLeft
33
- }
34
-
35
- function setScrollTop (el, pos) {
36
- return el === window ? window.scroll(window.pageXOffset, pos) : (el.scrollTop = pos)
37
- }
38
-
39
- function setScrollLeft (el, pos) {
40
- return el === window ? window.scroll(pos, window.pageYOffset) : (el.scrollLeft = pos)
41
- }
42
-
43
- function getOffsetHeight (el) {
44
- return el === window ? window.innerHeight : el.offsetHeight
45
- }
46
-
47
- function scrollToY (el, y) {
48
- if (el === window) {
49
- window.scroll(0, y)
50
- } else {
51
- el.scrollTop = y
52
- }
53
- }
54
-
55
- function hasScrollX (el) {
56
- return el.scrollWidth > el.clientWidth
57
- }
58
-
59
- const TableBodyClassNames = ['.ant-table-scroll .ant-table-body', '.ant-table-fixed-left .ant-table-body-inner', '.ant-table-fixed-right .ant-table-body-inner']
60
-
61
- let checkOrder = 0
62
-
63
- export default {
64
- inheritAttrs: false,
65
- name: 'AVirtualTable',
66
- components: {
67
- ACheckbox: Checkbox,
68
- ATable: Table
69
- },
70
- props: {
71
- dataSource: {
72
- type: Array,
73
- default: () => []
74
- },
75
- columns: {
76
- type: Array,
77
- default: () => []
78
- },
79
- keyProp: {
80
- type: String,
81
- default: 'id'
82
- },
83
- itemSize: {
84
- type: Number,
85
- default: 60
86
- },
87
- scrollBox: {
88
- type: String,
89
- default: undefined
90
- },
91
- buffer: {
92
- type: Number,
93
- default: 100
94
- },
95
- /** 为 false 时用 lodash throttle(throttleTime);为 true 时用 rAF 合并滚动回调,跟手更好 */
96
- useRafScroll: {
97
- type: Boolean,
98
- default: true
99
- },
100
- throttleTime: {
101
- type: Number,
102
- default: 10
103
- },
104
- dynamic: {
105
- type: Boolean,
106
- default: true
107
- },
108
- virtualized: {
109
- type: Boolean,
110
- default: true
111
- },
112
- /** 数据行数大于该数值时启用虚拟滚动(默认 40 表示 41 行及以上) */
113
- autoVirtualizeThreshold: {
114
- type: Number,
115
- default: 40
116
- },
117
- isTree: {
118
- type: Boolean,
119
- default: false
120
- }
121
- },
122
- provide () {
123
- return {
124
- virtualTable: this
125
- }
126
- },
127
- data () {
128
- return {
129
- start: 0,
130
- end: undefined,
131
- sizes: {},
132
- renderData: [],
133
- isCheckedAll: false,
134
- isCheckedImn: false,
135
- isHideAppend: false
136
- }
137
- },
138
- computed: {
139
- /** virtualized 为 false 时关闭;否则数据行数「大于」阈值时启用(默认 40 即至少 41 行) */
140
- effectiveVirtualized () {
141
- return this.virtualized && this.dataSource.length > this.autoVirtualizeThreshold
142
- },
143
- tableColumns () {
144
- return this.columns.map(column => {
145
- if (column.type === 'selection') {
146
- return {
147
- title: () => {
148
- return (
149
- <a-checkbox
150
- checked={this.isCheckedAll}
151
- indeterminate={this.isCheckedImn}
152
- onchange={() => this.onCheckAllRows(!this.isCheckedAll)}>
153
- </a-checkbox>
154
- )
155
- },
156
- customRender: (text, row) => {
157
- return (
158
- <a-checkbox
159
- checked={row.$v_checked}
160
- onchange={() => this.onCheckRow(row, !row.$v_checked)}>
161
- </a-checkbox>
162
- )
163
- },
164
- width: 60,
165
- ...column
166
- }
167
- } else if (column.index) {
168
- return {
169
- customRender: (text, row, index) => {
170
- const curIndex = this.start + index
171
- return typeof column.index === 'function' ? column.index(curIndex) : curIndex + 1
172
- },
173
- ...column
174
- }
175
- }
176
- return column
177
- })
178
- },
179
- offsetMap () {
180
- if (!this.dynamic) return {}
181
-
182
- const { keyProp, itemSize, sizes, dataSource } = this
183
- const res = {}
184
- let total = 0
185
- for (let i = 0; i < dataSource.length; i++) {
186
- const key = dataSource[i][keyProp]
187
- res[key] = total
188
-
189
- const curSize = sizes[key]
190
- const size = typeof curSize === 'number' ? curSize : itemSize
191
- total += size
192
- }
193
- return res
194
- }
195
- },
196
- methods: {
197
- initData () {
198
- this.isInnerScroll = false
199
- this.scrollPos = [0, 0, 0, 0]
200
- this.isDeactivated = false
201
- this._scrollRafId = null
202
-
203
- this.scroller = this.getScroller()
204
- this.setToTop()
205
- this.recordTablePos()
206
-
207
- this.handleScroll()
208
- this.$nextTick(() => {
209
- this.handleScroll()
210
- })
211
- // 滚动:rAF 每帧最多更新一次,减少快速滚动时空白露底;resize 同步调度
212
- this._throttledFallbackScroll = throttle(this.handleScroll, this.throttleTime)
213
- if (this.useRafScroll) {
214
- this.onScroll = this.scheduleScrollUpdate
215
- this.onResizeOrScroll = this.scheduleScrollUpdate
216
- } else {
217
- this.onScroll = this._throttledFallbackScroll
218
- this.onResizeOrScroll = this._throttledFallbackScroll
219
- }
220
- this.scroller.addEventListener('scroll', this.onScroll, { passive: true })
221
- window.addEventListener('resize', this.onResizeOrScroll)
222
- },
223
-
224
- /** 将多次 scroll 合并到下一帧,贴近显示器刷新,减轻「滚过了行才贴上」的体感 */
225
- scheduleScrollUpdate () {
226
- if (this._scrollRafId != null) return
227
- this._scrollRafId = requestAnimationFrame(() => {
228
- this._scrollRafId = null
229
- this.handleScroll()
230
- })
231
- },
232
-
233
- cancelPendingScrollRaf () {
234
- if (this._scrollRafId == null) return
235
- cancelAnimationFrame(this._scrollRafId)
236
- this._scrollRafId = null
237
- },
238
-
239
- setToTop () {
240
- if (this.isInnerScroll) {
241
- this.toTop = 0
242
- } else {
243
- this.toTop = this.$el.getBoundingClientRect().top - (this.scroller === window ? 0 : this.scroller.getBoundingClientRect().top) + getScrollTop(this.scroller)
244
- }
245
- },
246
-
247
- getScroller () {
248
- let el
249
- if (this.scrollBox) {
250
- if (this.scrollBox === 'window' || this.scrollBox === window) return window
251
-
252
- el = document.querySelector(this.scrollBox)
253
- if (!el) throw new Error(` scrollBox prop: '${this.scrollBox}' is not a valid selector`)
254
- if (!isScroller(el)) {
255
- // eslint-disable-next-line no-console
256
- console.warn(`Warning! scrollBox prop: '${this.scrollBox}' is not a scroll element`)
257
- }
258
- return el
259
- }
260
- if (this.$attrs.scroll && this.$attrs.scroll.y) {
261
- this.isInnerScroll = true
262
- return this.$el.querySelector('.ant-table-body')
263
- } else {
264
- return getParentScroller(this.$el)
265
- }
266
- },
267
-
268
- handleScroll () {
269
- if (this.isDeactivated) return
270
- this.scrollPos[0] = getScrollTop(this.scroller)
271
- this.scrollPos[1] = getScrollLeft(this.scroller)
272
-
273
- if (!this.effectiveVirtualized) return
274
-
275
- this.updateSizes()
276
- this.calcRenderData()
277
- this.calcPosition()
278
- },
279
-
280
- updateSizes () {
281
- if (!this.dynamic) return
282
-
283
- let rows = []
284
- if (this.isTree) {
285
- rows = this.$el.querySelectorAll('.ant-table-body .ant-table-row-level-0')
286
- } else {
287
- rows = this.$el.querySelectorAll('.ant-table-body .ant-table-tbody .ant-table-row')
288
- }
289
-
290
- Array.from(rows).forEach((row, index) => {
291
- const item = this.renderData[index]
292
- if (!item) return
293
-
294
- let offsetHeight = row.offsetHeight
295
- const nextEl = row.nextSibling
296
- if (nextEl && nextEl.classList && nextEl.classList.contains('ant-table-expanded-row')) {
297
- offsetHeight += row.nextSibling.offsetHeight
298
- }
299
-
300
- if (this.isTree) {
301
- let next = row.nextSibling
302
- while (next && next !== rows[index + 1]) {
303
- offsetHeight += next?.offsetHeight || 0
304
- next = next.nextSibling
305
- }
306
- }
307
-
308
- const key = item[this.keyProp]
309
- if (this.sizes[key] !== offsetHeight) {
310
- this.$set(this.sizes, key, offsetHeight)
311
- row._offsetHeight = offsetHeight
312
- }
313
- })
314
- },
315
-
316
- calcRenderData () {
317
- const { scroller, buffer, dataSource: data } = this
318
- const top = this.scrollPos[0] - buffer - this.toTop
319
- // 兜底:scroll.y 可能是数字,也可能是 'calc(...)' / '300px' 等 CSS 字符串
320
- // 字符串场景下无法直接参与算术,需要退化为从真实 DOM 读取实际可视高度
321
- let scrollerHeight
322
- if (this.isInnerScroll) {
323
- const rawY = this.$attrs.scroll && this.$attrs.scroll.y
324
- if (typeof rawY === 'number' && !isNaN(rawY)) {
325
- scrollerHeight = rawY
326
- } else if (scroller && typeof scroller.clientHeight === 'number') {
327
- scrollerHeight = scroller.clientHeight
328
- } else {
329
- scrollerHeight = 0
330
- }
331
- } else {
332
- scrollerHeight = getOffsetHeight(scroller)
333
- }
334
- const bottom = this.scrollPos[0] + scrollerHeight + buffer - this.toTop
335
-
336
- let start
337
- let end
338
- if (!this.dynamic) {
339
- start = top <= 0 ? 0 : Math.floor(top / this.itemSize)
340
- end = bottom <= 0 ? 0 : Math.ceil(bottom / this.itemSize)
341
- } else {
342
- let l = 0
343
- let r = data.length - 1
344
- let mid = 0
345
- while (l <= r) {
346
- mid = Math.floor((l + r) / 2)
347
- const midVal = this.getItemOffsetTop(mid)
348
- if (midVal < top) {
349
- const midNextVal = this.getItemOffsetTop(mid + 1)
350
- if (midNextVal > top) break
351
- l = mid + 1
352
- } else {
353
- r = mid - 1
354
- }
355
- }
356
-
357
- start = mid
358
- end = data.length - 1
359
- for (let i = start + 1; i < data.length; i++) {
360
- const offsetTop = this.getItemOffsetTop(i)
361
- if (offsetTop >= bottom) {
362
- end = i
363
- break
364
- }
365
- }
366
- }
367
-
368
- if (start % 2) {
369
- start = start - 1
370
- }
371
- this.top = top
372
- this.bottom = bottom
373
- this.start = start
374
- this.end = end
375
- this.renderData = data.slice(start, end + 1)
376
- this.$emit('renderChange', this.renderData, this.start, this.end)
377
- },
378
-
379
- calcPosition () {
380
- const len = this.dataSource.length
381
- const last = len - 1
382
- const wrapHeight = this.getItemOffsetTop(last) + this.getItemSize(last)
383
- const offsetTop = this.getItemOffsetTop(this.start)
384
-
385
- TableBodyClassNames.forEach(className => {
386
- const el = this.$el.querySelector(className)
387
- if (!el) return
388
-
389
- if (!el.wrapEl) {
390
- const wrapEl = document.createElement('div')
391
- const innerEl = document.createElement('div')
392
- wrapEl.appendChild(innerEl)
393
- innerEl.appendChild(el.children[0])
394
- el.insertBefore(wrapEl, el.firstChild)
395
- el.wrapEl = wrapEl
396
- el.innerEl = innerEl
397
- }
398
-
399
- if (el.wrapEl) {
400
- el.wrapEl.style.display = len === 0 ? 'block' : 'inline-block'
401
- el.innerEl.style.display = len === 0 ? 'block' : 'inline-block'
402
-
403
- if (this.isInnerScroll && !hasScrollX(this.scroller)) {
404
- el.wrapEl.style.width = '100%'
405
- el.innerEl.style.width = '100%'
406
- }
407
-
408
- el.wrapEl.style.height = wrapHeight + 'px'
409
- el.innerEl.style.transform = `translateY(${offsetTop}px)`
410
- }
411
- })
412
- },
413
-
414
- getItemOffsetTop (index) {
415
- if (!this.dynamic) {
416
- return this.itemSize * index
417
- }
418
-
419
- const item = this.dataSource[index]
420
- if (item) {
421
- return this.offsetMap[item[this.keyProp]] || 0
422
- }
423
- return 0
424
- },
425
-
426
- getItemSize (index) {
427
- if (index <= -1) return 0
428
- const item = this.dataSource[index]
429
- if (item) {
430
- const key = item[this.keyProp]
431
- return this.sizes[key] || this.itemSize
432
- }
433
- return this.itemSize
434
- },
435
-
436
- update () {
437
- this.setToTop()
438
- this.handleScroll()
439
- },
440
-
441
- scrollTo (index, offsetY = 0, stop = false) {
442
- const item = this.dataSource[index]
443
- if (item && this.scroller) {
444
- this.updateSizes()
445
- this.calcRenderData()
446
-
447
- this.$nextTick(() => {
448
- const offsetTop = this.getItemOffsetTop(index) - offsetY
449
- scrollToY(this.scroller, offsetTop)
450
-
451
- if (!stop) {
452
- setTimeout(() => {
453
- this.scrollTo(index, offsetY, true)
454
- }, 50)
455
- }
456
- })
457
- }
458
- },
459
-
460
- renderAllData () {
461
- this.renderData = this.dataSource
462
- const len = this.dataSource.length
463
- const last = len > 0 ? len - 1 : 0
464
- this.$emit('renderChange', this.dataSource, 0, last)
465
-
466
- this.$nextTick(() => {
467
- TableBodyClassNames.forEach(className => {
468
- const el = this.$el.querySelector(className)
469
- if (!el) return
470
-
471
- if (el.wrapEl) {
472
- el.wrapEl.style.height = 'auto'
473
- el.innerEl.style.transform = `translateY(${0}px)`
474
- }
475
- })
476
- })
477
- },
478
-
479
- doUpdate () {
480
- if (this.hasDoUpdate) return
481
- if (!this.scroller) return
482
-
483
- this.isHideAppend = true
484
- this.update()
485
- this.hasDoUpdate = true
486
- this.$nextTick(() => {
487
- this.hasDoUpdate = false
488
- this.isHideAppend = false
489
- })
490
- },
491
-
492
- onCheckAllRows (val) {
493
- val = this.isCheckedImn ? true : val
494
- this.dataSource.forEach(row => {
495
- if (row.$v_checked === val) return
496
-
497
- this.$set(row, '$v_checked', val)
498
- this.$set(row, '$v_checkedOrder', val ? checkOrder++ : undefined)
499
- })
500
- this.isCheckedAll = val
501
- this.isCheckedImn = false
502
- this.emitSelectionChange()
503
- if (val === false) checkOrder = 0
504
- },
505
-
506
- onCheckRow (row, val) {
507
- if (row.$v_checked === val) return
508
-
509
- this.$set(row, '$v_checked', val)
510
- this.$set(row, '$v_checkedOrder', val ? checkOrder++ : undefined)
511
-
512
- const checkedLen = this.dataSource.filter(row => row.$v_checked === true).length
513
- if (checkedLen === 0) {
514
- this.isCheckedAll = false
515
- this.isCheckedImn = false
516
- } else if (checkedLen === this.dataSource.length) {
517
- this.isCheckedAll = true
518
- this.isCheckedImn = false
519
- } else {
520
- this.isCheckedAll = false
521
- this.isCheckedImn = true
522
- }
523
- this.emitSelectionChange()
524
- },
525
-
526
- emitSelectionChange () {
527
- const selection = this.dataSource.filter(row => row.$v_checked).sort((a, b) => a.$v_checkedOrder - b.$v_checkedOrder)
528
- this.$emit('selection-change', selection)
529
- },
530
-
531
- toggleRowSelection (row, selected) {
532
- const val = typeof selected === 'boolean' ? selected : !row.$v_checked
533
- this.onCheckRow(row, val)
534
- },
535
-
536
- clearSelection () {
537
- this.isCheckedImn = false
538
- this.onCheckAllRows(false)
539
- },
540
-
541
- recordTablePos () {
542
- if (!this.isInnerScroll) return
543
-
544
- this.tableBodyEl = this.$el.querySelector('.ant-table-body')
545
- this.onTableScroll = throttle(() => {
546
- this.scrollPos[2] = getScrollTop(this.tableBodyEl)
547
- this.scrollPos[3] = getScrollLeft(this.tableBodyEl)
548
- }, 100)
549
- this.tableBodyEl.addEventListener('scroll', this.onTableScroll, { passive: true })
550
- },
551
-
552
- restoreScrollY () {
553
- if (!this.scroller) return
554
-
555
- const [top, left, top2, left2] = this.scrollPos
556
- setScrollTop(this.scroller, top)
557
- setScrollLeft(this.scroller, left)
558
-
559
- if (this.isInnerScroll) {
560
- const leftScroller = document.querySelector(TableBodyClassNames[1])
561
- const rightScroller = document.querySelector(TableBodyClassNames[2])
562
- if (leftScroller) setScrollTop(leftScroller, top)
563
- if (rightScroller) setScrollTop(rightScroller, top)
564
- } else {
565
- setScrollTop(this.tableBodyEl, top2)
566
- setScrollLeft(this.tableBodyEl, left2)
567
- }
568
- },
569
-
570
- updateData (data = []) {
571
- this.list = data
572
- this.$emit('update:dataSource', this.list)
573
- },
574
-
575
- getData () {
576
- return this.list || this.dataSource
577
- }
578
- },
579
- watch: {
580
- dataSource (data, oldData) {
581
- if (!this.effectiveVirtualized) {
582
- this.renderAllData()
583
- } else {
584
- this.doUpdate()
585
- }
586
- if (this.list && data !== oldData) {
587
- this.list = data
588
- }
589
- },
590
- effectiveVirtualized: {
591
- immediate: true,
592
- handler (val) {
593
- if (!val) {
594
- this.renderAllData()
595
- } else {
596
- this.doUpdate()
597
- }
598
- }
599
- }
600
- },
601
- created () {
602
- this.$nextTick(() => {
603
- this.initData()
604
- })
605
- },
606
- mounted () {
607
- const appendEl = this.$refs.append
608
- const body = this.$el && this.$el.querySelector('.ant-table-body')
609
- if (body && appendEl) {
610
- body.appendChild(appendEl)
611
- }
612
- },
613
- beforeDestroy () {
614
- this.cancelPendingScrollRaf()
615
- if (this.scroller) {
616
- this.scroller.removeEventListener('scroll', this.onScroll)
617
- window.removeEventListener('resize', this.onResizeOrScroll || this.onScroll)
618
- }
619
- if (this.tableBodyEl) {
620
- this.tableBodyEl.removeEventListener('scroll', this.onTableScroll)
621
- }
622
- },
623
- activated () {
624
- this.isDeactivated = false
625
- this.restoreScrollY()
626
- },
627
- deactivated () {
628
- this.isDeactivated = true
629
- this.cancelPendingScrollRaf()
630
- },
631
- render (h) {
632
- const slotChildren = Object.keys(this.$slots).map(name =>
633
- h('template', { slot: name, key: `named-slot-${name}` }, this.$slots[name])
634
- )
635
- return h('div', [
636
- h(Table, {
637
- props: {
638
- ...this.$attrs,
639
- pagination: false,
640
- columns: this.tableColumns,
641
- dataSource: this.renderData
642
- },
643
- on: this.$listeners,
644
- scopedSlots: this.$scopedSlots,
645
- ref: 'innerTable'
646
- }, slotChildren.length ? slotChildren : undefined),
647
- h('div', {
648
- class: 'ant-table-append',
649
- ref: 'append',
650
- directives: [{ name: 'show', value: !this.isHideAppend }]
651
- }, this.$slots.append || [])
652
- ])
653
- }
654
- }
655
- </script>
656
-
657
- <style lang="less">
658
- </style>
@@ -1,140 +0,0 @@
1
- <template>
2
- <div class="a-virtual-table-basic-demo">
3
- <a-alert
4
- show-icon
5
- type="info"
6
- message="AVirtualTable 基础示例(对齐上游 BasicDemo)"
7
- style="margin-bottom: 16px"
8
- >
9
- <template slot="description">
10
- 共 {{ list.length }} 条数据;虚拟列表约渲染少量行。
11
- 插槽使用 <code>slot-scope=&quot;text, record, index&quot;</code>(与 ant-design-vue Table 一致)。
12
- <span v-if="renderInfo">当前切片:第 {{ renderInfo.start }}–{{ renderInfo.end }} 行,本帧 {{ renderInfo.count }} 行 DOM。</span>
13
- </template>
14
- </a-alert>
15
-
16
- <a-virtual-table
17
- :columns="columns"
18
- :data-source="list"
19
- :item-size="54"
20
- key-prop="id"
21
- row-key="id"
22
- :buffer="280"
23
- :scroll="{ x: 1300, y: 600 }"
24
- @render-change="onRenderChange"
25
- >
26
- <span slot="customTitle"><a-icon type="smile-o" /> Name (自定义头)</span>
27
- <template slot="name" slot-scope="text, record">
28
- {{ text }} — id: {{ record.id }}
29
- </template>
30
- </a-virtual-table>
31
- </div>
32
- </template>
33
-
34
- <script>
35
- import AVirtualTable from '@vue2-client/components/AVirtualTable'
36
-
37
- /** 生成与上游 demo 相近的大列表测试数据 */
38
- function mockRows (count) {
39
- const contents = [
40
- '这是一条测试数据',
41
- '君不见黄河之水天上来,奔流到海不复回。',
42
- '十年生死两茫茫',
43
- '寻寻觅觅,冷冷清清,凄凄惨惨戚戚。',
44
- '桃花坞里桃花庵,桃花庵里桃花仙;桃花仙人种桃树,又摘桃花卖酒钱。',
45
- '明月几时有,把酒问青天。',
46
- '槛菊愁烟兰泣露,罗幕轻寒,',
47
- '红豆生南国,春来发几枝。',
48
- '黄鹂'
49
- ]
50
- const rows = []
51
- for (let i = 0; i < count; i++) {
52
- rows.push({
53
- id: i,
54
- name: '王小虎',
55
- text: contents[i % contents.length],
56
- address: '上海市普陀区金沙江路 1518 弄'
57
- })
58
- }
59
- return rows
60
- }
61
-
62
- export default {
63
- name: 'AVirtualTableBasicDemo',
64
- components: {
65
- AVirtualTable
66
- },
67
- data () {
68
- return {
69
- renderInfo: null,
70
- columns: [
71
- {
72
- dataIndex: 'name',
73
- key: 'name',
74
- slots: { title: 'customTitle' },
75
- scopedSlots: { customRender: 'name' },
76
- width: 200
77
- },
78
- {
79
- title: 'id',
80
- dataIndex: 'id',
81
- key: 'id',
82
- width: 100
83
- },
84
- {
85
- title: 'text',
86
- dataIndex: 'text',
87
- key: 'text',
88
- width: 400
89
- },
90
- {
91
- title: 'Address',
92
- dataIndex: 'address',
93
- key: 'address 1',
94
- ellipsis: true,
95
- width: 400
96
- },
97
- {
98
- title: 'Long Column Long Column Long Column',
99
- dataIndex: 'address',
100
- key: 'address 2',
101
- ellipsis: true,
102
- width: 300
103
- },
104
- {
105
- title: 'Long Column Long Column',
106
- dataIndex: 'address',
107
- key: 'address 3',
108
- ellipsis: true,
109
- width: 300
110
- },
111
- {
112
- title: 'Long Column',
113
- dataIndex: 'address',
114
- key: 'address 4',
115
- ellipsis: true,
116
- width: 150,
117
- fixed: 'right'
118
- }
119
- ],
120
- list: mockRows(2000)
121
- }
122
- },
123
- methods: {
124
- onRenderChange (renderData, start, end) {
125
- this.renderInfo = {
126
- start,
127
- end,
128
- count: renderData ? renderData.length : 0
129
- }
130
- }
131
- }
132
- }
133
- </script>
134
-
135
- <style lang="less" scoped>
136
- .a-virtual-table-basic-demo {
137
- padding: 16px;
138
- max-width: 100%;
139
- }
140
- </style>
@@ -1,3 +0,0 @@
1
- import AVirtualTable from './a-virtual-table.vue'
2
-
3
- export default AVirtualTable