sh-view 2.8.1 → 2.8.2

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.
Files changed (91) hide show
  1. package/.eslintrc.js +25 -20
  2. package/other.js +8 -8
  3. package/package.json +9 -6
  4. package/packages/components/index.js +91 -91
  5. package/packages/components/sh-alert/alert.ts +30 -0
  6. package/packages/components/sh-alert/index.vue +143 -168
  7. package/packages/components/sh-badge/index.vue +242 -242
  8. package/packages/components/sh-calendar/index.vue +650 -650
  9. package/packages/components/sh-card/index.vue +148 -148
  10. package/packages/components/sh-code-editor/index.vue +19 -19
  11. package/packages/components/sh-col/index.vue +92 -92
  12. package/packages/components/sh-corner/index.vue +230 -230
  13. package/packages/components/sh-count-to/index.vue +131 -131
  14. package/packages/components/sh-date/index.vue +301 -301
  15. package/packages/components/sh-drawer/index.vue +579 -579
  16. package/packages/components/sh-drawer/scrollbar.js +78 -78
  17. package/packages/components/sh-empty/index.vue +42 -42
  18. package/packages/components/sh-form/js/props.js +76 -76
  19. package/packages/components/sh-form/js/useForm.js +229 -229
  20. package/packages/components/sh-header/index.vue +261 -260
  21. package/packages/components/sh-icon/css/default/ionicons.svg +869 -869
  22. package/packages/components/sh-icon/css/font/iconfont.json +247 -247
  23. package/packages/components/sh-icon/index.vue +41 -41
  24. package/packages/components/sh-image/index.vue +133 -133
  25. package/packages/components/sh-list/index.vue +146 -146
  26. package/packages/components/sh-loading/index.vue +53 -53
  27. package/packages/components/sh-modal/index.vue +188 -188
  28. package/packages/components/sh-noticebar/index.vue +215 -215
  29. package/packages/components/sh-poptip/index.vue +597 -597
  30. package/packages/components/sh-progress/index.vue +276 -276
  31. package/packages/components/sh-pull-refresh/index.vue +289 -289
  32. package/packages/components/sh-result/index.vue +114 -114
  33. package/packages/components/sh-row/index.vue +66 -66
  34. package/packages/components/sh-split/components/trigger.vue +33 -33
  35. package/packages/components/sh-split/index.vue +342 -342
  36. package/packages/components/sh-table/components/importModal.vue +363 -363
  37. package/packages/components/sh-table/components/sh-column.vue +68 -68
  38. package/packages/components/sh-table/js/excel_to_json.js +313 -313
  39. package/packages/components/sh-table/js/props.js +305 -305
  40. package/packages/components/sh-table/js/tableMethods.js +167 -167
  41. package/packages/components/sh-table/js/useTable.js +636 -636
  42. package/packages/components/sh-table/table.vue +217 -217
  43. package/packages/components/sh-tabs/index.vue +426 -426
  44. package/packages/components/sh-tag/index.vue +168 -168
  45. package/packages/components/sh-toolbar/index.vue +182 -182
  46. package/packages/components/sh-tree/components/table-tree.vue +289 -289
  47. package/packages/components/sh-tree/mixin/treeProps.js +122 -122
  48. package/packages/components/sh-upload/index.vue +535 -535
  49. package/packages/components/sh-water-fall/index.vue +80 -80
  50. package/packages/components/sh-water-mark/index.vue +96 -96
  51. package/packages/css/index.js +4 -4
  52. package/packages/directive/index.js +19 -19
  53. package/packages/directive/module/click-out.js +14 -14
  54. package/packages/directive/module/draggable.js +42 -42
  55. package/packages/directive/module/line-clamp.js +22 -22
  56. package/packages/directive/module/prevent-click.js +18 -18
  57. package/packages/directive/module/resize.js +14 -14
  58. package/packages/directive/module/ripple.js +166 -166
  59. package/packages/index.js +39 -39
  60. package/packages/mixin/index.js +86 -86
  61. package/packages/other/sh-cron-modal/components/cron-content.vue +294 -294
  62. package/packages/other/sh-cron-modal/index.vue +81 -81
  63. package/packages/other/sh-cron-modal/mixin/cron-emits.js +1 -1
  64. package/packages/other/sh-cron-modal/mixin/cron-props.js +9 -9
  65. package/packages/other/sh-cron-modal/tabs/cron-week-box.vue +126 -126
  66. package/packages/other/sh-menu/index.vue +326 -326
  67. package/packages/other/sh-menu/menu-group-content.vue +136 -136
  68. package/packages/other/sh-menu/menu-item-content.vue +71 -71
  69. package/packages/other/sh-menu-card/index.vue +250 -250
  70. package/packages/other/sh-menu-card/menu-box.vue +87 -87
  71. package/packages/other/sh-preview/components/sh-excel.vue +163 -163
  72. package/packages/other/sh-preview/js/data-hook.js +41 -41
  73. package/packages/other/sh-preview/js/data-props.js +15 -15
  74. package/packages/other/sh-system-tip/index.vue +115 -115
  75. package/packages/utils/resize.js +69 -70
  76. package/packages/utils/transfer-queue.js +12 -12
  77. package/packages/vxeTable/index.js +193 -184
  78. package/packages/vxeTable/plugins/export.js +450 -450
  79. package/packages/vxeTable/render/cell/vxe-render-img.vue +27 -27
  80. package/packages/vxeTable/render/cell/vxe-render-table.vue +51 -51
  81. package/packages/vxeTable/render/cell/vxe-render-time.vue +44 -44
  82. package/packages/vxeTable/render/cell/vxe-render-tree.vue +70 -70
  83. package/packages/vxeTable/render/filters/vxe-filter-input.vue +26 -26
  84. package/packages/vxeTable/render/filters/vxe-filter-time.vue +26 -26
  85. package/packages/vxeTable/render/globalRenders.jsx +514 -514
  86. package/packages/vxeTable/render/mixin/cell-hooks.js +198 -198
  87. package/packages/vxeTable/render/mixin/cell-props.js +23 -23
  88. package/packages/vxeTable/render/mixin/filter-hooks.js +46 -46
  89. package/tsconfig.json +25 -0
  90. package/types/component.d.ts +1 -0
  91. package/types/index.ts +0 -0
