@xuekl/cli-components 1.6.1 → 1.9.145

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/XklTable.vue CHANGED
@@ -1,41 +1,76 @@
1
1
  <template>
2
- <el-table ref="XklTable" :data="table.list" style="width: 100%"
3
- :header-cell-style="table.headerCellStyle || recieveConf.headerCellStyle"
4
- :header-cell-class-name="table.headerCellClassName" :cell-class-name="table.cellClassName" v-bind="table.element">
2
+ <el-table ref="XklTableRef" class="xkl-table" :data="table.virtual ? table.virtualList : table.list"
3
+ style="width: 100%" :header-cell-style="table.headerCellStyle || recieveConf.headerCellStyle"
4
+ :header-cell-class-name="table.headerCellClassName" :cell-class-name="table.cellClassName"
5
+ v-bind="{ ...table.element, ...$attrs }" v-on="table.events || {}">
5
6
  <el-table-column v-if="table.expand" v-bind="table.expandConfig" type="expand" align="center">
6
7
  <template v-slot="{ row }">
7
8
  <slot name="expand" :row="row"></slot>
8
9
  </template>
9
10
  </el-table-column>
10
- <el-table-column v-if="table.selection" v-bind="table.selectionConfig" type="selection"
11
- align="center"></el-table-column>
11
+ <el-table-column v-if="!!table.virtual" align="center" :show-overflow-tooltip="false"
12
+ class-name="el-table-column--selection" fixed="left" width="50px">
13
+ <template #header>
14
+ <el-checkbox @change="virtualSelectAll()" v-model="virtualAllSelected"
15
+ :indeterminate="virtualIsIndeterminate"></el-checkbox>
16
+ </template>
17
+ <template #default="{ row }">
18
+ <el-checkbox @change="virtualSelect(row)" v-model="row.selected" @click.stop></el-checkbox>
19
+ </template>
20
+ </el-table-column>
21
+ <template v-else>
22
+ <el-table-column v-if="table.disabledSelectionPlaceholder" class-name="el-table-column--selection"
23
+ fixed="left" align="center" width="50px">
24
+ <template #header>
25
+ <el-checkbox :disabled="true" :checked="true"></el-checkbox>
26
+ </template>
27
+ <el-checkbox :disabled="true" :checked="true"></el-checkbox>
28
+ </el-table-column>
29
+ <el-table-column v-if="!table.disabledSelectionPlaceholder && table.selection"
30
+ v-bind="table.selectionConfig" :show-overflow-tooltip="false" type="selection"
31
+ align="center"></el-table-column>
32
+ </template>
12
33
  <el-table-column v-if="table.index" type="index" align="center" v-bind="table.indexConfig"></el-table-column>
13
- <el-table-column v-for=" item in table.columns " :key="item.prop" :prop="item.prop" :label="item.label"
14
- :width="item.width" :align="item.align || 'center'" :sortable="item.sortable" v-bind="item.element">
34
+ <el-table-column v-if="table.customColumn" align="center" v-bind="table.customColumnConfig">
35
+ <template #default="{ row, $index }">
36
+ <slot name="customColumn" :row="row" :index="$index"></slot>
37
+ </template>
38
+ </el-table-column>
39
+ <el-table-column v-for="item in table.columns" :key="item.prop" :prop="item.prop" :label="item.label"
40
+ :width="item.width" :align="item.align || recieveConf.defaultAlignment || 'center'"
41
+ :sortable="item.sortable" v-bind="item.element">
15
42
  <template v-if="item.reflect" v-slot="{ row }">
16
43
  {{ row[item.reflect] }}
17
44
  </template>
18
45
  <template v-if="item.dict" v-slot="{ row }">
19
- <el-tag v-if="item.tags"
46
+ <el-tag v-if="item.tags && dictLabel(item, row)" :key="row[item.prop]"
20
47
  :style="{ backgroundColor: tagColor(item, row) + '22', borderColor: tagColor(item, row) + '22', color: tagColor(item, row) }">
