xt-element-ui 1.2.2 → 1.2.3

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": "xt-element-ui",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "基于 Vue2.7 + ElementUI 的组件库",
5
5
  "main": "lib/index.common.js",
6
6
  "module": "lib/index.esm.js",
@@ -34,7 +34,7 @@ export default {
34
34
  size: {
35
35
  type: String,
36
36
  default: null,
37
- validator: (val) => !val || ['small', 'medium', 'large'].includes(val)
37
+ validator: (val) => !val || ['mini', 'small', 'medium', 'large'].includes(val)
38
38
  }
39
39
  },
40
40
  data() {
@@ -42,20 +42,26 @@
42
42
  }
43
43
  }
44
44
 
45
+ /* small size - 字体大小 12px */
46
+ .ex-button-mini {
47
+ padding: 2px 4px;
48
+ font-size: calc(var(--xt-font-size-base) + $xt-font-size-increment-extra-small);
49
+ }
50
+
45
51
  /* small size - 字体大小 14px */
46
52
  .ex-button-small {
47
- padding: 4px 8px;
48
- font-size: calc(var(--xt-font-size-base) + $xt-font-size-increment-small);
53
+ padding: 5px 10px;
54
+ font-size: calc(var(--xt-font-size-base) + $xt-font-size-increment-base);
49
55
  }
50
56
 
51
57
  /* medium size - 字体大小 16px */
52
58
  .ex-button-medium {
53
- padding: 7px 14px;
59
+ padding: 8px 16px;
54
60
  font-size: calc(var(--xt-font-size-base) + $xt-font-size-increment-medium);
55
61
  }
56
62
 
57
63
  /* large size - 字体大小 18px */
58
64
  .ex-button-large {
59
- padding: 10px 20px;
65
+ padding: 11px 22px;
60
66
  font-size: calc(var(--xt-font-size-base) + $xt-font-size-increment-large);
61
67
  }
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  color: ["#0de7ff", "#a782ff", "#fcdd60", "#0de7ff", "#ffc5a1", "#6291ae", "#13ce66"],
3
- backgroundColor: "transparent",
3
+ backgroundColor: "#000",
4
4
  textStyle: {
5
5
  fontFamily: "Microsoft YaHei, sans-serif"
6
6
  },
@@ -65,7 +65,7 @@ EchartsUtil.currentTheme = "default";
65
65
  EchartsUtil.currentSize = "medium";
66
66
  EchartsUtil.inverse = false;
67
67
 