@@ -1,426 +1,426 @@
1
- <template>
2
- <div class="sh-tabs" :class="tabClass">
3
- <div class="sh-tabs-nav">
4
- <div v-if="slots.left" class="sh-tabs-nav-slot sh-tabs-nav-left"><slot name="left"></slot></div>
5
- <div ref="navWrapRef" v-resize="handleResize" class="sh-tabs-nav-wrap" :class="{ 'sh-tabs-nav-scrollable': scrollable }">
6
- <span v-if="scrollable" class="sh-tabs-nav-prev" :class="{ 'sh-tabs-nav-btn-disabled': scrollable }" @click="scrollPrev">
7
- <sh-icon :type="isHorizontal ? 'ios-arrow-up' : 'ios-arrow-back'"></sh-icon>
8
- </span>
9
- <div ref="navScrollRef" class="sh-tabs-nav-scroll" @DOMMouseScroll="handleScroll" @mousewheel="handleScroll">
10
- <div ref="navRef" v-resize="handleResize" class="sh-tabs-nav-inner" :style="navStyle">
11
- <template v-for="(tab, tabIndex) in tabList" :key="tabIndex">
12
- <div
13
- :class="{ 'sh-tab-item': true, 'sh-tab-item-disabled': tab.disabled, 'sh-tab-item-active': tab[labelKey] === activeKey }"
14
- :style="getTabItemStyle(tab, tabIndex)"
15
- @click="handleChange(tab)">
16
- <slot name="tabItem" v-bind="{ ...tab, isActive: tab[labelKey] === activeKey }">
17
- <div v-if="tab.icon" class="sh-tab-icon"><sh-icon :type="tab.icon"></sh-icon></div>
18
- <div class="sh-tab-label">{{ tab[labelField] }}</div>
19
- <div v-if="getTabIsClosable(tab)" class="sh-tab-close" @click.stop="handleClose(tab)"><sh-icon type="ios-close"></sh-icon></div>
20
- </slot>
21
- </div>
22
- </template>
23
- </div>
24
- </div>
25
- <span v-if="scrollable" class="sh-tabs-nav-next" :class="{ 'sh-tabs-nav-btn-disabled': scrollable }" @click="scrollNext">
26
- <sh-icon :type="isHorizontal ? 'ios-arrow-down' : 'ios-arrow-forward'"></sh-icon>
27
- </span>
28
- </div>
29
- <div v-if="slots.right" class="sh-tabs-nav-slot sh-tabs-nav-right"><slot name="right"></slot></div>
30
- </div>
31
- <div v-if="hasContent" class="sh-tabs-content">
32
- <div ref="contentWrap" class="sh-tabs-content-wrap" :class="{ 'sh-tabs-content-animated': animated }" :style="contentStyle">
33
- <slot name="tabContent" v-bind="tabList">
34
- <template v-for="(tab, tabIndex) in tabList" :key="tabIndex">
35
- <div class="sh-tabs-content-item">
36
- <slot :name="tab[labelKey]" v-bind="tab">{{ tab[labelField] }}</slot>
37
- </div>
38
- </template>
39
- </slot>
40
- </div>
41
- </div>
42
- </div>
43
- </template>
44
-
45
- <script>
46
- import { defineComponent, computed, getCurrentInstance, ref, watch, onBeforeMount } from 'vue'
47
- export default defineComponent({
48
- name: 'ShTabs',
49
- props: {
50
- modelValue: {
51
- type: [String, Number]
52
- },
53
- options: {
54
- type: Array,
55
- default() {
56
- return []
57
- }
58
- },
59
- labelField: {
60
- type: String,
61
- default: 'label'
62
- },
63
- labelKey: {
64
- type: String,
65
- default: 'value'
66
- },
67
- type: {
68
- type: String,
69
- default: 'line' // 'line', 'card'
70
- },
71
- size: {
72
- type: String,
73
- default: 'default' // 'small', 'default'
74
- },
75
- animated: {
76
- type: Boolean,
77
- default: true
78
- },
79
- closable: {
80
- type: Boolean,
81
- default: false
82
- },
83
- isContent: {
84
- type: Boolean,
85
- default: true
86
- },
87
- gutter: {
88
- type: Number,
89
- default: 5
90
- },
91
- placement: {
92
- type: String,
93
- default: 'top' // 'left', 'right', 'top', 'bottom'
94
- }
95
- },
96
- emits: ['update:modelValue', 'close', 'change'],
97
- setup(props, context) {
98
- const { proxy } = getCurrentInstance()
99
- const { $vUtils } = proxy
100
- const { emit, slots } = context
101
-
102
- const navRef = ref()
103
- const navWrapRef = ref()
104
- const navScrollRef = ref()
105
- const contentWrapRef = ref()
106
- const activeKey = ref(props.modelValue)
107
- const scrollable = ref(false)
108
- const closedList = ref([])
109
- const navStyle = ref({ transform: '' })
110
-
111
- const isHorizontal = computed(() => ['left', 'right'].includes(props.placement))
112
- const hasContent = computed(() => props.isContent && activeKey.value && props.options.map(item => item[props.labelKey]).includes(activeKey.value))
113
- const tabClass = computed(() => {
114
- return {
115
- 'sh-tabs-card': props.type === 'card',
116
- 'sh-tabs-reverse': ['bottom', 'right'].includes(props.placement),
117
- [isHorizontal.value ? 'sh-tabs-horizontal' : 'sh-tabs-vertical']: true
118
- }
119
- })
120
- const tabList = computed(() => props.options.filter(tab => !closedList.value.includes(tab[props.labelKey])) || [])
121
- const contentStyle = computed(() => {
122
- const x = tabList.value.findIndex(tab => tab[props.labelKey] === activeKey.value)
123
- const p = x === 0 ? '0%' : `-${x}00%`
124
- let style = {}
125
- if (x > -1) {
126
- style = { transform: `translateX(${p}) translateZ(0px)` }
127
- }
128
- return style
129
- })
130
-
131
- const scrollPrev = () => {
132
- const navScrollOffset = isHorizontal.value ? navScrollRef.value.offsetHeight : navScrollRef.value.offsetWidth
133
- const currentOffset = getCurrentScrollOffset()
134
- if (!currentOffset) return
135
- let newOffset = 0
136
- if (currentOffset > navScrollOffset) {
137
- newOffset = currentOffset - navScrollOffset
138
- }
139
- setOffset(newOffset)
140
- }
141
- const scrollNext = () => {
142
- const navOffset = isHorizontal.value ? navRef.value.offsetHeight : navRef.value.offsetWidth
143
- const containerOffset = isHorizontal.value ? navScrollRef.value.offsetHeight : navScrollRef.value.offsetWidth
144
- const currentOffset = getCurrentScrollOffset()
145
- if (navOffset - currentOffset <= containerOffset) return
146
- let newOffset = navOffset - currentOffset > containerOffset * 2 ? currentOffset + containerOffset : navOffset - containerOffset
147
- setOffset(newOffset)
148
- }
149
- const setOffset = value => {
150
- const direction = isHorizontal.value ? 'Y' : 'X'
151
- navStyle.value.transform = `translate${direction}(-${value}px)`
152
- }
153
- const getCurrentScrollOffset = () => {
154
- if (navStyle.value.transform && isHorizontal.value) {
155
- return Number(navStyle.value.transform.match(/translateY\(-(\d+(\.\d+)*)px\)/)[1])
156
- } else if (navStyle.value.transform) {
157
- return Number(navStyle.value.transform.match(/translateX\(-(\d+(\.\d+)*)px\)/)[1])
158
- }
159
- return 0
160
- }
161
- const getTabIsClosable = tab => {
162
- return props.closable && !tab.disabled && !tab.unClosed
163
- }
164
- const getTabItemStyle = (tab, tabIndex) => {
165
- let itemStyle = {}
166
- if (isHorizontal.value) {
167
- itemStyle.marginTop = tabIndex ? `${props.gutter}px` : 0
168
- } else {
169
- itemStyle.marginLeft = tabIndex ? `${props.gutter}px` : 0
170
- }
171
- return itemStyle
172
- }
173
- const handleResize = e => {
174
- const navOffset = isHorizontal.value ? navRef.value.offsetHeight : navRef.value.offsetWidth
175
- const containerOffset = isHorizontal.value ? navScrollRef.value.offsetHeight : navScrollRef.value.offsetWidth
176
- const currentOffset = getCurrentScrollOffset()
177
- if (containerOffset < navOffset) {
178
- scrollable.value = true
179
- if (navOffset - currentOffset < containerOffset) {
180
- setOffset(navOffset - containerOffset)
181
- }
182
- } else {
183
- scrollable.value = false
184
- if (currentOffset > 0) {
185
- setOffset(0)
186
- }
187
- }
188
- }
189
- const handleScroll = e => {
190
- e.preventDefault()
191
- e.stopPropagation()
192
- let delta = 0
193
- if (e.type === 'DOMMouseScroll' || e.type === 'mousewheel') {
194
- delta = e.wheelDelta ? e.wheelDelta : -(e.detail || 0) * 40
195
- }
196
- if (delta > 0) {
197
- scrollPrev()
198
- } else {
199
- scrollNext()
200
- }
201
- }
202
- const handleChange = tab => {
203
- if (!tab || tab.disabled) return
204
- activeKey.value = tab[props.labelKey]
205
- emit('change', tab)
206
- if (!scrollable.value) return
207
- const navOffset = isHorizontal.value ? navRef.value.offsetHeight : navRef.value.offsetWidth
208
- const containerOffset = isHorizontal.value ? navScrollRef.value.offsetHeight : navScrollRef.value.offsetWidth
209
- const prevTabs = navRef.value.querySelectorAll('.sh-tab-item')
210
- const tabIndex = tabList.value.findIndex(tb => tb[props.labelKey] === tab[props.labelKey])
211
- const offsetName = isHorizontal.value ? 'offsetHeight' : 'offsetWidth'
212
- let barOffset = 0
213
- if (tabIndex > 0) {
214
- let offset = 0
215
- for (let i = 0; i < tabIndex; i++) {
216
- offset += parseFloat(prevTabs[i][offsetName]) + props.gutter
217
- }
218
- barOffset = offset
219
- }
220
- let newOffset = navOffset - barOffset < containerOffset ? navOffset - containerOffset : barOffset
221
- setOffset(newOffset)
222
- }
223
- const handleClose = tab => {
224
- closedList.value.push(tab[props.labelKey])
225
- if (tab[props.labelKey] === activeKey.value) {
226
- let isNotDisabledList = tabList.value.filter(tab => !tab.disabled)
227
- activeKey.value = isNotDisabledList.length ? isNotDisabledList[0][props.labelKey] : ''
228
- }
229
- emit('close', tab)
230
- }
231
-
232
- watch(
233
- () => props.modelValue,
234
- value => {
235
- activeKey.value = value
236
- }
237
- )
238
- watch(
239
- () => activeKey.value,
240
- value => {
241
- emit('update:modelValue', value)
242
- }
243
- )
244
- watch(
245
- () => props.options,
246
- value => {
247
- closedList.value = []
248
- }
249
- )
250
-
251
- return {
252
- navRef,
253
- navWrapRef,
254
- navScrollRef,
255
- contentWrapRef,
256
- slots,
257
- isHorizontal,
258
- scrollable,
259
- navStyle,
260
- tabClass,
261
- tabList,
262
- activeKey,
263
- hasContent,
264
- contentStyle,
265
- scrollPrev,
266
- scrollNext,
267
- handleScroll,
268
- handleResize,
269
- handleChange,
270
- handleClose,
271
- getTabIsClosable,
272
- getTabItemStyle
273
- }
274
- }
275
- })
276
- </script>
277
-
278
- <style scoped lang="scss">
279
- .sh-tabs {
280
- width: 100%;
281
- position: relative;
282
- display: flex;
283
- .sh-tab-close {
284
- display: inline-flex;
285
- align-items: center;
286
- padding: 0 1px;
287
- &:hover {
288
- background-color: rgba(0, 0, 0, 0.1);
289
- }
290
- }
291
- &-vertical {
292
- width: 100%;
293
- flex-wrap: wrap;
294
- flex-direction: column;
295
- &.sh-tabs-reverse {
296
- flex-direction: column-reverse;
297
- }
298
- .sh-tabs-nav {
299
- width: 100%;
300
- }
301
- }
302
- &-horizontal {
303
- flex-wrap: nowrap;
304
- flex-direction: row;
305
- .sh-tabs-nav {
306
- width: auto;
307
- }
308
- .sh-tabs-nav,
309
- .sh-tabs-nav-wrap,
310
- .sh-tabs-nav-inner {
311
- flex-direction: column;
312
- }
313
- .sh-tabs-nav-prev,
314
- .sh-tabs-nav-next {
315
- width: 100%;
316
- height: 26px;
317
- }
318
- &.sh-tabs-reverse {
319
- flex-direction: row-reverse;
320
- }
321
- }
322
- &-nav {
323
- display: flex;
324
- &-slot {
325
- display: inline-flex;
326
- align-items: center;
327
- }
328
- &-wrap {
329
- flex: 1;
330
- display: flex;
331
- align-items: stretch;
332
- overflow: hidden;
333
- }
334
- &-prev,
335
- &-next {
336
- width: 30px;
337
- height: 100%;
338
- display: inline-flex;
339
- align-items: center;
340
- justify-content: center;
341
- box-shadow: 0 1px 5px rgba(0, 0, 0, 0.3);
342
- position: relative;
343
- z-index: 10;
344
- &:hover {
345
- color: var(--vxe-primary-color);
346
- }
347
- &.sh-tabs-nav-btn-disabled {
348
- }
349
- }
350
- &-scroll {
351
- flex: 1;
352
- overflow: hidden;
353
- }
354
- &-inner {
355
- display: flex;
356
- width: fit-content;
357
- white-space: nowrap;
358
- transition: transform 0.2s ease-in-out;
359
- .sh-tab-item {
360
- display: inline-flex;
361
- align-items: stretch;
362
- cursor: pointer;
363
- line-height: 20px;
364
- box-sizing: border-box;
365
- border-bottom: 2px solid transparent;
366
- &:hover {
367
- color: var(--vxe-primary-color);
368
- }
369
- &.sh-tab-item-active {
370
- border-bottom: 2px solid var(--vxe-primary-darken-color);
371
- color: var(--vxe-primary-color) !important;
372
- }
373
- &.sh-tab-item-disabled {
374
- cursor: not-allowed;
375
- color: var(--vxe-input-disabled-color) !important;
376
- }
377
- .sh-tab-icon {
378
- display: inline-flex;
379
- align-items: center;
380
- padding: 0 2px 0 5px;
381
- }
382
- .sh-tab-label {
383
- flex: 1;
384
- padding: 5px;
385
- }
386
- }
387
- }
388
- }
389
- &.sh-tabs-card {
390
- .sh-tabs-nav-prev,
391
- .sh-tabs-nav-next {
392
- border: 1px solid var(--vxe-table-border-color);
393
- }
394
- .sh-tabs-nav-inner {
395
- .sh-tab-item {
396
- border: 1px solid var(--vxe-table-border-color);
397
- }
398
- .sh-tab-item-active {
399
- background-color: var(--vxe-primary-color);
400
- border-color: var(--vxe-primary-color);
401
- color: #fff !important;
402
- }
403
- }
404
- }
405
- &-content {
406
- display: flex;
407
- overflow-x: hidden;
408
- width: 100%;
409
- &-wrap {
410
- flex: 1;
411
- display: flex;
412
- white-space: nowrap;
413
- width: 100%;
414
- &.sh-tabs-content-animated {
415
- transition: transform 0.2s ease-in-out;
416
- }
417
- }
418
- &-item {
419
- display: inline-block;
420
- min-width: 100%;
421
- vertical-align: top;
422
- position: relative;
423
- }
424
- }
425
- }
426
- </style>
1
+ <template>
2
+ <div class="sh-tabs" :class="tabClass">
3
+ <div class="sh-tabs-nav">
4
+ <div v-if="slots.left" class="sh-tabs-nav-slot sh-tabs-nav-left"><slot name="left"></slot></div>
5
+ <div ref="navWrapRef" v-resize="handleResize" class="sh-tabs-nav-wrap" :class="{ 'sh-tabs-nav-scrollable': scrollable }">
6
+ <span v-if="scrollable" class="sh-tabs-nav-prev" :class="{ 'sh-tabs-nav-btn-disabled': scrollable }" @click="scrollPrev">
7
+ <sh-icon :type="isHorizontal ? 'ios-arrow-up' : 'ios-arrow-back'"></sh-icon>
8
+ </span>
9
+ <div ref="navScrollRef" class="sh-tabs-nav-scroll" @DOMMouseScroll="handleScroll" @mousewheel="handleScroll">
10
+ <div ref="navRef" v-resize="handleResize" class="sh-tabs-nav-inner" :style="navStyle">
11
+ <template v-for="(tab, tabIndex) in tabList" :key="tabIndex">
12
+ <div
13
+ :class="{ 'sh-tab-item': true, 'sh-tab-item-disabled': tab.disabled, 'sh-tab-item-active': tab[labelKey] === activeKey }"
14
+ :style="getTabItemStyle(tab, tabIndex)"
15
+ @click="handleChange(tab)">
16
+ <slot name="tabItem" v-bind="{ ...tab, isActive: tab[labelKey] === activeKey }">
17
+ <div v-if="tab.icon" class="sh-tab-icon"><sh-icon :type="tab.icon"></sh-icon></div>
18
+ <div class="sh-tab-label">{{ tab[labelField] }}</div>
19
+ <div v-if="getTabIsClosable(tab)" class="sh-tab-close" @click.stop="handleClose(tab)"><sh-icon type="ios-close"></sh-icon></div>
20
+ </slot>
21
+ </div>
22
+ </template>
23
+ </div>
24
+ </div>
25
+ <span v-if="scrollable" class="sh-tabs-nav-next" :class="{ 'sh-tabs-nav-btn-disabled': scrollable }" @click="scrollNext">
26
+ <sh-icon :type="isHorizontal ? 'ios-arrow-down' : 'ios-arrow-forward'"></sh-icon>
27
+ </span>
28
+ </div>
29
+ <div v-if="slots.right" class="sh-tabs-nav-slot sh-tabs-nav-right"><slot name="right"></slot></div>
30
+ </div>
31
+ <div v-if="hasContent" class="sh-tabs-content">
32
+ <div ref="contentWrap" class="sh-tabs-content-wrap" :class="{ 'sh-tabs-content-animated': animated }" :style="contentStyle">
33
+ <slot name="tabContent" v-bind="tabList">
34
+ <template v-for="(tab, tabIndex) in tabList" :key="tabIndex">
35
+ <div class="sh-tabs-content-item">
36
+ <slot :name="tab[labelKey]" v-bind="tab">{{ tab[labelField] }}</slot>
37
+ </div>
38
+ </template>
39
+ </slot>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ </template>
44
+
45
+ <script>
46
+ import { defineComponent, computed, getCurrentInstance, ref, watch, onBeforeMount } from 'vue'
47
+ export default defineComponent({
48
+ name: 'ShTabs',
49
+ props: {
50
+ modelValue: {
51
+ type: [String, Number]
52
+ },
53
+ options: {
54
+ type: Array,
55
+ default() {
56
+ return []
57
+ }
58
+ },
59
+ labelField: {
60
+ type: String,
61
+ default: 'label'
62
+ },
63
+ labelKey: {
64
+ type: String,
65
+ default: 'value'
66
+ },
67
+ type: {
68
+ type: String,
69
+ default: 'line' // 'line', 'card'
70
+ },
71
+ size: {
72
+ type: String,
73
+ default: 'default' // 'small', 'default'
74
+ },
75
+ animated: {
76
+ type: Boolean,
77
+ default: true
78
+ },
79
+ closable: {
80
+ type: Boolean,
81
+ default: false
82
+ },
83
+ isContent: {
84
+ type: Boolean,
85
+ default: true
86
+ },
87
+ gutter: {
88
+ type: Number,
89
+ default: 5
90
+ },
91
+ placement: {
92
+ type: String,
93
+ default: 'top' // 'left', 'right', 'top', 'bottom'
94
+ }
95
+ },
96
+ emits: ['update:modelValue', 'close', 'change'],
97
+ setup(props, context) {
98
+ const { proxy } = getCurrentInstance()
99
+ const { $vUtils } = proxy
100
+ const { emit, slots } = context
101
+
102
+ const navRef = ref()
103
+ const navWrapRef = ref()
104
+ const navScrollRef = ref()
105
+ const contentWrapRef = ref()
106
+ const activeKey = ref(props.modelValue)
107
+ const scrollable = ref(false)
108
+ const closedList = ref([])
109
+ const navStyle = ref({ transform: '' })
110
+
111
+ const isHorizontal = computed(() => ['left', 'right'].includes(props.placement))
112
+ const hasContent = computed(() => props.isContent && activeKey.value && props.options.map(item => item[props.labelKey]).includes(activeKey.value))
113
+ const tabClass = computed(() => {
114
+ return {
115
+ 'sh-tabs-card': props.type === 'card',
116
+ 'sh-tabs-reverse': ['bottom', 'right'].includes(props.placement),
117
+ [isHorizontal.value ? 'sh-tabs-horizontal' : 'sh-tabs-vertical']: true
118
+ }
119
+ })
120
+ const tabList = computed(() => props.options.filter(tab => !closedList.value.includes(tab[props.labelKey])) || [])
121
+ const contentStyle = computed(() => {
122
+ const x = tabList.value.findIndex(tab => tab[props.labelKey] === activeKey.value)
123
+ const p = x === 0 ? '0%' : `-${x}00%`
124
+ let style = {}
125
+ if (x > -1) {
126
+ style = { transform: `translateX(${p}) translateZ(0px)` }
127
+ }
128
+ return style
129
+ })
130
+
131
+ const scrollPrev = () => {
132
+ const navScrollOffset = isHorizontal.value ? navScrollRef.value.offsetHeight : navScrollRef.value.offsetWidth
133
+ const currentOffset = getCurrentScrollOffset()
134
+ if (!currentOffset) return
135
+ let newOffset = 0
136
+ if (currentOffset > navScrollOffset) {
137
+ newOffset = currentOffset - navScrollOffset
138
+ }
139
+ setOffset(newOffset)
140
+ }
141
+ const scrollNext = () => {
142
+ const navOffset = isHorizontal.value ? navRef.value.offsetHeight : navRef.value.offsetWidth
143
+ const containerOffset = isHorizontal.value ? navScrollRef.value.offsetHeight : navScrollRef.value.offsetWidth
144
+ const currentOffset = getCurrentScrollOffset()
145
+ if (navOffset - currentOffset <= containerOffset) return
146
+ let newOffset = navOffset - currentOffset > containerOffset * 2 ? currentOffset + containerOffset : navOffset - containerOffset
147
+ setOffset(newOffset)
148
+ }
149
+ const setOffset = value => {
150
+ const direction = isHorizontal.value ? 'Y' : 'X'
151
+ navStyle.value.transform = `translate${direction}(-${value}px)`
152
+ }
153
+ const getCurrentScrollOffset = () => {
154
+ if (navStyle.value.transform && isHorizontal.value) {
155
+ return Number(navStyle.value.transform.match(/translateY\(-(\d+(\.\d+)*)px\)/)[1])
156
+ } else if (navStyle.value.transform) {
157
+ return Number(navStyle.value.transform.match(/translateX\(-(\d+(\.\d+)*)px\)/)[1])
158
+ }
159
+ return 0
160
+ }
161
+ const getTabIsClosable = tab => {
162
+ return props.closable && !tab.disabled && !tab.unClosed
163
+ }
164
+ const getTabItemStyle = (tab, tabIndex) => {
165
+ let itemStyle = {}
166
+ if (isHorizontal.value) {
167
+ itemStyle.marginTop = tabIndex ? `${props.gutter}px` : 0
168
+ } else {
169
+ itemStyle.marginLeft = tabIndex ? `${props.gutter}px` : 0
170
+ }
171
+ return itemStyle
172
+ }
173
+ const handleResize = e => {
174
+ const navOffset = isHorizontal.value ? navRef.value.offsetHeight : navRef.value.offsetWidth
175
+ const containerOffset = isHorizontal.value ? navScrollRef.value.offsetHeight : navScrollRef.value.offsetWidth
176
+ const currentOffset = getCurrentScrollOffset()
177
+ if (containerOffset < navOffset) {
178
+ scrollable.value = true
179
+ if (navOffset - currentOffset < containerOffset) {
180
+ setOffset(navOffset - containerOffset)
181
+ }
182
+ } else {
183
+ scrollable.value = false
184
+ if (currentOffset > 0) {
185
+ setOffset(0)
186
+ }
187
+ }
188
+ }
189
+ const handleScroll = e => {
190
+ e.preventDefault()
191
+ e.stopPropagation()
192
+ let delta = 0
193
+ if (e.type === 'DOMMouseScroll' || e.type === 'mousewheel') {
194
+ delta = e.wheelDelta ? e.wheelDelta : -(e.detail || 0) * 40
195
+ }
196
+ if (delta > 0) {
197
+ scrollPrev()
198
+ } else {
199
+ scrollNext()
200
+ }
201
+ }
202
+ const handleChange = tab => {
203
+ if (!tab || tab.disabled) return
204
+ activeKey.value = tab[props.labelKey]
205
+ emit('change', tab)
206
+ if (!scrollable.value) return
207
+ const navOffset = isHorizontal.value ? navRef.value.offsetHeight : navRef.value.offsetWidth
208
+ const containerOffset = isHorizontal.value ? navScrollRef.value.offsetHeight : navScrollRef.value.offsetWidth
209
+ const prevTabs = navRef.value.querySelectorAll('.sh-tab-item')
210
+ const tabIndex = tabList.value.findIndex(tb => tb[props.labelKey] === tab[props.labelKey])
211
+ const offsetName = isHorizontal.value ? 'offsetHeight' : 'offsetWidth'
212
+ let barOffset = 0
213
+ if (tabIndex > 0) {
214
+ let offset = 0
215
+ for (let i = 0; i < tabIndex; i++) {
216
+ offset += parseFloat(prevTabs[i][offsetName]) + props.gutter
217
+ }
218
+ barOffset = offset
219
+ }
220
+ let newOffset = navOffset - barOffset < containerOffset ? navOffset - containerOffset : barOffset
221
+ setOffset(newOffset)
222
+ }
223
+ const handleClose = tab => {
224
+ closedList.value.push(tab[props.labelKey])
225
+ if (tab[props.labelKey] === activeKey.value) {
226
+ let isNotDisabledList = tabList.value.filter(tab => !tab.disabled)
227
+ activeKey.value = isNotDisabledList.length ? isNotDisabledList[0][props.labelKey] : ''
228
+ }
229
+ emit('close', tab)
230
+ }
231
+
232
+ watch(
233
+ () => props.modelValue,
234
+ value => {
235
+ activeKey.value = value
236
+ }
237
+ )
238
+ watch(
239
+ () => activeKey.value,
240
+ value => {
241
+ emit('update:modelValue', value)
242
+ }
243
+ )
244
+ watch(
245
+ () => props.options,
246
+ value => {
247
+ closedList.value = []
248
+ }
249
+ )
250
+
251
+ return {
252
+ navRef,
253
+ navWrapRef,
254
+ navScrollRef,
255
+ contentWrapRef,
256
+ slots,
257
+ isHorizontal,
258
+ scrollable,
259
+ navStyle,
260
+ tabClass,
261
+ tabList,
262
+ activeKey,
263
+ hasContent,
264
+ contentStyle,
265
+ scrollPrev,
266
+ scrollNext,
267
+ handleScroll,
268
+ handleResize,
269
+ handleChange,
270
+ handleClose,
271
+ getTabIsClosable,
272
+ getTabItemStyle
273
+ }
274
+ }
275
+ })
276
+ </script>
277
+
278
+ <style scoped lang="scss">
279
+ .sh-tabs {
280
+ width: 100%;
281
+ position: relative;
282
+ display: flex;
283
+ .sh-tab-close {
284
+ display: inline-flex;
285
+ align-items: center;
286
+ padding: 0 1px;
287
+ &:hover {
288
+ background-color: rgba(0, 0, 0, 0.1);
289
+ }
290
+ }
291
+ &-vertical {
292
+ width: 100%;
293
+ flex-wrap: wrap;
294
+ flex-direction: column;
295
+ &.sh-tabs-reverse {
296
+ flex-direction: column-reverse;
297
+ }
298
+ .sh-tabs-nav {
299
+ width: 100%;
300
+ }
301
+ }
302
+ &-horizontal {
303
+ flex-wrap: nowrap;
304
+ flex-direction: row;
305
+ .sh-tabs-nav {
306
+ width: auto;
307
+ }
308
+ .sh-tabs-nav,
309
+ .sh-tabs-nav-wrap,
310
+ .sh-tabs-nav-inner {
311
+ flex-direction: column;
312
+ }
313
+ .sh-tabs-nav-prev,
314
+ .sh-tabs-nav-next {
315
+ width: 100%;
316
+ height: 26px;
317
+ }
318
+ &.sh-tabs-reverse {
319
+ flex-direction: row-reverse;
320
+ }
321
+ }
322
+ &-nav {
323
+ display: flex;
324
+ &-slot {
325
+ display: inline-flex;
326
+ align-items: center;
327
+ }
328
+ &-wrap {
329
+ flex: 1;
330
+ display: flex;
331
+ align-items: stretch;
332
+ overflow: hidden;
333
+ }
334
+ &-prev,
335
+ &-next {
336
+ width: 30px;
337
+ height: 100%;
338
+ display: inline-flex;
339
+ align-items: center;
340
+ justify-content: center;
341
+ box-shadow: 0 1px 5px rgba(0, 0, 0, 0.3);
342
+ position: relative;
343
+ z-index: 10;
344
+ &:hover {
345
+ color: var(--vxe-primary-color);
346
+ }
347
+ &.sh-tabs-nav-btn-disabled {
348
+ }
349
+ }
350
+ &-scroll {
351
+ flex: 1;
352
+ overflow: hidden;
353
+ }
354
+ &-inner {
355
+ display: flex;
356
+ width: fit-content;
357
+ white-space: nowrap;
358
+ transition: transform 0.2s ease-in-out;
359
+ .sh-tab-item {
360
+ display: inline-flex;
361
+ align-items: stretch;
362
+ cursor: pointer;
363
+ line-height: 20px;
364
+ box-sizing: border-box;
365
+ border-bottom: 2px solid transparent;
366
+ &:hover {
367
+ color: var(--vxe-primary-color);
368
+ }
369
+ &.sh-tab-item-active {
370
+ border-bottom: 2px solid var(--vxe-primary-darken-color);
371
+ color: var(--vxe-primary-color) !important;
372
+ }
373
+ &.sh-tab-item-disabled {
374
+ cursor: not-allowed;
375
+ color: var(--vxe-input-disabled-color) !important;
376
+ }
377
+ .sh-tab-icon {
378
+ display: inline-flex;
379
+ align-items: center;
380
+ padding: 0 2px 0 5px;
381
+ }
382
+ .sh-tab-label {
383
+ flex: 1;
384
+ padding: 5px;
385
+ }
386
+ }
387
+ }
388
+ }
389
+ &.sh-tabs-card {
390
+ .sh-tabs-nav-prev,
391
+ .sh-tabs-nav-next {
392
+ border: 1px solid var(--vxe-table-border-color);
393
+ }
394
+ .sh-tabs-nav-inner {
395
+ .sh-tab-item {
396
+ border: 1px solid var(--vxe-table-border-color);
397
+ }
398
+ .sh-tab-item-active {
399
+ background-color: var(--vxe-primary-color);
400
+ border-color: var(--vxe-primary-color);
401
+ color: #fff !important;
402
+ }
403
+ }
404
+ }
405
+ &-content {
406
+ display: flex;
407
+ overflow-x: hidden;
408
+ width: 100%;
409
+ &-wrap {
410
+ flex: 1;
411
+ display: flex;
412
+ white-space: nowrap;
413
+ width: 100%;
414
+ &.sh-tabs-content-animated {
415
+ transition: transform 0.2s ease-in-out;
416
+ }
417
+ }
418
+ &-item {
419
+ display: inline-block;
420
+ min-width: 100%;
421
+ vertical-align: top;
422
+ position: relative;
423
+ }
424
+ }
425
+ }
426
+ </style>