@veltra/desktop 1.0.8 → 1.0.10
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/dist/components/action/action-group.vue.d.ts +2 -2
- package/dist/components/action/action.vue.d.ts +3 -3
- package/dist/components/auto-complete/auto-complete.vue.d.ts +1 -1
- package/dist/components/batch-edit/batch-edit.js.map +1 -1
- package/dist/components/batch-edit/batch-edit.vue.d.ts +2 -2
- package/dist/components/batch-edit/use-edit.js.map +1 -1
- package/dist/components/button/button.js.map +1 -1
- package/dist/components/button/button.vue.d.ts +1 -1
- package/dist/components/cascade/cascade.js +15 -13
- package/dist/components/cascade/cascade.js.map +1 -1
- package/dist/components/cascade/cascade.vue.d.ts +7 -7
- package/dist/components/cascade/node.js.map +1 -1
- package/dist/components/cascade/use-check.js +3 -2
- package/dist/components/cascade/use-check.js.map +1 -1
- package/dist/components/cascade/use-select.js +3 -3
- package/dist/components/cascade/use-select.js.map +1 -1
- package/dist/components/checkbox-group/checkbox-group.js.map +1 -1
- package/dist/components/checkbox-group/checkbox-group.vue.d.ts +2 -2
- package/dist/components/date-panel/date-panel.js.map +1 -1
- package/dist/components/date-panel/date-panel.vue.d.ts +1 -1
- package/dist/components/date-picker/date-picker.js +9 -9
- package/dist/components/date-picker/date-picker.js.map +1 -1
- package/dist/components/date-picker/date-picker.vue.d.ts +1 -1
- package/dist/components/date-range-picker/date-range-picker.js +9 -9
- package/dist/components/date-range-picker/date-range-picker.js.map +1 -1
- package/dist/components/date-range-picker/date-range-picker.vue.d.ts +1 -1
- package/dist/components/drawer/drawer.js.map +1 -1
- package/dist/components/empty/empty.vue.d.ts +1 -1
- package/dist/components/expression-editor/expression-editor.js.map +1 -1
- package/dist/components/expression-editor/internal/editor-runtime/create-runtime.js.map +1 -1
- package/dist/components/expression-editor/internal/features/commands/register-command-packs.js.map +1 -1
- package/dist/components/expression-editor/internal/features/insertion/insertion-service.js.map +1 -1
- package/dist/components/expression-editor/use-decorators.js.map +1 -1
- package/dist/components/expression-editor/use-expression-drag-drop.js.map +1 -1
- package/dist/components/file-picker/file-picker.js.map +1 -1
- package/dist/components/file-picker/file-picker.vue.d.ts +1 -1
- package/dist/components/file-picker/helper.js +19 -7
- package/dist/components/file-picker/helper.js.map +1 -1
- package/dist/components/file-viewer/file-viewer.js +294 -0
- package/dist/components/file-viewer/file-viewer.js.map +1 -0
- package/dist/components/file-viewer/file-viewer.vue.d.ts +42 -0
- package/dist/components/file-viewer/helper.js +185 -0
- package/dist/components/file-viewer/helper.js.map +1 -0
- package/dist/components/file-viewer/index.d.ts +1 -0
- package/dist/components/file-viewer/previewers/docx-previewer.js +76 -0
- package/dist/components/file-viewer/previewers/docx-previewer.js.map +1 -0
- package/dist/components/file-viewer/previewers/image-previewer.js +64 -0
- package/dist/components/file-viewer/previewers/image-previewer.js.map +1 -0
- package/dist/components/file-viewer/previewers/pdf-previewer.js +90 -0
- package/dist/components/file-viewer/previewers/pdf-previewer.js.map +1 -0
- package/dist/components/file-viewer/previewers/sheet-previewer.js +175 -0
- package/dist/components/file-viewer/previewers/sheet-previewer.js.map +1 -0
- package/dist/components/file-viewer/previewers/text-previewer.js +74 -0
- package/dist/components/file-viewer/previewers/text-previewer.js.map +1 -0
- package/dist/components/file-viewer/previewers/video-previewer.js +62 -0
- package/dist/components/file-viewer/previewers/video-previewer.js.map +1 -0
- package/dist/components/file-viewer/style.d.ts +1 -0
- package/dist/components/file-viewer/style.js +4 -0
- package/dist/components/file-viewer/style2.css +445 -0
- package/dist/components/form/form-model.js +2 -1
- package/dist/components/form/form-model.js.map +1 -1
- package/dist/components/grid/grid.js.map +1 -1
- package/dist/components/grid/use-responsive.js.map +1 -1
- package/dist/components/grid-input/grid-input.vue.d.ts +1 -1
- package/dist/components/index.d.ts +12 -9
- package/dist/components/input/input.vue.d.ts +3 -3
- package/dist/components/list/list.js.map +1 -1
- package/dist/components/loading/directive.js.map +1 -1
- package/dist/components/menu/menu-item.js.map +1 -1
- package/dist/components/message/message2.js.map +1 -1
- package/dist/components/multi-select/multi-select-option.js +14 -10
- package/dist/components/multi-select/multi-select-option.js.map +1 -1
- package/dist/components/multi-select/multi-select.js +16 -19
- package/dist/components/multi-select/multi-select.js.map +1 -1
- package/dist/components/multi-select/multi-select.vue.d.ts +4 -4
- package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +7 -7
- package/dist/components/notification/index.js.map +1 -1
- package/dist/components/notification/notification.vue.d.ts +1 -1
- package/dist/components/number-input/number-input.js.map +1 -1
- package/dist/components/number-input/number-input.vue.d.ts +1 -1
- package/dist/components/number-range-input/number-range-input.vue.d.ts +1 -1
- package/dist/components/palette/color-transform.js.map +1 -1
- package/dist/components/palette/di.js.map +1 -1
- package/dist/components/palette/palette-alpha.js +7 -10
- package/dist/components/palette/palette-alpha.js.map +1 -1
- package/dist/components/palette/palette-hue.js +5 -7
- package/dist/components/palette/palette-hue.js.map +1 -1
- package/dist/components/palette/palette-sv.js +8 -10
- package/dist/components/palette/palette-sv.js.map +1 -1
- package/dist/components/palette/palette.js +22 -24
- package/dist/components/palette/palette.js.map +1 -1
- package/dist/components/password-input/password-input.vue.d.ts +1 -1
- package/dist/components/pop-confirm/pop-confirm.vue.d.ts +2 -2
- package/dist/components/progress/progress.js +1 -1
- package/dist/components/progress/progress.js.map +1 -1
- package/dist/components/progress-nodes/progress-nodes.js.map +1 -1
- package/dist/components/radio-group/radio-group.vue.d.ts +2 -2
- package/dist/components/rich-text-editor/rich-text-editor.js.map +1 -1
- package/dist/components/rich-text-editor/rich-text-editor.vue.d.ts +1 -1
- package/dist/components/rich-text-editor/toolbar.js.map +1 -1
- package/dist/components/scroll/scroll.js.map +1 -1
- package/dist/components/select/select.js +14 -12
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/select/select.vue.d.ts +3 -3
- package/dist/components/slider/slider-thumb.js +10 -13
- package/dist/components/slider/slider-thumb.js.map +1 -1
- package/dist/components/slider/slider.js +12 -15
- package/dist/components/slider/slider.js.map +1 -1
- package/dist/components/table/di.js.map +1 -1
- package/dist/components/table/node/row.d.ts +10 -1
- package/dist/components/table/node/row.js +9 -0
- package/dist/components/table/node/row.js.map +1 -1
- package/dist/components/table/table-body.js +25 -26
- package/dist/components/table/table-body.js.map +1 -1
- package/dist/components/table/table-cell.js +16 -9
- package/dist/components/table/table-cell.js.map +1 -1
- package/dist/components/table/table-row.js +77 -19
- package/dist/components/table/table-row.js.map +1 -1
- package/dist/components/table/table.js +75 -34
- package/dist/components/table/table.js.map +1 -1
- package/dist/components/table/table.vue.d.ts +2 -2
- package/dist/components/table/use-columns.js +4 -0
- package/dist/components/table/use-columns.js.map +1 -1
- package/dist/components/table/use-fixed-columns.js +27 -8
- package/dist/components/table/use-fixed-columns.js.map +1 -1
- package/dist/components/table/use-rows.js +2 -2
- package/dist/components/table/use-rows.js.map +1 -1
- package/dist/components/table/use-table.js +50 -3
- package/dist/components/table/use-table.js.map +1 -1
- package/dist/components/tabs/index.d.ts +3 -1
- package/dist/components/tabs/style2.css +170 -107
- package/dist/components/tabs/tabs-horizontal.js +133 -0
- package/dist/components/tabs/tabs-horizontal.js.map +1 -0
- package/dist/components/tabs/tabs-horizontal.vue.d.ts +40 -0
- package/dist/components/tabs/tabs-vertical.js +89 -0
- package/dist/components/tabs/tabs-vertical.js.map +1 -0
- package/dist/components/tabs/tabs-vertical.vue.d.ts +38 -0
- package/dist/components/tabs/tabs.js +75 -105
- package/dist/components/tabs/tabs.js.map +1 -1
- package/dist/components/tabs/tabs.vue.d.ts +9 -23
- package/dist/components/tabs/use-tabs-bar.js +105 -0
- package/dist/components/tabs/use-tabs-bar.js.map +1 -0
- package/dist/components/text/text.js.map +1 -1
- package/dist/components/textarea/textarea.js.map +1 -1
- package/dist/components/textarea/textarea.vue.d.ts +4 -4
- package/dist/components/theme/theme.js.map +1 -1
- package/dist/components/tip/tip.vue.d.ts +1 -1
- package/dist/components/tree/style2.css +45 -23
- package/dist/components/tree/tree-node.js +50 -35
- package/dist/components/tree/tree-node.js.map +1 -1
- package/dist/components/tree/tree-node2.js.map +1 -1
- package/dist/components/tree/tree.js +27 -20
- package/dist/components/tree/tree.js.map +1 -1
- package/dist/components/tree/tree.vue.d.ts +5 -5
- package/dist/components/tree/use-check.js +52 -14
- package/dist/components/tree/use-check.js.map +1 -1
- package/dist/components/tree/use-tree-nodes.js +51 -24
- package/dist/components/tree/use-tree-nodes.js.map +1 -1
- package/dist/components/tree-select/tree-select.vue.d.ts +7 -7
- package/dist/index.d.ts +57 -53
- package/dist/index.js +51 -48
- package/dist/types/file-viewer.d.ts +71 -0
- package/dist/types/index.d.ts +3 -2
- package/dist/types/tabs.d.ts +106 -14
- package/dist/types/tree.d.ts +6 -1
- package/package.json +32 -31
- package/src/components/cascade/cascade.vue +18 -18
- package/src/components/cascade/node.ts +4 -1
- package/src/components/cascade/use-check.ts +5 -4
- package/src/components/cascade/use-select.ts +4 -5
- package/src/components/date-picker/date-picker.vue +10 -9
- package/src/components/date-range-picker/date-range-picker.vue +12 -12
- package/src/components/file-picker/helper.ts +21 -10
- package/src/components/file-viewer/file-viewer.vue +311 -0
- package/src/components/file-viewer/helper.ts +193 -0
- package/src/components/file-viewer/index.ts +1 -0
- package/src/components/file-viewer/previewers/docx-previewer.vue +78 -0
- package/src/components/file-viewer/previewers/image-previewer.vue +64 -0
- package/src/components/file-viewer/previewers/pdf-previewer.vue +90 -0
- package/src/components/file-viewer/previewers/sheet-previewer.vue +210 -0
- package/src/components/file-viewer/previewers/text-previewer.vue +76 -0
- package/src/components/file-viewer/previewers/video-previewer.vue +72 -0
- package/src/components/file-viewer/style.scss +504 -0
- package/src/components/file-viewer/style.ts +4 -0
- package/src/components/index.ts +2 -0
- package/src/components/multi-select/multi-select-option.vue +16 -3
- package/src/components/multi-select/multi-select.vue +17 -9
- package/src/components/palette/di.ts +5 -3
- package/src/components/palette/palette-alpha.vue +7 -10
- package/src/components/palette/palette-hue.vue +5 -7
- package/src/components/palette/palette-sv.vue +12 -14
- package/src/components/palette/palette.vue +19 -21
- package/src/components/progress/progress.vue +1 -1
- package/src/components/select/select.vue +21 -19
- package/src/components/slider/slider-thumb.vue +11 -14
- package/src/components/slider/slider.vue +15 -15
- package/src/components/table/di.ts +58 -43
- package/src/components/table/node/row.ts +10 -1
- package/src/components/table/table-body.vue +28 -33
- package/src/components/table/table-cell.vue +17 -9
- package/src/components/table/table-row.tsx +83 -17
- package/src/components/table/table.vue +72 -45
- package/src/components/table/use-fixed-columns.ts +46 -12
- package/src/components/table/use-rows.ts +14 -3
- package/src/components/table/use-table.ts +27 -3
- package/src/components/tabs/index.ts +2 -0
- package/src/components/tabs/style.scss +219 -133
- package/src/components/tabs/tabs-horizontal.vue +128 -0
- package/src/components/tabs/tabs-vertical.vue +77 -0
- package/src/components/tabs/tabs.vue +56 -127
- package/src/components/tabs/use-tabs-bar.ts +119 -0
- package/src/components/tree/style.scss +74 -29
- package/src/components/tree/tree-node.vue +10 -4
- package/src/components/tree/tree.vue +39 -12
- package/src/components/tree/use-check.ts +79 -23
- package/src/components/tree/use-tree-nodes.ts +85 -34
- package/src/types/file-viewer.ts +69 -0
- package/src/types/index.ts +1 -0
- package/src/types/tabs.ts +113 -19
- package/src/types/tree.ts +6 -1
|
@@ -1,167 +1,96 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :class="[cls.b, cls.m(position!)
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
</u-icon>
|
|
36
|
-
</li>
|
|
37
|
-
</ul>
|
|
38
|
-
|
|
39
|
-
<transition name="fade" mode="out-in">
|
|
40
|
-
<KeepAlive v-if="keepAlive">
|
|
41
|
-
<component :key="model" :is="renderSlots()" />
|
|
42
|
-
</KeepAlive>
|
|
43
|
-
<component v-else :key="model" :is="renderSlots()" />
|
|
44
|
-
</transition>
|
|
2
|
+
<div :class="[cls.b, cls.m(position!)]">
|
|
3
|
+
<u-tabs-horizontal
|
|
4
|
+
v-if="isHorizontal"
|
|
5
|
+
v-model="model"
|
|
6
|
+
:items="items"
|
|
7
|
+
:position="horizontalPosition"
|
|
8
|
+
:size="size"
|
|
9
|
+
:closable="closable"
|
|
10
|
+
:block="block"
|
|
11
|
+
:rounded="rounded"
|
|
12
|
+
@click="onClick"
|
|
13
|
+
@close="onClose"
|
|
14
|
+
/>
|
|
15
|
+
<u-tabs-vertical
|
|
16
|
+
v-else
|
|
17
|
+
v-model="model"
|
|
18
|
+
:items="items"
|
|
19
|
+
:position="verticalPosition"
|
|
20
|
+
:size="size"
|
|
21
|
+
:closable="closable"
|
|
22
|
+
:rounded="rounded"
|
|
23
|
+
@click="onClick"
|
|
24
|
+
@close="onClose"
|
|
25
|
+
/>
|
|
26
|
+
|
|
27
|
+
<div v-if="hasContentSlots" :class="cls.e('content')">
|
|
28
|
+
<transition name="fade" mode="out-in">
|
|
29
|
+
<keep-alive v-if="keepAlive">
|
|
30
|
+
<component :key="model" :is="renderContent()" />
|
|
31
|
+
</keep-alive>
|
|
32
|
+
<component v-else :key="model" :is="renderContent()" />
|
|
33
|
+
</transition>
|
|
34
|
+
</div>
|
|
45
35
|
</div>
|
|
46
36
|
</template>
|
|
47
37
|
|
|
48
38
|
<script lang="ts" setup>
|
|
49
39
|
import { useFallbackProps } from '@veltra/compositions'
|
|
50
|
-
import { vRipple } from '@veltra/directives'
|
|
51
|
-
import { Close, Plus } from '@veltra/icons/normal'
|
|
52
40
|
import { bem } from '@veltra/utils'
|
|
53
|
-
import { computed, createVNode,
|
|
41
|
+
import { computed, createVNode, useSlots } from 'vue'
|
|
54
42
|
|
|
55
|
-
import type {
|
|
56
|
-
import { UIcon } from '../icon'
|
|
43
|
+
import type { ComponentSize, TabItem, TabsEmits, TabsProps } from '../../types'
|
|
57
44
|
import { UScroll } from '../scroll'
|
|
45
|
+
import UTabsHorizontal from './tabs-horizontal.vue'
|
|
46
|
+
import UTabsVertical from './tabs-vertical.vue'
|
|
58
47
|
|
|
59
|
-
defineOptions({
|
|
60
|
-
name: 'Tabs'
|
|
61
|
-
})
|
|
48
|
+
defineOptions({ name: 'Tabs' })
|
|
62
49
|
|
|
63
50
|
const props = withDefaults(defineProps<TabsProps>(), {
|
|
64
|
-
position: 'top'
|
|
51
|
+
position: 'top',
|
|
52
|
+
closable: false,
|
|
53
|
+
block: false,
|
|
54
|
+
rounded: false,
|
|
55
|
+
keepAlive: false
|
|
65
56
|
})
|
|
66
57
|
|
|
67
58
|
const emit = defineEmits<TabsEmits>()
|
|
68
59
|
|
|
69
|
-
const slots =
|
|
70
|
-
[key: string]: (props: { key: string }) => any
|
|
71
|
-
[key: `name:${string}`]: () => any
|
|
72
|
-
}>()
|
|
60
|
+
const slots = useSlots() as Record<string, ((props?: any) => any) | undefined>
|
|
73
61
|
|
|
74
62
|
const { size } = useFallbackProps([props], {
|
|
75
63
|
size: 'default' as ComponentSize
|
|
76
64
|
})
|
|
77
65
|
|
|
78
|
-
const tabItems = computed(() => {
|
|
79
|
-
return props.items
|
|
80
|
-
})
|
|
81
|
-
|
|
82
66
|
const cls = bem('tabs')
|
|
83
67
|
|
|
84
|
-
const headerRef = shallowRef<HTMLElement>()
|
|
85
|
-
|
|
86
|
-
/** 当前活动标签 */
|
|
87
|
-
const index = shallowRef(-1)
|
|
88
68
|
const model = defineModel<string>()
|
|
89
69
|
|
|
90
|
-
const
|
|
70
|
+
const isHorizontal = computed(() => props.position === 'top' || props.position === 'bottom')
|
|
91
71
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
([model, items]) => {
|
|
96
|
-
if (changedByEvent) return
|
|
97
|
-
index.value = items.findIndex((item) => item.key === model)
|
|
98
|
-
},
|
|
99
|
-
{ immediate: true }
|
|
72
|
+
/** 避免模板中使用类型断言:收敛 position 到水平 / 垂直子组件所需的子集。 */
|
|
73
|
+
const horizontalPosition = computed<'top' | 'bottom'>(() =>
|
|
74
|
+
props.position === 'bottom' ? 'bottom' : 'top'
|
|
100
75
|
)
|
|
101
76
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
async ([index, position, editable]) => {
|
|
105
|
-
if (index === -1 || editable) return
|
|
106
|
-
await nextTick()
|
|
107
|
-
|
|
108
|
-
const headerEl = headerRef.value!
|
|
109
|
-
|
|
110
|
-
const headerRect = headerEl.getBoundingClientRect()
|
|
111
|
-
const el = headerEl.children[index]! as HTMLLIElement
|
|
112
|
-
const { offsetWidth, offsetHeight } = el
|
|
113
|
-
|
|
114
|
-
const rect = el.getBoundingClientRect()
|
|
115
|
-
|
|
116
|
-
if (position === 'top' || position === 'bottom') {
|
|
117
|
-
markStyle.value = {
|
|
118
|
-
width: offsetWidth + 'px',
|
|
119
|
-
transform: `translate3d(${rect.left - headerRect.left}px, 0, 0)`
|
|
120
|
-
}
|
|
121
|
-
} else {
|
|
122
|
-
markStyle.value = {
|
|
123
|
-
height: offsetHeight + 'px',
|
|
124
|
-
transform: `translate3d(0, ${rect.top - headerRect.top}px, 0)`
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
{ immediate: true }
|
|
77
|
+
const verticalPosition = computed<'left' | 'right'>(() =>
|
|
78
|
+
props.position === 'right' ? 'right' : 'left'
|
|
129
79
|
)
|
|
130
80
|
|
|
131
|
-
|
|
132
|
-
|
|
81
|
+
/** 是否存在任一内容面板插槽;没有则不渲染内容区域。 */
|
|
82
|
+
const hasContentSlots = computed(() => Object.keys(slots).length > 0)
|
|
133
83
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
model.value = item.key
|
|
137
|
-
nextTick(() => {
|
|
138
|
-
changedByEvent = false
|
|
139
|
-
})
|
|
140
|
-
}
|
|
84
|
+
const onClick = (item: TabItem, index: number) => emit('click', item, index)
|
|
85
|
+
const onClose = (item: TabItem, index: number) => emit('close', item, index)
|
|
141
86
|
|
|
142
|
-
const
|
|
87
|
+
const renderContent = () => {
|
|
143
88
|
const key = model.value
|
|
144
89
|
if (!key) return null
|
|
145
90
|
const nodes = slots[key]?.({ key })
|
|
146
91
|
if (Array.isArray(nodes)) {
|
|
147
|
-
return createVNode(UScroll,
|
|
92
|
+
return createVNode(UScroll, null, { default: () => nodes })
|
|
148
93
|
}
|
|
149
94
|
return nodes
|
|
150
95
|
}
|
|
151
|
-
|
|
152
|
-
/** 关闭标签 */
|
|
153
|
-
const handleClose = (item: TabItem, index: number) => {
|
|
154
|
-
const { items } = props
|
|
155
|
-
if (model.value === item.key) {
|
|
156
|
-
model.value = (items[index + 1] ?? items[index - 1])!.key
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
emit('update:items', [...items.slice(0, index), ...items.slice(index + 1)])
|
|
160
|
-
|
|
161
|
-
emit('delete', item, index)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const handleAdd = () => {
|
|
165
|
-
emit('create')
|
|
166
|
-
}
|
|
167
96
|
</script>
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { useResizeObserver } from '@veltra/compositions'
|
|
2
|
+
import {
|
|
3
|
+
type ModelRef,
|
|
4
|
+
type Ref,
|
|
5
|
+
type ShallowRef,
|
|
6
|
+
nextTick,
|
|
7
|
+
onBeforeUnmount,
|
|
8
|
+
onMounted,
|
|
9
|
+
shallowRef,
|
|
10
|
+
watch
|
|
11
|
+
} from 'vue'
|
|
12
|
+
|
|
13
|
+
import type { TabItem } from '../../types'
|
|
14
|
+
|
|
15
|
+
interface UseTabsBarOptions {
|
|
16
|
+
viewportRef: ShallowRef<HTMLElement | undefined>
|
|
17
|
+
listRef: ShallowRef<HTMLElement | undefined>
|
|
18
|
+
items: Ref<TabItem[]>
|
|
19
|
+
model: ModelRef<string | undefined>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 水平标签栏的溢出滚动逻辑:
|
|
24
|
+
* - 计算 nav 按钮显隐与可用状态
|
|
25
|
+
* - 提供按步滚动与活动标签自动滚入视野的方法
|
|
26
|
+
* - 绑定 wheel / scroll / resize 事件
|
|
27
|
+
*/
|
|
28
|
+
export function useTabsBar(options: UseTabsBarOptions) {
|
|
29
|
+
const { viewportRef, listRef, items, model } = options
|
|
30
|
+
|
|
31
|
+
const showNav = shallowRef(false)
|
|
32
|
+
const canPrev = shallowRef(false)
|
|
33
|
+
const canNext = shallowRef(false)
|
|
34
|
+
|
|
35
|
+
/** 依据 viewport 与内容尺寸推导滚动按钮可用状态 */
|
|
36
|
+
const updateNavState = () => {
|
|
37
|
+
const vp = viewportRef.value
|
|
38
|
+
if (!vp) return
|
|
39
|
+
const { scrollLeft, scrollWidth, clientWidth } = vp
|
|
40
|
+
const overflowing = scrollWidth - clientWidth > 1
|
|
41
|
+
showNav.value = overflowing
|
|
42
|
+
canPrev.value = overflowing && scrollLeft > 0
|
|
43
|
+
canNext.value = overflowing && scrollLeft + clientWidth < scrollWidth - 1
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 箭头按钮:按视口宽度 80% 的步长平滑滚动 */
|
|
47
|
+
const scrollByStep = (dir: 1 | -1) => {
|
|
48
|
+
const vp = viewportRef.value
|
|
49
|
+
if (!vp) return
|
|
50
|
+
vp.scrollTo({ left: vp.scrollLeft + dir * vp.clientWidth * 0.8, behavior: 'smooth' })
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 活动标签自动滚入视野 */
|
|
54
|
+
const ensureActiveVisible = () => {
|
|
55
|
+
const vp = viewportRef.value
|
|
56
|
+
const list = listRef.value
|
|
57
|
+
if (!vp || !list) return
|
|
58
|
+
const activeKey = model.value
|
|
59
|
+
if (!activeKey) return
|
|
60
|
+
const activeIndex = items.value.findIndex((i) => i.key === activeKey)
|
|
61
|
+
if (activeIndex < 0) return
|
|
62
|
+
const el = list.children[activeIndex] as HTMLElement | undefined
|
|
63
|
+
if (!el) return
|
|
64
|
+
|
|
65
|
+
const vpRect = vp.getBoundingClientRect()
|
|
66
|
+
const elRect = el.getBoundingClientRect()
|
|
67
|
+
if (elRect.left < vpRect.left) {
|
|
68
|
+
vp.scrollTo({ left: vp.scrollLeft + (elRect.left - vpRect.left) - 8, behavior: 'smooth' })
|
|
69
|
+
} else if (elRect.right > vpRect.right) {
|
|
70
|
+
vp.scrollTo({ left: vp.scrollLeft + (elRect.right - vpRect.right) + 8, behavior: 'smooth' })
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 鼠标滚轮(纵向)驱动水平滚动;触控板横滑(已有 deltaX)不拦截 */
|
|
75
|
+
const handleWheel = (e: WheelEvent) => {
|
|
76
|
+
if (!showNav.value) return
|
|
77
|
+
const vp = viewportRef.value
|
|
78
|
+
if (!vp) return
|
|
79
|
+
if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) return
|
|
80
|
+
if (e.deltaY === 0) return
|
|
81
|
+
e.preventDefault()
|
|
82
|
+
vp.scrollLeft += e.deltaY
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
useResizeObserver({ targets: [viewportRef, listRef], onResize: () => updateNavState() })
|
|
86
|
+
|
|
87
|
+
watch(model, async () => {
|
|
88
|
+
await nextTick()
|
|
89
|
+
ensureActiveVisible()
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
watch(
|
|
93
|
+
items,
|
|
94
|
+
async () => {
|
|
95
|
+
await nextTick()
|
|
96
|
+
updateNavState()
|
|
97
|
+
ensureActiveVisible()
|
|
98
|
+
},
|
|
99
|
+
{ deep: false }
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
onMounted(() => {
|
|
103
|
+
const vp = viewportRef.value
|
|
104
|
+
if (!vp) return
|
|
105
|
+
vp.addEventListener('wheel', handleWheel, { passive: false })
|
|
106
|
+
vp.addEventListener('scroll', updateNavState, { passive: true })
|
|
107
|
+
updateNavState()
|
|
108
|
+
nextTick(ensureActiveVisible)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
onBeforeUnmount(() => {
|
|
112
|
+
const vp = viewportRef.value
|
|
113
|
+
if (!vp) return
|
|
114
|
+
vp.removeEventListener('wheel', handleWheel)
|
|
115
|
+
vp.removeEventListener('scroll', updateNavState)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
return { showNav, canPrev, canNext, updateNavState, scrollByStep, ensureActiveVisible }
|
|
119
|
+
}
|
|
@@ -1,19 +1,40 @@
|
|
|
1
1
|
@use 'pkg:@veltra/styles/mixins' as m;
|
|
2
2
|
@use 'pkg:@veltra/styles/functions' as fn;
|
|
3
|
+
@use 'sass:map';
|
|
3
4
|
|
|
4
5
|
$root-name: tree;
|
|
5
6
|
|
|
7
|
+
// 每一级缩进与展开图标 / 叶子占位保持一致的宽度,
|
|
8
|
+
// 保证节点内容在各层之间横向对齐。
|
|
9
|
+
$indent-width: 20px;
|
|
10
|
+
$indent-leaf-width: 20px;
|
|
11
|
+
$expand-icon-size: 20px;
|
|
12
|
+
// 节点稳态高度,与 `tree.vue` 中 `estimateSize` 协同,用于虚拟滚动初始估算。
|
|
13
|
+
$node-min-height-small: 28px;
|
|
14
|
+
$node-min-height-default: 32px;
|
|
15
|
+
$node-min-height-large: 40px;
|
|
16
|
+
$node-min-heights: (
|
|
17
|
+
small: $node-min-height-small,
|
|
18
|
+
default: $node-min-height-default,
|
|
19
|
+
large: $node-min-height-large
|
|
20
|
+
);
|
|
21
|
+
|
|
6
22
|
@include m.b($root-name) {
|
|
7
23
|
@include m.size using ($size) {
|
|
8
24
|
@include m.bem($root-name, node-content) {
|
|
9
25
|
border-radius: fn.use-var(radius, $size);
|
|
10
26
|
padding: fn.use-var(gap, $size);
|
|
11
27
|
line-height: 1;
|
|
28
|
+
gap: fn.use-var(gap, $size);
|
|
12
29
|
}
|
|
13
30
|
|
|
14
31
|
@include m.bem($root-name, checkbox) {
|
|
15
32
|
margin-right: fn.use-var(gap, $size);
|
|
16
33
|
}
|
|
34
|
+
|
|
35
|
+
@include m.bem($root-name, node) {
|
|
36
|
+
min-height: map.get($node-min-heights, $size);
|
|
37
|
+
}
|
|
17
38
|
}
|
|
18
39
|
|
|
19
40
|
@include m.e(checkbox) {
|
|
@@ -32,8 +53,20 @@ $root-name: tree;
|
|
|
32
53
|
width: 100%;
|
|
33
54
|
}
|
|
34
55
|
}
|
|
56
|
+
|
|
57
|
+
// 非虚拟模式通过相邻兄弟间距提供呼吸;
|
|
58
|
+
// 虚拟模式下改由 virtualizer 的 gap 参数统一负责。
|
|
59
|
+
&:not(.is-virtual) {
|
|
60
|
+
@include m.bem($root-name, node) {
|
|
61
|
+
& + & {
|
|
62
|
+
margin-top: 2px;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
35
66
|
}
|
|
36
67
|
|
|
68
|
+
// 选中 / 勾选态下,对节点内容提供 hover 反馈;
|
|
69
|
+
// 禁用节点不参与 hover 反馈,避免假性交互。
|
|
37
70
|
@include m.is(selectable, checkable) {
|
|
38
71
|
@include m.bem($root-name, node) {
|
|
39
72
|
&:not(.is-disabled) {
|
|
@@ -47,15 +80,27 @@ $root-name: tree;
|
|
|
47
80
|
}
|
|
48
81
|
|
|
49
82
|
@include m.e(node) {
|
|
83
|
+
position: relative;
|
|
50
84
|
cursor: pointer;
|
|
51
|
-
margin-bottom: 0.2rem;
|
|
52
85
|
@include m.flex($align: center);
|
|
53
86
|
|
|
54
87
|
@include m.is(selected) {
|
|
88
|
+
// 左侧 3px 色带,配合淡色背景共同构成选中态。
|
|
89
|
+
&::before {
|
|
90
|
+
content: '';
|
|
91
|
+
position: absolute;
|
|
92
|
+
left: 0;
|
|
93
|
+
top: 4px;
|
|
94
|
+
bottom: 4px;
|
|
95
|
+
width: 3px;
|
|
96
|
+
border-radius: 3px;
|
|
97
|
+
background-color: fn.use-var(color, primary);
|
|
98
|
+
}
|
|
99
|
+
|
|
55
100
|
@include m.bem($root-name, node-content) {
|
|
56
|
-
background-color: fn.use-var(color, primary, light-9)
|
|
101
|
+
background-color: fn.use-var(color, primary, light-9);
|
|
57
102
|
color: fn.use-var(color, primary);
|
|
58
|
-
font-weight:
|
|
103
|
+
font-weight: 500;
|
|
59
104
|
}
|
|
60
105
|
}
|
|
61
106
|
|
|
@@ -67,43 +112,41 @@ $root-name: tree;
|
|
|
67
112
|
|
|
68
113
|
@include m.is(disabled) {
|
|
69
114
|
cursor: not-allowed;
|
|
115
|
+
|
|
70
116
|
@include m.bem($root-name, node-content) {
|
|
71
117
|
color: fn.use-var(text-color, disabled);
|
|
118
|
+
|
|
119
|
+
&:hover {
|
|
120
|
+
background-color: transparent;
|
|
121
|
+
}
|
|
72
122
|
}
|
|
73
123
|
}
|
|
74
124
|
}
|
|
75
125
|
|
|
76
|
-
|
|
126
|
+
// 纯缩进占位:每级 20px 空白,不绘制连接线,
|
|
127
|
+
// 视觉层级由缩进 + 展开图标 + 选中态共同表达。
|
|
128
|
+
@include m.e(indent) {
|
|
77
129
|
flex-shrink: 0;
|
|
78
|
-
width:
|
|
79
|
-
|
|
130
|
+
width: $indent-width;
|
|
131
|
+
|
|
132
|
+
@include m.em(indent, leaf) {
|
|
133
|
+
width: $indent-leaf-width;
|
|
134
|
+
}
|
|
80
135
|
}
|
|
81
136
|
|
|
82
137
|
@include m.e(expand-icon) {
|
|
83
138
|
cursor: pointer;
|
|
84
|
-
transition:
|
|
85
|
-
transform 0.2s,
|
|
86
|
-
background-color 0.2s;
|
|
87
139
|
flex-shrink: 0;
|
|
88
|
-
|
|
140
|
+
width: $expand-icon-size;
|
|
141
|
+
height: $expand-icon-size;
|
|
142
|
+
font-size: $expand-icon-size;
|
|
89
143
|
border-radius: 50%;
|
|
144
|
+
transition:
|
|
145
|
+
transform 0.15s cubic-bezier(0.4, 0, 0.2, 1),
|
|
146
|
+
background-color 0.15s cubic-bezier(0.4, 0, 0.2, 1);
|
|
90
147
|
|
|
91
148
|
&:hover {
|
|
92
|
-
background-color: fn.use-var(color,
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
@include m.e(icon-placeholder) {
|
|
97
|
-
padding: 5px;
|
|
98
|
-
|
|
99
|
-
&::before {
|
|
100
|
-
content: '';
|
|
101
|
-
box-sizing: border-box;
|
|
102
|
-
width: 100%;
|
|
103
|
-
height: 100%;
|
|
104
|
-
display: block;
|
|
105
|
-
border-radius: 50%;
|
|
106
|
-
border: 2px solid fn.use-var(text-color, main);
|
|
149
|
+
background-color: fn.use-var(bg-color, hover);
|
|
107
150
|
}
|
|
108
151
|
}
|
|
109
152
|
|
|
@@ -111,17 +154,19 @@ $root-name: tree;
|
|
|
111
154
|
flex-grow: 1;
|
|
112
155
|
overflow: hidden;
|
|
113
156
|
user-select: none;
|
|
114
|
-
word-break: break-
|
|
115
|
-
transition: background-color 0.
|
|
157
|
+
word-break: break-word;
|
|
158
|
+
transition: background-color 0.2s ease;
|
|
116
159
|
@include m.flex($align: center);
|
|
117
160
|
}
|
|
118
161
|
|
|
119
162
|
@include m.e(ripple) {
|
|
120
|
-
background-color: fn.use-var(color, primary, light-
|
|
121
|
-
opacity: 0.
|
|
163
|
+
background-color: fn.use-var(color, primary, light-5);
|
|
164
|
+
opacity: 0.25;
|
|
122
165
|
}
|
|
123
166
|
|
|
124
167
|
@include m.e(empty) {
|
|
125
168
|
text-align: center;
|
|
169
|
+
padding: fn.use-var(gap, default) 0;
|
|
170
|
+
color: fn.use-var(text-color, secondary);
|
|
126
171
|
}
|
|
127
172
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
3
|
:class="[cls.e('node'), bem.is('expanded', node.expanded), bem.is('disabled', node.disabled)]"
|
|
4
|
-
:
|
|
5
|
-
:ref="measureElement"
|
|
4
|
+
:ref="measureRef"
|
|
6
5
|
>
|
|
6
|
+
<span v-for="i in node.depth" :key="i" :class="cls.e('indent')" aria-hidden="true"></span>
|
|
7
|
+
|
|
7
8
|
<u-icon v-if="!node.isLeaf" :class="cls.e('expand-icon')" @click.stop="toggleExpand">
|
|
8
9
|
<ArrowRight />
|
|
9
10
|
</u-icon>
|
|
10
|
-
<i v-else :class="cls.e('
|
|
11
|
+
<i v-else :class="[cls.e('indent'), cls.em('indent', 'leaf')]" aria-hidden="true"></i>
|
|
11
12
|
|
|
12
13
|
<div
|
|
13
14
|
:class="cls.e('node-content')"
|
|
@@ -35,7 +36,7 @@
|
|
|
35
36
|
<script lang="ts" setup>
|
|
36
37
|
import { vRipple } from '@veltra/directives'
|
|
37
38
|
import { ArrowRight } from '@veltra/icons/normal'
|
|
38
|
-
import { bem
|
|
39
|
+
import { bem } from '@veltra/utils'
|
|
39
40
|
import { inject } from 'vue'
|
|
40
41
|
|
|
41
42
|
import type { TreeNodeProps } from '../../types'
|
|
@@ -53,6 +54,11 @@ const props = defineProps<TreeNodeProps>()
|
|
|
53
54
|
const { treeProps, treeEmit, cls, getTreeSlotsNode, getFlattedNodes, toggleCheck, handleSelect } =
|
|
54
55
|
inject(TreeDIKey)!
|
|
55
56
|
|
|
57
|
+
function measureRef(el: unknown) {
|
|
58
|
+
if (typeof props.index !== 'number' || !props.measureElement) return
|
|
59
|
+
props.measureElement(props.index, el as Element | null)
|
|
60
|
+
}
|
|
61
|
+
|
|
56
62
|
function toggleExpand() {
|
|
57
63
|
const { node } = props
|
|
58
64
|
node.expanded = !node.expanded
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
<u-scroll
|
|
3
3
|
:class="className"
|
|
4
4
|
ref="scrollRef"
|
|
5
|
-
:content-style="{ height: virtualEnabled ? withUnit(totalHeight, 'px') : undefined }"
|
|
6
5
|
:content-class="[cls.e('wrap'), bem.is('virtual', virtualEnabled)]"
|
|
7
6
|
>
|
|
8
7
|
<template v-if="virtualEnabled">
|
|
@@ -11,7 +10,7 @@
|
|
|
11
10
|
:node="node"
|
|
12
11
|
:key="key"
|
|
13
12
|
:class="bem.is('selected', node.data === selectedData)"
|
|
14
|
-
:
|
|
13
|
+
:index="index"
|
|
15
14
|
:measure-element="measureElement"
|
|
16
15
|
:style="{ transform: `translateY(${offset}px)` }"
|
|
17
16
|
/>
|
|
@@ -32,9 +31,8 @@
|
|
|
32
31
|
</template>
|
|
33
32
|
|
|
34
33
|
<script lang="ts" setup>
|
|
35
|
-
import { useFormComponent, useFormFallbackProps } from '@veltra/compositions'
|
|
36
|
-
import {
|
|
37
|
-
import { bem, nextFrame, withUnit, scrollIntoContainerView } from '@veltra/utils'
|
|
34
|
+
import { useFormComponent, useFormFallbackProps, useVirtualizer } from '@veltra/compositions'
|
|
35
|
+
import { bem, nextFrame, scrollIntoContainerView } from '@veltra/utils'
|
|
38
36
|
import { computed, provide, shallowRef, useSlots, watch, watchEffect, type VNode } from 'vue'
|
|
39
37
|
|
|
40
38
|
import type { TreeProps, TreeEmit, _TreeExposed } from '../../types'
|
|
@@ -122,28 +120,57 @@ const { checkedData, toggleCheck } = useCheck({
|
|
|
122
120
|
getFlattedNodes
|
|
123
121
|
})
|
|
124
122
|
|
|
125
|
-
const
|
|
123
|
+
const estimateSize = (): number => {
|
|
124
|
+
// 基于 style.scss 中节点稳态高度推算的默认值;
|
|
125
|
+
// 实际高度仍由 `measureElement` 通过 ResizeObserver 回填覆盖。
|
|
126
|
+
switch (size.value) {
|
|
127
|
+
case 'small':
|
|
128
|
+
return 32
|
|
129
|
+
case 'large':
|
|
130
|
+
return 44
|
|
131
|
+
default:
|
|
132
|
+
return 36
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 80 阈值内化为消费者侧语义;低于此值时不启用虚拟化。
|
|
137
|
+
const virtualEnabled = computed(() => nodes.value.length > 80)
|
|
138
|
+
|
|
139
|
+
const { virtualizer, items } = useVirtualizer({
|
|
126
140
|
count: computed(() => nodes.value.length),
|
|
127
|
-
|
|
141
|
+
scrollEl: () => scrollRef.value?.containerRef ?? null,
|
|
142
|
+
// 仅在虚拟化启用时把内容容器的 height 撑开到 totalSize;
|
|
143
|
+
// 关闭时传 null,hook 会清除此前写入的内联 height,回到 CSS 默认。
|
|
144
|
+
contentEl: () => (virtualEnabled.value ? (scrollRef.value?.contentRef ?? null) : null),
|
|
145
|
+
estimateSize,
|
|
128
146
|
gap: 2,
|
|
129
|
-
|
|
130
|
-
|
|
147
|
+
// 以节点稳定 key 作为虚拟项身份,展开 / 收起 / 过滤引起的 nodes 数组变更
|
|
148
|
+
// 保留未移动节点的真实测量值,避免再次滚动首屏抖动。
|
|
149
|
+
getItemKey: (i) => nodes.value[i]?.key ?? i
|
|
131
150
|
})
|
|
132
151
|
|
|
152
|
+
// 身份稳定性已由底层 `getItemKey` 保证;直接以 `node.key` 作为 Vue 层 key。
|
|
153
|
+
// `item.index` 仅作为 nodes / snapshot 竞态边缘的 fallback。
|
|
133
154
|
const virtualNodes = computed(() => {
|
|
134
155
|
const _nodes = nodes.value
|
|
135
|
-
|
|
136
|
-
return virtualList.value.map((item) => {
|
|
156
|
+
return items.value.map((item) => {
|
|
137
157
|
const node = _nodes[item.index]!
|
|
138
158
|
return {
|
|
139
159
|
node,
|
|
140
|
-
key: node
|
|
160
|
+
key: node?.key ?? item.index,
|
|
141
161
|
offset: item.start,
|
|
142
162
|
index: item.index
|
|
143
163
|
}
|
|
144
164
|
})
|
|
145
165
|
})
|
|
146
166
|
|
|
167
|
+
const measureElement: (index: number, el: Element | null) => void = (index, el) =>
|
|
168
|
+
virtualizer.measureElement(index, el)
|
|
169
|
+
|
|
170
|
+
function scrollTo(index: number): void {
|
|
171
|
+
virtualizer.scrollToIndex(index, { align: 'center' })
|
|
172
|
+
}
|
|
173
|
+
|
|
147
174
|
function scrollIntoView() {
|
|
148
175
|
const { selectable, checkable, scrollToView } = props
|
|
149
176
|
if (!scrollToView || (!selectable && !checkable) || !nodes.value) return
|