@studio-west/component-sw 0.11.9 → 0.11.11
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/README.md +5 -5
- package/dist/SwButton-yS_tKW9w.js +4 -0
- package/dist/{SwButton-jKDKwFV9.js → SwButton.vue_vue_type_script_setup_true_lang-aODPwFa6.js} +11 -11
- package/dist/{SwDatePicker-CJjKSM8k.js → SwDatePicker-CpmdOhuc.js} +1 -1
- package/dist/{SwDropdownItem-CtlMVgsX.js → SwDropdownItem-BE6ZRWT1.js} +1 -1
- package/dist/{SwGide-DbSSyZ-y.js → SwGide-_a5-3g_f.js} +2 -2
- package/dist/SwInput-CbNd7Vin.js +90 -0
- package/dist/{SwMessage-CovKkpf6.js → SwMessage-DdUbYQet.js} +6 -6
- package/dist/SwSection-CQe2kE0O.js +34 -0
- package/dist/SwSelect-BxbCfof-.js +1883 -0
- package/dist/{SwSlider-YncjYKPw.js → SwSlider-jWTzzPZg.js} +1 -1
- package/dist/SwSwitch-DeMdyD0-.js +47 -0
- package/dist/index-C3iiqwEz.js +188 -0
- package/dist/index.cjs +6 -1
- package/dist/index.js +1 -1
- package/package.json +6 -2
- package/src/Alert.ts +65 -0
- package/src/components/SwAlert.vue +70 -0
- package/src/components/SwButton.vue +50 -0
- package/src/components/SwButtonGroup.vue +67 -0
- package/src/components/SwCollapse.vue +36 -0
- package/src/components/SwDatePicker.vue +375 -0
- package/src/components/SwDropdown.vue +202 -0
- package/src/components/SwDropdownItem.vue +26 -0
- package/src/components/SwDropdownNew.vue +175 -0
- package/src/components/SwFormItem.vue +21 -0
- package/src/components/SwGide.vue +128 -0
- package/src/components/SwIcon.vue +16 -0
- package/src/components/SwInput.vue +100 -0
- package/src/components/SwMessage.vue +53 -0
- package/src/components/SwSection.vue +17 -0
- package/src/components/SwSelect.vue +151 -0
- package/src/components/SwSkeleton.vue +13 -0
- package/src/components/SwSkeletonItem.vue +27 -0
- package/src/components/SwSlider.vue +281 -0
- package/src/components/SwSwitch.vue +51 -0
- package/src/components/SwTable.vue +239 -0
- package/src/components/SwTableColumn.vue +25 -0
- package/src/components/SwTabs.vue +41 -0
- package/src/components/SwTabsPane.vue +44 -0
- package/src/index.ts +43 -0
- package/src/utils/index.ts +149 -0
- package/types/components.d.ts +72 -0
- package/types/index.d.ts +82 -70
- package/dist/SwInput-DCV1rrWa.js +0 -89
- package/dist/SwSection-D8ooQ21I.js +0 -37
- package/dist/SwSelect-C2RKinez.js +0 -72
- package/dist/SwSwitch-6rl1IT4p.js +0 -47
- package/dist/index-B5koqczP.js +0 -190
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<slot v-if="props.visual"></slot>
|
|
3
|
+
<div v-else class="sw-skeleton" :class="props.class"><slot name="skeleton" ><sw-skeleton-item animate size="small"/></slot></div>
|
|
4
|
+
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup>
|
|
8
|
+
import SwSkeletonItem from "@/components/SwSkeletonItem.vue"
|
|
9
|
+
const props = defineProps({
|
|
10
|
+
class: {type:String, default: ''},
|
|
11
|
+
visual: {type:Boolean, default: false}
|
|
12
|
+
})
|
|
13
|
+
</script>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="c" :style="s"><template v-if="props.animate"><div /></template></div>
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup>
|
|
6
|
+
import {computed} from "vue";
|
|
7
|
+
|
|
8
|
+
const props = defineProps({
|
|
9
|
+
width: {type: String, default: '100'},
|
|
10
|
+
class: {type:String, default: ''},
|
|
11
|
+
animate: {type:Boolean, default: false},
|
|
12
|
+
size: {type:String, default: ''}
|
|
13
|
+
})
|
|
14
|
+
const c = computed(() =>{
|
|
15
|
+
let s = ['sw-skeleton-item']
|
|
16
|
+
if(props.size.length > 0) s.push('sw-' + props.size)
|
|
17
|
+
if(props.animate) s.push('sw-animate')
|
|
18
|
+
if(props.class.length > 0) s.push(props.class)
|
|
19
|
+
return s
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
const s = computed(() =>{
|
|
23
|
+
return {
|
|
24
|
+
width: props.width + '%'
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
</script>
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="style">
|
|
3
|
+
<div class="track"
|
|
4
|
+
@click="onTrackClick"
|
|
5
|
+
@mousedown.prevent="(e) => onStartDrag(e, 0)"
|
|
6
|
+
@touchstart.prevent="(e) => onStartDrag(e.touches[0], 0)">
|
|
7
|
+
<div class="fill" :style="fillStyle"></div>
|
|
8
|
+
</div>
|
|
9
|
+
<!-- Multiple thumbs -->
|
|
10
|
+
<div v-for="(value, index) in values"
|
|
11
|
+
class="thumb"
|
|
12
|
+
:key="index"
|
|
13
|
+
:style="thumbStyles[index]"
|
|
14
|
+
@mouseenter="showTooltip(index)"
|
|
15
|
+
@mouseleave="hideTooltip(index)"
|
|
16
|
+
@focus="showTooltip(index)"
|
|
17
|
+
@blur="hideTooltip(index)"
|
|
18
|
+
@mousedown.prevent="(e) => onStartDrag(e, index)"
|
|
19
|
+
@touchstart.prevent="(e) => onStartDrag(e.touches[0], index)"
|
|
20
|
+
>
|
|
21
|
+
<slot :index="index" :value="value"></slot>
|
|
22
|
+
<div
|
|
23
|
+
v-if="tooltipVisible[index] && showValue"
|
|
24
|
+
class="tooltip"
|
|
25
|
+
:class="{ 'tooltip-vertical': props.vertical }"
|
|
26
|
+
:style="tooltipStyle(index)"
|
|
27
|
+
>
|
|
28
|
+
{{ value }}
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<script setup>
|
|
35
|
+
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
36
|
+
import { calculateTooltipPosition } from '@/utils/index.js'
|
|
37
|
+
|
|
38
|
+
const model = defineModel({type: [Number, Array], required: true})
|
|
39
|
+
const props = defineProps({
|
|
40
|
+
class: { type: String, default: ''},
|
|
41
|
+
size: {type:String, default: ''},
|
|
42
|
+
min: {type: Number, default: 0},
|
|
43
|
+
max: {type: Number, default: 100},
|
|
44
|
+
step: {type: Number, default: 1},
|
|
45
|
+
vertical: {type: Boolean, default: false},
|
|
46
|
+
showValue: {type: Boolean, default: false}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const tooltipVisible = ref([])
|
|
50
|
+
const tooltipPositions = ref([])
|
|
51
|
+
|
|
52
|
+
const style = computed(() =>{
|
|
53
|
+
let s = ['sw-slider']
|
|
54
|
+
if(props.size.length > 0) s.push('sw-' + props.size)
|
|
55
|
+
if(props.class.length > 0) s.push(props.class)
|
|
56
|
+
if(props.vertical) s.push('vertical')
|
|
57
|
+
return s
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
const showTooltip = (index) => {
|
|
62
|
+
const newVisibility = [...tooltipVisible.value]
|
|
63
|
+
newVisibility[index] = true
|
|
64
|
+
tooltipVisible.value = newVisibility
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const hideTooltip = (index) => {
|
|
68
|
+
const newVisibility = [...tooltipVisible.value]
|
|
69
|
+
newVisibility[index] = false
|
|
70
|
+
tooltipVisible.value = newVisibility
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const tooltipStyle = (index) => {
|
|
74
|
+
const sliderElement = document.querySelector('.sw-slider')
|
|
75
|
+
if (sliderElement) {
|
|
76
|
+
return calculateTooltipPosition(sliderElement, props.vertical)
|
|
77
|
+
}
|
|
78
|
+
// Fallback positions
|
|
79
|
+
if (props.vertical) {
|
|
80
|
+
return { right: '100%', marginRight: '10px' }
|
|
81
|
+
} else {
|
|
82
|
+
return { bottom: '100%', marginBottom: '10px' }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Determine ifwe're dealing with an array (multiple thumbs)
|
|
87
|
+
const isArray = computed(() => Array.isArray(model.value))
|
|
88
|
+
|
|
89
|
+
// Get values as array for uniform treatment
|
|
90
|
+
const values = computed({
|
|
91
|
+
get() {
|
|
92
|
+
return isArray.value ? [...model.value] : [model.value]
|
|
93
|
+
},
|
|
94
|
+
set(newValues) {
|
|
95
|
+
if (isArray.value) {
|
|
96
|
+
// Sort values for display purposes
|
|
97
|
+
model.value = [...newValues].sort((a, b) => a - b)
|
|
98
|
+
} else {
|
|
99
|
+
model.value = newValues[0]
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// Calculate percentages for each thumb
|
|
105
|
+
const percentages = computed(() => {
|
|
106
|
+
const range= props.max - props.min
|
|
107
|
+
return values.value.map(value => {
|
|
108
|
+
return range === 0 ? 0 : ((value - props.min) / range) * 100
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// Style for the fill between first and last thumbs
|
|
113
|
+
const fillStyle = computed(() => {
|
|
114
|
+
if (props.vertical) {
|
|
115
|
+
if(isArray.value) {
|
|
116
|
+
const firstPercent = percentages.value[0] || 0
|
|
117
|
+
const lastPercent = percentages.value[percentages.value.length - 1] || 0
|
|
118
|
+
const minPercent = Math.min(firstPercent, lastPercent)
|
|
119
|
+
const maxPercent = Math.max(firstPercent, lastPercent)
|
|
120
|
+
return {
|
|
121
|
+
height: `${maxPercent - minPercent}%`,
|
|
122
|
+
top: `${minPercent}%`,
|
|
123
|
+
left: '0',
|
|
124
|
+
width: '100%'
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
// For single value, start from 0
|
|
128
|
+
const percent = percentages.value[0] || 0
|
|
129
|
+
return {
|
|
130
|
+
height: `${percent}%`,
|
|
131
|
+
top: '0',
|
|
132
|
+
left: '0',
|
|
133
|
+
width: '100%'
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
if (isArray.value) {
|
|
138
|
+
const firstPercent = percentages.value[0] || 0
|
|
139
|
+
const lastPercent= percentages.value[percentages.value.length - 1] || 0
|
|
140
|
+
const minPercent = Math.min(firstPercent, lastPercent)
|
|
141
|
+
const maxPercent = Math.max(firstPercent, lastPercent)
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
width: `${maxPercent - minPercent}%`,
|
|
145
|
+
left: `${minPercent}%`,
|
|
146
|
+
top: '0',
|
|
147
|
+
height: '100%'
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
// For single value, start from 0
|
|
151
|
+
const percent = percentages.value[0] || 0
|
|
152
|
+
return {
|
|
153
|
+
width: `${percent}%`,
|
|
154
|
+
left: '0',
|
|
155
|
+
top: '0',
|
|
156
|
+
height: '100%'
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
// Stylesfor each thumb
|
|
163
|
+
const thumbStyles = computed(() => {
|
|
164
|
+
return percentages.value.map((percent, index) => {
|
|
165
|
+
return props.vertical
|
|
166
|
+
? { top: `${percent}%`, zIndex: values.value.length - index }
|
|
167
|
+
: { left: `${percent}%`, zIndex: values.value.length - index }
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
// === Track click handler ===
|
|
172
|
+
function onTrackClick(e) {
|
|
173
|
+
// Only for single thumb sliders
|
|
174
|
+
if (isArray.value) return
|
|
175
|
+
|
|
176
|
+
const slider = e.currentTarget.closest('.sw-slider')
|
|
177
|
+
if (!slider) return
|
|
178
|
+
|
|
179
|
+
const rect = slider.getBoundingClientRect()
|
|
180
|
+
const ratio = props.vertical
|
|
181
|
+
? (e.clientY - rect.top) / rect.height
|
|
182
|
+
: (e.clientX - rect.left) / rect.width
|
|
183
|
+
|
|
184
|
+
const clampedRatio = Math.max(0, Math.min(1, ratio))
|
|
185
|
+
const range = props.max - props.min
|
|
186
|
+
let value = props.min + clampedRatio * range
|
|
187
|
+
|
|
188
|
+
// Apply step
|
|
189
|
+
const steps = Math.round((value - props.min) / props.step)
|
|
190
|
+
value = props.min + steps * props.step
|
|
191
|
+
value = Math.max(props.min, Math.min(props.max, value))
|
|
192
|
+
|
|
193
|
+
// Update the value
|
|
194
|
+
values.value = [value]
|
|
195
|
+
|
|
196
|
+
// Update tooltip position
|
|
197
|
+
setTimeout(() => {
|
|
198
|
+
const newPositions = [...tooltipPositions.value]
|
|
199
|
+
newPositions[0] = tooltipStyle(0)
|
|
200
|
+
tooltipPositions.value = newPositions
|
|
201
|
+
}, 0)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// === Dragging ===
|
|
205
|
+
const isDragging = ref(false)
|
|
206
|
+
const draggingIndex = ref(null)
|
|
207
|
+
const sliderRect = ref(null)
|
|
208
|
+
|
|
209
|
+
function onStartDrag(e, index) {
|
|
210
|
+
// For single thumb sliders, also handle track clicks
|
|
211
|
+
// if (!isArray.value && index !== 0) return
|
|
212
|
+
|
|
213
|
+
const slider = e.target.closest('.sw-slider')
|
|
214
|
+
if (!slider) return
|
|
215
|
+
sliderRect.value = slider.getBoundingClientRect()
|
|
216
|
+
draggingIndex.value = index
|
|
217
|
+
isDragging.value = true
|
|
218
|
+
updateValueFromClient(e.clientX || e.touches?.[0]?.clientX, e.clientY|| e.touches?.[0]?.clientY)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function onDrag(e) {
|
|
222
|
+
if (!isDragging.value) return
|
|
223
|
+
e.preventDefault?.()
|
|
224
|
+
const clientX = e.clientX || e.touches?.[0]?.clientX
|
|
225
|
+
const clientY = e.clientY ||e.touches?.[0]?.clientY
|
|
226
|
+
updateValueFromClient(clientX, clientY)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function stopDragging() {
|
|
230
|
+
isDragging.value = false
|
|
231
|
+
draggingIndex.value = null
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function updateValueFromClient(clientX, clientY) {
|
|
235
|
+
if (!sliderRect.value || draggingIndex.value === null) return
|
|
236
|
+
|
|
237
|
+
const rect = sliderRect.value
|
|
238
|
+
const ratio = props.vertical
|
|
239
|
+
? (clientY - rect.top) / rect.height
|
|
240
|
+
: (clientX - rect.left) / rect.width
|
|
241
|
+
|
|
242
|
+
const clampedRatio = Math.max(0, Math.min(1, ratio))
|
|
243
|
+
const range= props.max - props.min
|
|
244
|
+
let value = props.min + clampedRatio * range
|
|
245
|
+
|
|
246
|
+
// Apply step
|
|
247
|
+
const steps = Math.round((value - props.min) / props.step)
|
|
248
|
+
value = props.min + steps * props.step
|
|
249
|
+
value = Math.max(props.min, Math.min(props.max,value))
|
|
250
|
+
|
|
251
|
+
// Update the specific thumb value
|
|
252
|
+
const newValues = [...values.value]
|
|
253
|
+
newValues[draggingIndex.value] = value
|
|
254
|
+
values.value = newValues
|
|
255
|
+
|
|
256
|
+
// Update tooltip position
|
|
257
|
+
setTimeout(() => {
|
|
258
|
+
const newPositions = [...tooltipPositions.value]
|
|
259
|
+
newPositions[draggingIndex.value] = tooltipStyle(draggingIndex.value)
|
|
260
|
+
tooltipPositions.value = newPositions
|
|
261
|
+
}, 0)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Global listeners
|
|
265
|
+
onMounted(() => {
|
|
266
|
+
window.addEventListener('mousemove', onDrag)
|
|
267
|
+
window.addEventListener('mouseup', stopDragging)
|
|
268
|
+
window.addEventListener('touchmove', onDrag, { passive: false })
|
|
269
|
+
window.addEventListener('touchend', stopDragging)
|
|
270
|
+
|
|
271
|
+
// Initialize tooltip visibility array
|
|
272
|
+
tooltipVisible.value = new Array(values.value.length).fill(false)
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
onUnmounted(() => {
|
|
276
|
+
window.removeEventListener('mousemove', onDrag)
|
|
277
|
+
window.removeEventListener('mouseup', stopDragging)
|
|
278
|
+
window.removeEventListener('touchmove', onDrag)
|
|
279
|
+
window.removeEventListener('touchend', stopDragging)
|
|
280
|
+
})
|
|
281
|
+
</script>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="style">
|
|
3
|
+
<!--input type="checkbox" :name="props.name" class="sw-control" :id="props.id" v-model="model"-->
|
|
4
|
+
<input
|
|
5
|
+
type="checkbox"
|
|
6
|
+
:name="props.name"
|
|
7
|
+
class="sw-control"
|
|
8
|
+
:id="props.id"
|
|
9
|
+
:checked="model"
|
|
10
|
+
@change="handleChange"
|
|
11
|
+
>
|
|
12
|
+
<label :for="props.id" class="sw-label" :data-onlabel="props.on" :data-offlabel="props.off" />
|
|
13
|
+
<slot></slot>
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup lang="ts">
|
|
18
|
+
import { computed } from "vue"
|
|
19
|
+
|
|
20
|
+
// Define prop types
|
|
21
|
+
interface Props {
|
|
22
|
+
id?: string
|
|
23
|
+
class?: string
|
|
24
|
+
name?: string
|
|
25
|
+
on?: string
|
|
26
|
+
off?: string
|
|
27
|
+
size?: string
|
|
28
|
+
checkbox?: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const model = defineModel<boolean>()
|
|
32
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
33
|
+
id: 'checkbox',
|
|
34
|
+
class: '',
|
|
35
|
+
name: '',
|
|
36
|
+
on: '',
|
|
37
|
+
off: '',
|
|
38
|
+
size: '',
|
|
39
|
+
checkbox: false
|
|
40
|
+
})
|
|
41
|
+
const style = computed(() =>{
|
|
42
|
+
let s = ['sw-switch']
|
|
43
|
+
if(props.size.length > 0) s.push('sw-' + props.size)
|
|
44
|
+
if(props.checkbox) s.push('sw-checkbox')
|
|
45
|
+
if(props.class.length > 0) s.push(props.class)
|
|
46
|
+
return s
|
|
47
|
+
})
|
|
48
|
+
const handleChange = (event) => {
|
|
49
|
+
model.value = event.target.checked
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<table class="sw-table">
|
|
3
|
+
<thead>
|
|
4
|
+
<tr v-for="(row, rowIndex) in headerRows" :key="rowIndex">
|
|
5
|
+
<th
|
|
6
|
+
v-for="cell in row"
|
|
7
|
+
:key="cell.key"
|
|
8
|
+
:colspan="cell.colspan"
|
|
9
|
+
:rowspan="cell.rowspan"
|
|
10
|
+
:style="{ minWidth: cell.width, minHeight: cell.height }"
|
|
11
|
+
>
|
|
12
|
+
{{ cell.label }}
|
|
13
|
+
</th>
|
|
14
|
+
</tr>
|
|
15
|
+
</thead>
|
|
16
|
+
<!--tbody>
|
|
17
|
+
<tr v-for="(row, index) in data" :key="index">
|
|
18
|
+
<td v-for="column in flatColumns" :key="column.prop">
|
|
19
|
+
<slot
|
|
20
|
+
:name="column.prop"
|
|
21
|
+
:row="row"
|
|
22
|
+
:$index="index"
|
|
23
|
+
>
|
|
24
|
+
{{ row[column.prop] }}
|
|
25
|
+
</slot>
|
|
26
|
+
</td>
|
|
27
|
+
</tr>
|
|
28
|
+
</tbody-->
|
|
29
|
+
<tbody>
|
|
30
|
+
<tr v-for="(row, rowIndex) in processedTableData" :key="rowIndex">
|
|
31
|
+
<td
|
|
32
|
+
v-for="cell in row"
|
|
33
|
+
:key="cell.key"
|
|
34
|
+
:colspan="cell.colspan"
|
|
35
|
+
:rowspan="cell.rowspan"
|
|
36
|
+
:style="cell.style"
|
|
37
|
+
>
|
|
38
|
+
<slot
|
|
39
|
+
:name="cell.prop"
|
|
40
|
+
:row="cell.originalRow"
|
|
41
|
+
:$index="cell.rowIndex"
|
|
42
|
+
>
|
|
43
|
+
{{ cell.label }}
|
|
44
|
+
</slot>
|
|
45
|
+
</td>
|
|
46
|
+
</tr>
|
|
47
|
+
</tbody>
|
|
48
|
+
</table>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<script setup>
|
|
52
|
+
import { ref, useSlots, onMounted, computed } from 'vue'
|
|
53
|
+
import SwTableColumn from './SwTableColumn.vue'
|
|
54
|
+
|
|
55
|
+
const props = defineProps({
|
|
56
|
+
data: {
|
|
57
|
+
type: Array,
|
|
58
|
+
required: true
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const slots = useSlots()
|
|
63
|
+
const columnTree = ref([])
|
|
64
|
+
// Функция для построения дерева колонок
|
|
65
|
+
function buildColumnTree(vnodes) {
|
|
66
|
+
const result = []
|
|
67
|
+
|
|
68
|
+
for (const vnode of vnodes) {
|
|
69
|
+
if (vnode.type === SwTableColumn) {
|
|
70
|
+
const column = {
|
|
71
|
+
...vnode.props,
|
|
72
|
+
children: []
|
|
73
|
+
}
|
|
74
|
+
const children = vnode.children?.default?.()
|
|
75
|
+
if (children && children.length > 0) {
|
|
76
|
+
column.children = buildColumnTree(children)
|
|
77
|
+
}
|
|
78
|
+
result.push(column)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return result
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Получение максимальной глубины дерева
|
|
85
|
+
function getMaxDepth(columns) {
|
|
86
|
+
if (!columns || columns.length === 0) return 1
|
|
87
|
+
let maxDepth = 1
|
|
88
|
+
for (const column of columns) {
|
|
89
|
+
if (column.children && column.children.length > 0) {
|
|
90
|
+
const childDepth = getMaxDepth(column.children)
|
|
91
|
+
maxDepth = Math.max(maxDepth, 1 + childDepth)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return maxDepth
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Преобразование дерева в строки для thead
|
|
98
|
+
function buildHeaderRows(columns, depth) {
|
|
99
|
+
const rows = Array.from({ length: depth }, () => [])
|
|
100
|
+
|
|
101
|
+
function processColumns(cols, rowIndex, parentKey = '') {
|
|
102
|
+
for (let i = 0; i < cols.length; i++) {
|
|
103
|
+
const col = cols[i]
|
|
104
|
+
const key = parentKey ? `${parentKey}-${col.prop || i}` : (col.prop || i)
|
|
105
|
+
|
|
106
|
+
if (col.children && col.children.length > 0) {
|
|
107
|
+
// Это родительская колонка
|
|
108
|
+
const leafCount = countLeafColumns(col.children)
|
|
109
|
+
rows[rowIndex].push({
|
|
110
|
+
key,
|
|
111
|
+
label: col.label,
|
|
112
|
+
colspan: leafCount,
|
|
113
|
+
rowspan: 1,
|
|
114
|
+
width: col.width,
|
|
115
|
+
height: col.height
|
|
116
|
+
})
|
|
117
|
+
processColumns(col.children, rowIndex + 1, key)
|
|
118
|
+
} else {
|
|
119
|
+
// Это конечная колонка
|
|
120
|
+
rows[rowIndex].push({
|
|
121
|
+
key: col.prop,
|
|
122
|
+
label: col.label,
|
|
123
|
+
colspan: 1,
|
|
124
|
+
rowspan: depth - rowIndex,
|
|
125
|
+
width: col.width,
|
|
126
|
+
height: col.height,
|
|
127
|
+
prop: col.prop
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
processColumns(columns, 0)
|
|
133
|
+
return rows
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Подсчет количества листовых колонок
|
|
137
|
+
function countLeafColumns(columns) {
|
|
138
|
+
let count = 0
|
|
139
|
+
|
|
140
|
+
function traverse(cols) {
|
|
141
|
+
for (const col of cols) {
|
|
142
|
+
if (col.children && col.children.length > 0) traverse(col.children)
|
|
143
|
+
else count++
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
traverse(columns)
|
|
147
|
+
return count
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Извлечение плоского списка конечных колонок
|
|
151
|
+
function extractLeafColumns(columns) {
|
|
152
|
+
const result = []
|
|
153
|
+
|
|
154
|
+
function traverse(cols) {
|
|
155
|
+
for (const col of cols) {
|
|
156
|
+
if (col.children && col.children.length > 0) traverse(col.children)
|
|
157
|
+
else result.push(col)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
traverse(columns)
|
|
161
|
+
return result
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Обработка данных для tbody с поддержкой colspan
|
|
165
|
+
function processBodyData(data, columns) {
|
|
166
|
+
const processedRows = []
|
|
167
|
+
const cellMap = new Map() // Для отслеживания объединенных ячеек
|
|
168
|
+
data.forEach((row, rowIndex) => {
|
|
169
|
+
const processedRow = []
|
|
170
|
+
|
|
171
|
+
columns.forEach((column, colIndex) => {
|
|
172
|
+
const key = `${rowIndex}-${column.prop}`
|
|
173
|
+
|
|
174
|
+
// Проверяем, не занята ли эта ячейка из-за rowspan/colspan
|
|
175
|
+
if (cellMap.has(key)) {
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const cellData = row[column.prop]
|
|
180
|
+
let cellValue = cellData
|
|
181
|
+
let colspan = 1
|
|
182
|
+
let rowspan = 1
|
|
183
|
+
let style = {}
|
|
184
|
+
|
|
185
|
+
// Проверяем, есть ли у значения свойства rowspan/colspan
|
|
186
|
+
if (cellData && typeof cellData === 'object' && cellData !== null) {
|
|
187
|
+
cellValue = cellData.label || cellData.value || ''
|
|
188
|
+
colspan = cellData.colspan || 1
|
|
189
|
+
rowspan = cellData.rowspan || 1
|
|
190
|
+
style = cellData.style || {}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const cell = {
|
|
194
|
+
key,
|
|
195
|
+
label: cellValue,
|
|
196
|
+
prop: column.prop,
|
|
197
|
+
colspan,
|
|
198
|
+
rowspan,
|
|
199
|
+
style,
|
|
200
|
+
originalRow: row,
|
|
201
|
+
rowIndex
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
processedRow.push(cell)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
// Помечаем объединенные ячейки
|
|
208
|
+
for (let r = 0; r < rowspan; r++) {
|
|
209
|
+
for (let c = 0; c < colspan; c++) {
|
|
210
|
+
if (r === 0 && c === 0) continue // Основная ячейка
|
|
211
|
+
const cellKey = `${rowIndex + r}-${columns[colIndex + c]?.prop}`
|
|
212
|
+
if (cellKey) {
|
|
213
|
+
cellMap.set(cellKey, true)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
// Добавляем строку только если в ней есть ячейки
|
|
220
|
+
if (processedRow.length > 0) {
|
|
221
|
+
processedRows.push(processedRow)
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
return processedRows
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Сбор колонок из слотов
|
|
229
|
+
onMounted(() => {
|
|
230
|
+
const columnVNodes = slots.default?.() || []
|
|
231
|
+
// columns.value = extractLeafColumns(columnVNodes)
|
|
232
|
+
columnTree.value = buildColumnTree(columnVNodes)
|
|
233
|
+
})
|
|
234
|
+
// Вычисляемые свойства
|
|
235
|
+
const maxDepth = computed(() => getMaxDepth(columnTree.value))
|
|
236
|
+
const headerRows = computed(() => buildHeaderRows(columnTree.value, maxDepth.value))
|
|
237
|
+
const flatColumns = computed(() => extractLeafColumns(columnTree.value))
|
|
238
|
+
const processedTableData = computed(() => processBodyData(props.data, flatColumns.value))
|
|
239
|
+
</script>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div style="display: none;"></div>
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup>
|
|
6
|
+
|
|
7
|
+
defineProps({
|
|
8
|
+
prop: {
|
|
9
|
+
type: String,
|
|
10
|
+
required: true
|
|
11
|
+
},
|
|
12
|
+
label: {
|
|
13
|
+
type: String,
|
|
14
|
+
default: ''
|
|
15
|
+
},
|
|
16
|
+
width: {
|
|
17
|
+
type: String,
|
|
18
|
+
default: 'auto'
|
|
19
|
+
},
|
|
20
|
+
height: {
|
|
21
|
+
type: String,
|
|
22
|
+
default: 'auto'
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
</script>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import {ref, provide} from 'vue'
|
|
3
|
+
const activeTab = ref(0);
|
|
4
|
+
const tabs = ref([]);
|
|
5
|
+
|
|
6
|
+
const props = defineProps({
|
|
7
|
+
class: {type:String, default: ''}
|
|
8
|
+
})
|
|
9
|
+
const model = defineModel()
|
|
10
|
+
const emit = defineEmits(['tab-click'])
|
|
11
|
+
|
|
12
|
+
function registerTab(tab) {
|
|
13
|
+
tabs.value.push(tab)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function unregisterTab(tab) {
|
|
17
|
+
tabs.value = tabs.value.filter(t => t !== tab)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
provide('registerTab', registerTab)
|
|
21
|
+
provide('unregisterTab', unregisterTab)
|
|
22
|
+
provide('activeTab', model)
|
|
23
|
+
provide('tabs', tabs)
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<div :class="'sw-tabs ' + props.class">
|
|
28
|
+
<header v-if="tabs.length !== 0">
|
|
29
|
+
<span
|
|
30
|
+
v-for="(tab, index) in tabs"
|
|
31
|
+
:key="index"
|
|
32
|
+
:class="[{ active: model === index }]"
|
|
33
|
+
@click="model = index; emit('tab-click', index)"
|
|
34
|
+
>
|
|
35
|
+
{{ tab.label || tab.title }}
|
|
36
|
+
</span>
|
|
37
|
+
</header>
|
|
38
|
+
<slot />
|
|
39
|
+
</div>
|
|
40
|
+
</template>
|
|
41
|
+
|