21
48
  {{ dictLabel(item, row) }}
22
49
  </el-tag>
23
50
  <span v-else>{{ dictLabel(item, row) }}</span>
24
51
  </template>
25
52
  <template v-else-if="item.tags" v-slot="{ row }">
26
- <el-tag
53
+ <el-tag v-if="row[item.prop]" :key="row[item.prop]"
27
54
  :style="{ backgroundColor: tagColor(item, row) + '22', borderColor: tagColor(item, row) + '22', color: tagColor(item, row) }">
28
55
  {{ row[item.prop] }}
29
56
  </el-tag>
30
57
  </template>
31
- <template v-else-if="item.slot" v-slot="{ row, $index }">
58
+ <template v-else-if="slots[item.prop]" v-slot="{ row, $index }">
32
59
  <slot :name="item.prop" :row="row" :index="$index"></slot>
33
60
  </template>
61
+ <!-- <template v-else-if="item.slot" v-slot="{ row, $index }">
62
+ <slot :name="item.prop" :row="row" :index="$index"></slot>
63
+ </template> -->
34
64
  <template v-else-if="item.format" v-slot="{ row }">
35
- {{moment(row[item.prop]).format(item.format)}}
65
+ {{ moment(row[item.prop]).format(item.format) }}
36
66
  </template>
37
67
  </el-table-column>
38
- <el-table-column v-if="table.operation.operation !== false" align="center" v-bind="table.operation">
68
+ <el-table-column v-for="item in table.endColumns" :key="item.prop" :prop="item.prop" :label="item.label"
69
+ :width="item.width" :align="item.align || recieveConf.defaultAlignment || 'center'"
70
+ :sortable="item.sortable" v-bind="item.element">
71
+ </el-table-column>
72
+ <el-table-column v-if="table.operation.operation !== false" align="center" :resizable="false"
73
+ v-bind="table.operation">
39
74
  <template #default="{ row, $index }">
40
75
  <slot name="_operation" :row="row" :index="$index"></slot>
41
76
  </template>
@@ -44,6 +79,8 @@
44
79
  <el-empty description="暂无数据"></el-empty>
45
80
  </template>
46
81
  </el-table>
82
+
83
+ <!-- <xkl-table-v2 v-else ref="XklTable" :table="table"></xkl-table-v2> -->
47
84
  </template>
48
85
  <script lang="ts">