68
- EchartsUtil.baseOption = {
68
+ EchartsUtil.EchartsUtil = {
69
69
  backgroundColor: "transparent",
70
70
  tooltip: {
71
71
  trigger: "axis",
@@ -0,0 +1,2 @@
1
+ import ExPage from './index.vue'
2
+ export default ExPage
@@ -0,0 +1,2 @@
1
+ import ExSelectTree from './index.vue'
2
+ export default ExSelectTree
@@ -56,7 +56,7 @@
56
56
  import Emitter from "element-ui/lib/mixins/emitter";
57
57
  import { addResizeListener, removeResizeListener } from "element-ui/lib/utils/resize-event";
58
58
  export default {
59
- name: "XtSelectTree",
59
+ name: "ExSelectTree",
60
60
  mixins: [Emitter],
61
61
  inheritAttrs: false,
62
62
  model: {
@@ -14,9 +14,10 @@ export default {
14
14
  index: ctx.props.index
15
15
  }
16
16
  if (ctx.props.column) params.column = ctx.props.column
17
- const isVnode = (it) => typeof it === 'object' && it.hasOwnProperty('componentOptions')
17
+ const isVnode = (it) => it !== null && it !== undefined && typeof it === 'object' && it.hasOwnProperty('componentOptions')
18
18
  const renderNode = ctx.props.formatter(h, params)
19
- return isVnode(renderNode) ? renderNode : h('span', renderNode)
19
+ if (renderNode === null || renderNode === undefined || renderNode === '') return h('span', '')
20
+ return isVnode(renderNode) ? renderNode : h('span', String(renderNode))
20
21
  }
21
22
  }
22
23
  </script>
@@ -0,0 +1,234 @@
1
+ <template>
2
+ <el-table
3
+ ref="innerTable"
4
+ v-bind="$attrs"
5
+ v-on="$listeners"
6
+ :data="renderList"
7
+ >
8
+ <slot />
9
+ </el-table>
10
+ </template>
11
+
12
+ <script>
13
+ export default {
14
+ name: 'VirtualElTable',
15
+ inheritAttrs: false,
16
+
17
+ props: {
18
+ virtualScroll: {
19
+ type: Boolean,
20
+ default: false
21
+ },
22
+ rowHeight: {
23
+ type: Number,
24
+ default: 48
25
+ },
26
+ bufferSize: {
27
+ type: Number,
28
+ default: 5
29
+ }
30
+ },
31
+
32
+ data() {
33
+ return {
34
+ scrollWrap: null,
35
+ phantomDom: null,
36
+ scrollTop: 0,
37
+ // 根据数据量与容器高度预先估算一个合理的 endIndex,避免首屏只渲染 0 行
38
+ startIndex: 0,
39
+ endIndex: 20,
40
+ rafId: null,
41
+ unwatchPhantom: null,
42
+ layoutTimer: null,
43
+ retryTimers: []
44
+ }
45
+ },
46
+
47
+ computed: {
48
+ originData() {
49
+ return this.$attrs.data || []
50
+ },
51
+ renderList() {
52
+ if (!this.virtualScroll) return this.originData
53
+ const start = Math.max(0, this.startIndex - this.bufferSize)
54
+ const end = Math.min(this.originData.length, this.endIndex + this.bufferSize)
55
+ return this.originData.slice(start, end)
56
+ },
57
+ totalListHeight() {
58
+ return this.originData.length * this.rowHeight
59
+ },
60
+ contentOffsetY() {
61
+ return Math.max(0, this.startIndex - this.bufferSize) * this.rowHeight
62
+ }
63
+ },
64
+
65
+ watch: {
66
+ renderList() {
67
+ this.scheduleLayout()
68
+ },
69
+ originData: {
70
+ handler(val) {
71
+ if (!this.virtualScroll) return
72
+ if (val && val.length) {
73
+ this.$nextTick(() => {
74
+ this.calcVisibleRange()
75
+ })
76
+ }
77
+ }
78
+ }
79
+ },
80
+
81
+ mounted() {
82
+ this.$nextTick(() => {
83
+ this.initVirtualScroll()
84
+ })
85
+ },
86
+
87
+ beforeDestroy() {
88
+ this.unbindScrollEvent()
89
+ if (this.rafId) {
90
+ cancelAnimationFrame(this.rafId)
91
+ this.rafId = null
92
+ }
93
+ if (this.unwatchPhantom) {
94
+ this.unwatchPhantom()
95
+ this.unwatchPhantom = null
96
+ }
97
+ if (this.layoutTimer) {
98
+ clearTimeout(this.layoutTimer)
99
+ this.layoutTimer = null
100
+ }
101
+ this.retryTimers.forEach(t => clearTimeout(t))
102
+ this.retryTimers = []
103
+ },
104
+
105
+ methods: {
106
+ initVirtualScroll() {
107
+ if (!this.virtualScroll) return
108
+ this.scrollWrap = this.$refs.innerTable.$el.querySelector('.el-table__body-wrapper')
109
+ if (!this.scrollWrap) {
110
+ console.warn('[VirtualElTable] 无法找到滚动容器,虚拟滚动功能已禁用')
111
+ this.virtualScroll = false
112
+ return
113
+ }
114
+
115
+ this.scrollWrap.style.overflowY = 'auto'
116
+ this.scrollWrap.style.overflowX = 'auto'
117
+
118
+ this.wrapTableBody()
119
+ this.scrollWrap.addEventListener('scroll', this.onScroll, { passive: true })
120
+
121
+ // 首次可见范围计算 + 多次重试:
122
+ // ElementUI 的 el-table 在 mounted 后仍会异步完成列宽/高度布局,
123
+ // 单次 $nextTick 可能拿到 clientHeight=0,导致计算失败且无法恢复。
124
+ // 以递增延迟多次调用,直到 scrollWrap.clientHeight 获得稳定值。
125
+ this.calcVisibleRange()
126
+ const delays = [100, 300, 600, 1000]
127
+ delays.forEach(ms => {
128
+ const timer = setTimeout(() => {
129
+ this.calcVisibleRange()
130
+ }, ms)
131
+ this.retryTimers.push(timer)
132
+ })
133
+ },
134
+
135
+ wrapTableBody() {
136
+ const bodyDom = this.scrollWrap.querySelector('.el-table__body')
137
+ if (!bodyDom || bodyDom.querySelector('.vs-phantom')) return
138
+
139
+ // phantom 采用标准 block 布局:
140
+ // - height = 总数据高度,确保撑开 body-wrapper 的滚动条
141
+ // - padding-top = 可见区域偏移量,将 table 推到正确位置
142
+ // - min-width: 100%(不设 width),允许 table 自然撑开,保证列宽生效
143
+ const phantom = document.createElement('div')
144
+ phantom.className = 'vs-phantom'
145
+ phantom.style.position = 'relative'
146
+ phantom.style.minWidth = '100%'
147
+ phantom.style.boxSizing = 'border-box'
148
+ phantom.style.zIndex = '1'
149
+ this.phantomDom = phantom
150
+
151
+ const table = bodyDom.querySelector('table')
152
+ phantom.appendChild(table)
153
+ bodyDom.appendChild(phantom)
154
+
155
+ this.unwatchPhantom = this.$watch(
156
+ ['totalListHeight', 'contentOffsetY'],
157
+ () => {
158
+ if (this.phantomDom) {
159
+ this.phantomDom.style.height = `${this.totalListHeight}px`
160
+ this.phantomDom.style.paddingTop = `${this.contentOffsetY}px`
161
+ }
162
+ },
163
+ { immediate: true }
164
+ )
165
+ },
166
+
167
+ onScroll() {
168
+ this.scrollTop = this.scrollWrap.scrollTop
169
+ if (this.rafId) return
170
+ this.rafId = requestAnimationFrame(() => {
171
+ this.calcVisibleRange()
172
+ this.rafId = null
173
+ })
174
+ },
175
+
176
+ calcVisibleRange() {
177
+ if (!this.scrollWrap) return
178
+ let viewHeight = this.scrollWrap.clientHeight
179
+
180
+ // 关键兜底:ElementUI 异步布局尚未完成时,clientHeight 可能为 0,
181
+ // 此时用组件接收到的 height / max-height 作为估算值,确保至少能渲染正确数量的行。
182
+ if (!viewHeight) {
183
+ const h = this.$attrs.height || this.$attrs.maxHeight
184
+ if (h) {
185
+ viewHeight = typeof h === 'number' ? h : parseInt(String(h), 10)
186
+ }
187
+ }
188
+ if (!viewHeight || viewHeight <= 0) return
189
+
190
+ const visibleRowCount = Math.ceil(viewHeight / this.rowHeight)
191
+ const newStart = Math.floor(this.scrollTop / this.rowHeight)
192
+ const newEnd = Math.min(this.originData.length, newStart + visibleRowCount)
193
+
194
+ if (newStart !== this.startIndex || newEnd !== this.endIndex) {
195
+ this.startIndex = newStart
196
+ this.endIndex = newEnd
197
+ }
198
+ },
199
+
200
+ unbindScrollEvent() {
201
+ if (this.scrollWrap) {
202
+ this.scrollWrap.removeEventListener('scroll', this.onScroll)
203
+ }
204
+ },
205
+
206
+ scheduleLayout() {
207
+ if (this.layoutTimer) return
208
+ this.layoutTimer = setTimeout(() => {
209
+ if (this.$refs.innerTable) {
210
+ this.$refs.innerTable.doLayout()
211
+ }
212
+ this.layoutTimer = null
213
+ }, 0)
214
+ },
215
+
216
+ clearSelection() {
217
+ return this.$refs.innerTable.clearSelection()
218
+ },
219
+ toggleRowSelection() {
220
+ return this.$refs.innerTable.toggleRowSelection()
221
+ },
222
+ doLayout() {
223
+ return this.$refs.innerTable.doLayout()
224
+ }
225
+ }
226
+ }
227
+ </script>
228
+
229
+ <style scoped>
230
+ .vs-phantom {
231
+ position: relative;
232
+ box-sizing: border-box;
233
+ }
234
+ </style>