49
86
  export default {
@@ -51,15 +88,18 @@ export default {
51
88
  }
52
89
  </script>
53
90
  <script setup lang="ts">
54
- import http from "@/utils/httpRequest"
55
- import { computed, ref } from "vue";
91
+ import { computed, onBeforeMount, onBeforeUnmount, onMounted, ref, useSlots } from "vue";
92
+ import useApp from "@/store/app";
93
+ import { tableConf } from './index'
94
+ import DictStore from "@/utils/DictStore";
56
95
  import moment from 'dayjs'
57
- import { baseConf, tableConf } from './index'
58
96
  const props = defineProps(['table'])
97
+ const emit = defineEmits(['virtual-select', 'virtual-select-all'])
98
+ const slots = useSlots()
59
99
  const { table } = props
60
100
  const dictTypes = table.columns.map(res => res.dict).filter(res => !!res)
61
101
  const dictStore = ref({})
62
- const XklTable = ref(null)
102
+ const XklTableRef = ref(null)
63
103
  table.element = table.element || {}
64
104
 
65
105
  const recieveConf = tableConf || {}
@@ -67,16 +107,30 @@ if (tableConf) {
67
107
  table.element = { ...tableConf.element, ...table.element }
68
108
  }
69
109
 
70
- dictTypes.forEach((dict: string) => {
71
- http({
72
- url: http.adornUrl(`${baseConf.dict_api_url}${dict}`),
73
- method: 'get',
74
- params: http.adornParams({})
75
- }).then(({ data }) => {
76
- dictStore.value[dict] = data.data
77
- })
110
+
111
+ const ratio = computed(() => {
112
+ const docEl = document.documentElement
113
+ const width = docEl.getBoundingClientRect().width;
114
+ return width / 1920
115
+ })
116
+
117
+ if (tableConf && tableConf.element) {
118
+ if (tableConf.element.maxHeight) {
119
+ table.element.maxHeight = tableConf.element.maxHeight * ratio.value
120
+ }
121
+ if (tableConf.element.height) {
122
+ table.element.height = tableConf.element.height * ratio.value
123
+ }
124
+ }
125
+
126
+ onBeforeMount(async () => {
127
+ for (let dict of dictTypes) {
128
+ dictStore.value[dict] = await DictStore.getDict(dict)
129
+ }
78
130
  })
79
131
 
132
+
133
+
80
134
  const dictLabel = computed(() => {
81
135
  return function (col: any, row: any) {
82
136
  if (dictStore.value[col.dict]) {
@@ -87,6 +141,7 @@ const dictLabel = computed(() => {
87
141
  }
88
142
  })
89
143
 
144
+
90
145
  const tagColor = computed(() => {
91
146
  return function (col, row) {
92
147
  if (col.tags) {
@@ -101,15 +156,35 @@ const tagColor = computed(() => {
101
156
  }
102
157
  })
103
158
 
104
- const getRef = () => {
105
- return XklTable.value
159
+ table.getRef = () => XklTableRef.value
160
+
161
+ const virtualAllSelected = ref(false)
162
+ const virtualIsIndeterminate = ref(false)
163
+
164
+ const setCheckboxState = (isAllSelected, isIndeterminate) => {
165
+ virtualAllSelected.value = isAllSelected
166
+ virtualIsIndeterminate.value = isIndeterminate
167
+ }
168
+
169
+ const virtualSelectAll = () => {
170
+ emit('virtual-select-all', virtualAllSelected.value, setCheckboxState)
106
171
  }
107
172
 
108
- table.getRef = () => XklTable.value
173
+ const virtualSelect = (row) => {
174
+ emit('virtual-select', row)
175
+ }
109
176
 
110
- defineExpose({
111
- getRef
177
+ onMounted(() => {
178
+ })
179
+
180
+
181
+ onBeforeUnmount(() => {
182
+ table.getRef = null
112
183
  })
113
184
 
114
185
 
186
+ defineExpose({
187
+ getRef: table.getRef,
188
+ setCheckboxState
189
+ })
115
190
  </script>
package/XklTableV2.vue ADDED
@@ -0,0 +1,54 @@
1
+ <template>
2
+ <div style="height: 400px">
3
+ <el-auto-resizer>
4
+ <template #default="{ height, width }">
5
+ <el-table-v2 v-model:sort-state="sortState" :header-class="table.headerClass || recieveConf.headerClass"
6
+ :columns="table.vColumns" :data="table.list" :width="width" :height="height" fixed
7
+ @column-sort="onSort" />
8
+ </template>
9
+ </el-auto-resizer>
10
+ </div>
11
+ </template>
12
+ <script lang="ts">
13
+ export default {
14
+ name: 'XklTableV2'
15
+ }
16
+ </script>
17
+ <script setup lang="ts">
18
+ import { ref } from 'vue';
19
+ import { baseConf, tableConf } from './index'
20
+ import { SortState, TableV2SortOrder } from 'element-plus';
21
+ const props = defineProps(['table'])
22
+ const { table } = props
23
+ const recieveConf = tableConf || {}
24
+ const sortState = ref<SortState>({})
25
+ table.vColumns = table.columns.map(col => {
26
+ let sortable = false
27
+ if (col.extra) {
28
+ sortable = col.extra.sortable
29
+ } else {
30
+ sortable = col.element.sortable
31
+ }
32
+ if (sortable) {
33
+ sortState.value[col.prop] = TableV2SortOrder.ASC
34
+ }
35
+ return {
36
+ key: col.prop,
37
+ dataKey: col.prop,
38
+ title: col.label,
39
+ width: 150,
40
+ sortable
41
+ }
42
+ })
43
+
44
+
45
+
46
+ const onSort = ({ key, order }) => {
47
+ sortState.value[key] = order
48
+ table.events.sortChange({ prop: key, order: order + 'ending' })
49
+ }
50
+
51
+ const clearSelection = () => { }
52
+
53
+ defineExpose({ clearSelection })
54
+ </script>
package/XklUpload.vue CHANGED
@@ -26,7 +26,8 @@ const { config, modelValue } = props
26
26
  config.params = config.params || (() => { })
27
27
 
28
28
  const headers = {
29
- [config.authorization || 'Authorization']: getToken()
29
+ [config.authorization || 'Authorization']: getToken(),
30
+ ...config.headers
30
31
  }
31
32
  const actionUrl = http.adornUrl(config.url || '/common/upload')
32
33
 
@@ -0,0 +1,332 @@
1
+ <template>
2
+ <div ref="XklVirtualScrollRef">
3
+ <slot></slot>
4
+ </div>
5
+ </template>
6
+ <script lang="ts">
7
+ export default {
8
+ name: 'XklVirtualScroll'
9
+ }
10
+ </script>
11
+ <script setup lang="ts">
12
+ import { debounce } from '@xuekl/cli-utils'
13
+ import { computed, nextTick, onMounted, reactive, ref, watch, onBeforeUnmount, onActivated } from 'vue';
14
+ const props = defineProps({
15
+ data: {
16
+ type: Array,
17
+ required: true
18
+ },
19
+ height: {
20
+ type: Number,
21
+ default: 60
22
+ },
23
+ buffer: {
24
+ type: Number,
25
+ default: 200
26
+ },
27
+ keyProp: {
28
+ type: String,
29
+ default: 'id'
30
+ },
31
+ throttleTime: {
32
+ type: Number,
33
+ default: 100
34
+ },
35
+ target: {
36
+ type: String
37
+ },
38
+ itemTarget: {
39
+ type: String
40
+ }
41
+ })
42
+
43
+ const emit = defineEmits(['change'])
44
+
45
+ const XklVirtualScrollRef = ref()
46
+ let ThisEl
47
+
48
+ const sizes: any = ref({})
49
+ const itemSize = ref(40)
50
+
51
+ const offsetMap = computed(() => {
52
+ const { keyProp, height, data }: any = props
53
+ const res = {}
54
+ let total = 0
55
+ for (let i = 0; i < data.length; i++) {
56
+ const key = data[i][keyProp]
57
+ res[key] = total
58
+
59
+ // const curSize = sizes.value[key]
60
+ const curSize = itemSize.value
61
+ const size = typeof curSize === 'number' ? curSize : height
62
+ total += size
63
+ }
64
+ return res
65
+ })
66
+
67
+ const rootForm = reactive<{
68
+ renderData: any[],
69
+ top: number,
70
+ bottom: number,
71
+ start: number,
72
+ end: number,
73
+ height: number,
74
+ timer?: NodeJS.Timeout,
75
+ onScroll?: any
76
+ }>({
77
+ renderData: [],
78
+ top: 0,
79
+ bottom: 0,
80
+ start: 0,
81
+ end: 0,
82
+ height: 360
83
+ })
84
+
85
+
86
+ const scroller = ref()
87
+
88
+ const updateSizes = () => {
89
+ const row = ThisEl.querySelector(props.itemTarget)
90
+ if (row) {
91
+ itemSize.value = row.offsetHeight
92
+ }
93
+ // const rows = ThisEl.querySelectorAll(props.itemTarget)
94
+
95
+ // Array.from(rows).forEach((row: any, index) => {
96
+ // const item = rootForm.renderData[index]
97
+ // if (!item) return
98
+
99
+ // const key = item[props.keyProp]
100
+ // const offsetHeight = row.offsetHeight
101
+
102
+ // if (sizes.value[key] !== offsetHeight) {
103
+ // sizes.value[key] = offsetHeight
104
+ // }
105
+ // })
106
+ }
107
+
108
+ const getOffsetTop = (index) => {
109
+ // const item = props.data[index]
110
+ // if (item) {
111
+ // return offsetMap.value[item[props.keyProp]] || 0
112
+ // }
113
+ // return 0
114
+ return index * itemSize.value
115
+ }
116
+
117
+ // 获取某条数据的尺寸
118
+ const getSize = (index) => {
119
+ return itemSize.value || props.height
120
+ // const item = props.data[index]
121
+ // if (item) {
122
+ // const key = item[props.keyProp]
123
+ // return sizes.value[key] || rootForm.height
124
+ // }
125
+ // return rootForm.height
126
+ }
127
+
128
+ let beforeScrollTop = 0
129
+
130
+ // 计算只在视图上渲染的数据
131
+ const calcRenderData = () => {
132
+ let startTime = Date.now()
133
+ const { data, buffer } = props
134
+ // 计算可视范围顶部、底部
135
+ const top = scroller.value.scrollTop - buffer
136
+ const bottom = scroller.value.scrollTop + scroller.value.offsetHeight + buffer
137
+ beforeScrollTop = scroller.value.scrollTop
138
+
139
+ // 二分法计算可视范围内的开始的第一个内容
140
+ let l = 0
141
+ let r = data.length - 1
142
+ let mid = 0
143
+ while (l <= r) {
144
+ mid = Math.floor((l + r) / 2)
145
+ const midVal = getOffsetTop(mid)
146
+ if (midVal < top) {
147
+ const midNextVal = getOffsetTop(mid + 1)
148
+ if (midNextVal > top) break
149
+ l = mid + 1
150
+ } else {
151
+ r = mid - 1
152
+ }
153
+ }
154
+
155
+ // 计算渲染内容的开始、结束索引
156
+ let start = mid
157
+ let end = data.length - 1
158
+ for (let i = start + 1; i < data.length; i++) {
159
+ const offsetTop = getOffsetTop(i)
160
+ if (offsetTop >= bottom) {
161
+ end = i
162
+ break
163
+ }
164
+ }
165
+
166
+ // 开始索引始终保持偶数,如果为奇数,则加1使其保持偶数【确保表格行的偶数数一致,不会导致斑马纹乱序显示】
167
+ if (start % 2) {
168
+ start = start - 1
169
+ }
170
+ // //console.log(start, end, 'start end')
171
+
172
+ rootForm.top = top
173
+ rootForm.bottom = bottom
174
+ rootForm.start = start
175
+ rootForm.end = end
176
+ rootForm.renderData = data.slice(start, end + 1)
177
+ //console.log("calcRenderData。。。", start, end, bottom, top, scroller.value.offsetHeight)
178
+ }
179
+
180
+ // 计算位置
181
+ const calcPosition = () => {
182
+ // const last = props.data.length - 1
183
+ const length = props.data.length
184
+ // 计算内容总高度
185
+ // const wrapHeight = last < 0 ? rootForm.height : getOffsetTop(last) + getSize(last)
186
+
187
+ const wrapHeight = (length * itemSize.value) || rootForm.height
188
+ // 计算当前滚动位置需要撑起的高度
189
+ const offsetTop = getOffsetTop(rootForm.start)
190
+
191
+
192
+ // 设置dom位置
193
+ // const classNames = [props.target]
194
+ // classNames.forEach(className => {
195
+
196
+
197
+ // })
198
+ const el = scroller.value
199
+ if (!el) return
200
+
201
+ // 创建wrapEl、innerEl
202
+ if (!el.wrapEl) {
203
+ const wrapEl = document.createElement('div')
204
+ const innerEl = document.createElement('div')
205
+ wrapEl.appendChild(innerEl)
206
+ innerEl.appendChild(el.children[0])
207
+ el.insertBefore(wrapEl, el.firstChild)
208
+ el.wrapEl = wrapEl
209
+ el.innerEl = innerEl
210
+ }
211
+
212
+ if (el.wrapEl) {
213
+ // 设置高度
214
+ el.wrapEl.style.height = wrapHeight + 'px'
215
+ // 设置transform撑起高度
216
+ // el.innerEl.style.transform = `translateY(${offsetTop}px)`
217
+ el.innerEl.style.setProperty('transform', `translateY(${offsetTop}px)`);
218
+
219
+ // 设置paddingTop撑起高度
220
+ // el.innerEl.style.paddingTop = `${offsetTop}px`
221
+ //console.log("calcPosition。。。")
222
+ }
223
+ }
224
+
225
+ // 空闲时更新位置
226
+ const updatePosition = () => {
227
+ //console.log('我就一直走')
228
+ rootForm.timer && clearTimeout(rootForm.timer)
229
+ rootForm.timer = setTimeout(() => {
230
+ rootForm.timer && clearTimeout(rootForm.timer)
231
+ // 传入false,避免一直循环调用
232
+ handleScroll()
233
+ }, props.throttleTime + 10)
234
+ }
235
+
236
+ // 【外部调用】更新
237
+ const update = () => {
238
+ //console.log('update');
239
+
240
+ updateAll()
241
+ }
242
+
243
+ // 【外部调用】滚动到第几行
244
+ const scrollTo = (index, stop = false) => {
245
+ const item = props.data[index]
246
+ if (item && scroller.value) {
247
+ updateSizes()
248
+ calcRenderData()
249
+
250
+ nextTick(() => {
251
+ const offsetTop = getOffsetTop(index)
252
+ scroller.value.scrollTop = offsetTop
253
+
254
+ // 调用两次scrollTo,第一次滚动时,如果表格行初次渲染高度发生变化时,会导致滚动位置有偏差,此时需要第二次执行滚动,确保滚动位置无误
255
+ if (!stop) {
256
+ setTimeout(() => {
257
+ scrollTo(index, true)
258
+ }, 50)
259
+ }
260
+ })
261
+ }
262
+ }
263
+
264
+ // 【外部调用】重置
265
+ const reset = () => {
266
+ sizes.value = {}
267
+ scrollTo(0, false)
268
+ }
269
+
270
+ const updateAll = () => {
271
+ updateSizes()
272
+ calcRenderData()
273
+ calcPosition()
274
+ emit('change', rootForm.renderData, rootForm.start, rootForm.end)
275
+ //console.log('data change', rootForm.renderData);
276
+ }
277
+
278
+ let renderData = debounce(updateAll, 100, false)
279
+
280
+ const handleScroll = () => {
281
+
282
+ // 更新当前尺寸(高度)
283
+ // updateSizes()
284
+ // 计算renderData
285
+ // calcRenderData()
286
+ renderData()
287
+ // 计算位置
288
+ // calcPosition()
289
+ // shouldUpdate && updatePosition()
290
+ // 触发事件
291
+
292
+ }
293
+
294
+ const initData = () => {
295
+
296
+ // 初次执行
297
+ // setTimeout(() => {
298
+ // handleScroll()
299
+ // }, 100)
300
+
301
+ // 监听事件
302
+ // rootForm.onScroll = throttle(handleScroll, props.throttleTime)
303
+ scroller.value.addEventListener('scroll', handleScroll);
304
+ window.addEventListener('resize', handleScroll);
305
+ }
306
+
307
+ watch(() => props.data, () => {
308
+ update()
309
+ })
310
+
311
+ onMounted(() => {
312
+ ThisEl = XklVirtualScrollRef.value
313
+ scroller.value = ThisEl.querySelector(props.target)
314
+
315
+ initData()
316
+ })
317
+
318
+ onActivated(() => {
319
+ scroller.value.scrollTop = beforeScrollTop
320
+ update()
321
+
322
+ })
323
+
324
+ onBeforeUnmount(() => {
325
+ if (scroller.value) {
326
+ scroller.value.removeEventListener('scroll', handleScroll)
327
+ window.removeEventListener('resize', handleScroll)
328
+ }
329
+ })
330
+ </script>
331
+
332
+ <style lang='less' scoped></style>