@handaotech-design/bom 0.0.40 → 0.0.41
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/es/components/bom-tree/index.vue +282 -71
- package/dist/es/components/bom-tree-view/index.d.ts +3 -0
- package/dist/es/components/bom-tree-view/index.js +10 -0
- package/dist/es/components/bom-tree-view/index.vue +420 -0
- package/dist/es/components/bom-workbench/index.vue +11 -1
- package/dist/es/utils/bom.d.ts +5 -1
- package/dist/es/utils/bom.js +55 -10
- package/dist/lib/components/bom-tree/index.vue +282 -71
- package/dist/lib/components/bom-tree-view/index.d.ts +3 -0
- package/dist/lib/components/bom-tree-view/index.js +29 -0
- package/dist/lib/components/bom-tree-view/index.vue +420 -0
- package/dist/lib/components/bom-workbench/index.vue +11 -1
- package/dist/lib/utils/bom.d.ts +5 -1
- package/dist/lib/utils/bom.js +55 -14
- package/dist/style.css +4 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
-
import { nextTick, onBeforeUnmount, onMounted, ref, toRefs, watch } from 'vue'
|
|
2
|
+
import { computed, nextTick, onBeforeUnmount, onMounted, ref, toRefs, watch } from 'vue'
|
|
3
3
|
import type { TreeProps } from 'ant-design-vue'
|
|
4
4
|
import { Dropdown, Tree } from 'ant-design-vue'
|
|
5
5
|
import { watchDebounced } from '@vueuse/shared'
|
|
@@ -8,7 +8,8 @@ import type { DataNode } from 'ant-design-vue/es/vc-tree-select/interface'
|
|
|
8
8
|
import type { EventDataNode } from 'ant-design-vue/es/tree'
|
|
9
9
|
import { MoreOutlined } from '@ant-design/icons-vue'
|
|
10
10
|
import HdGrayInput from '../gray-input'
|
|
11
|
-
import type { BomTreeConfig } from '../../models'
|
|
11
|
+
import type { BomNode, BomTreeConfig } from '../../models'
|
|
12
|
+
import { convertBomDataToTree } from '../../utils'
|
|
12
13
|
|
|
13
14
|
const props = withDefaults(defineProps<Props>(), {
|
|
14
15
|
maintainable: false,
|
|
@@ -29,22 +30,40 @@ interface Props {
|
|
|
29
30
|
config: BomTreeConfig
|
|
30
31
|
maintainable?: boolean
|
|
31
32
|
shouldSelect?: (node: DataNode) => Promise<boolean>
|
|
33
|
+
keyNodeMap: Map<string, any>
|
|
34
|
+
firstSelectableKey?: string
|
|
35
|
+
loadData?: (node: BomNode, eventNode?: EventDataNode) => Promise<DataNode[]>
|
|
32
36
|
}
|
|
33
37
|
|
|
38
|
+
const localTreeData = ref<DataNode[]>(props.treeData || [])
|
|
34
39
|
const expandedKeys = ref<(string | number)[]>([])
|
|
35
40
|
const searchValue = ref<string>('')
|
|
36
41
|
const autoExpandParent = ref<boolean>(true)
|
|
37
|
-
const { treeData: _treeData } = toRefs(props)
|
|
38
|
-
const filteredTreeData = ref<TreeProps['treeData']>([])
|
|
39
42
|
const treeContainerHeight = ref<number>(0)
|
|
40
43
|
const treeContainerId = 'treeContainer'
|
|
44
|
+
const treeContainerRef = ref<HTMLElement | null>(null)
|
|
45
|
+
const treeRef = ref<any>(null)
|
|
41
46
|
let keyToNodeMap = new Map<string, TreeNodeWithMeta>()
|
|
42
47
|
const selectedKeys = ref<string[]>([])
|
|
48
|
+
const MAX_ROOT = 200
|
|
49
|
+
const fullRootKeys = computed(() => (localTreeData.value || []).map(node => node.key as string))
|
|
50
|
+
const visibleRootKeys = ref<Set<string>>(new Set())
|
|
51
|
+
const displayTreeData = computed(() => {
|
|
52
|
+
if (!localTreeData.value) {
|
|
53
|
+
return []
|
|
54
|
+
}
|
|
55
|
+
return localTreeData.value.filter(node => visibleRootKeys.value.has(node.key as string))
|
|
56
|
+
})
|
|
43
57
|
const isPreservingTreeState = ref<boolean>(false)
|
|
58
|
+
const isAutoLoadingRoots = ref(false)
|
|
59
|
+
const searchMatches = ref<string[]>([])
|
|
60
|
+
const currentMatchIndex = ref(0)
|
|
44
61
|
const initTreeState = () => {
|
|
45
62
|
searchValue.value = ''
|
|
46
63
|
selectedKeys.value = []
|
|
47
64
|
expandedKeys.value = []
|
|
65
|
+
searchMatches.value = []
|
|
66
|
+
currentMatchIndex.value = 0
|
|
48
67
|
}
|
|
49
68
|
|
|
50
69
|
const onExpand = (keys: Key[]) => {
|
|
@@ -53,66 +72,87 @@ const onExpand = (keys: Key[]) => {
|
|
|
53
72
|
}
|
|
54
73
|
|
|
55
74
|
const updateTreeContainerHeight = () => {
|
|
56
|
-
const treeContainer = document.getElementById(treeContainerId)
|
|
75
|
+
const treeContainer = treeContainerRef.value || document.getElementById(treeContainerId)
|
|
57
76
|
if (treeContainer) {
|
|
58
77
|
treeContainerHeight.value = treeContainer.clientHeight
|
|
59
78
|
}
|
|
60
79
|
}
|
|
61
80
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
if (!_.isEmpty(node.children)) {
|
|
73
|
-
traverse(node.children, [...parentKeys, node.key as string])
|
|
74
|
-
}
|
|
75
|
-
}
|
|
81
|
+
// 获取直接父节点的 key
|
|
82
|
+
const getParentKeys = (key: string): string[] | undefined => {
|
|
83
|
+
const treeNode = keyToNodeMap.get(key)
|
|
84
|
+
return treeNode?.parentKeys || []
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const ensureRootVisibleByKey = (key?: string) => {
|
|
88
|
+
if (!key) {
|
|
89
|
+
return
|
|
76
90
|
}
|
|
77
|
-
|
|
78
|
-
|
|
91
|
+
const parentKeys = getParentKeys(key)
|
|
92
|
+
const rootKey = parentKeys?.[0] ?? key
|
|
93
|
+
if (!rootKey || visibleRootKeys.value.has(rootKey)) {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
const next = new Set(visibleRootKeys.value)
|
|
97
|
+
next.add(rootKey)
|
|
98
|
+
visibleRootKeys.value = next
|
|
79
99
|
}
|
|
80
100
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
result.push({ ...node })
|
|
85
|
-
return result
|
|
86
|
-
}
|
|
87
|
-
if (Array.isArray(node.children)) {
|
|
88
|
-
const children = node.children.reduce(getNodes, [])
|
|
89
|
-
if (!_.isEmpty(children)) {
|
|
90
|
-
result.push({ ...node, children })
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return result
|
|
101
|
+
async function locateNodeByKey(targetKey: string) {
|
|
102
|
+
if (!targetKey) {
|
|
103
|
+
return
|
|
94
104
|
}
|
|
95
|
-
|
|
105
|
+
ensureRootVisibleByKey(targetKey)
|
|
106
|
+
const parentKeys = getParentKeys(targetKey) || []
|
|
107
|
+
expandedKeys.value = Array.from(new Set([
|
|
108
|
+
...expandedKeys.value,
|
|
109
|
+
...parentKeys,
|
|
110
|
+
]))
|
|
111
|
+
await nextTick()
|
|
112
|
+
await nextTick()
|
|
113
|
+
treeRef.value?.scrollTo?.({
|
|
114
|
+
key: targetKey,
|
|
115
|
+
align: 'top',
|
|
116
|
+
})
|
|
117
|
+
focusNode(targetKey)
|
|
96
118
|
}
|
|
97
119
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
120
|
+
const scrollToMatch = async (index: number) => {
|
|
121
|
+
if (!searchMatches.value.length) {
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
const total = searchMatches.value.length
|
|
125
|
+
const normalized = ((index % total) + total) % total
|
|
126
|
+
currentMatchIndex.value = normalized
|
|
127
|
+
const targetKey = searchMatches.value[normalized]
|
|
128
|
+
await locateNodeByKey(targetKey)
|
|
102
129
|
}
|
|
103
130
|
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
131
|
+
const focusPreviousMatch = () => {
|
|
132
|
+
if (!searchMatches.value.length) {
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
scrollToMatch(currentMatchIndex.value - 1)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const focusNextMatch = () => {
|
|
139
|
+
if (!searchMatches.value.length) {
|
|
107
140
|
return
|
|
108
141
|
}
|
|
142
|
+
scrollToMatch(currentMatchIndex.value + 1)
|
|
143
|
+
}
|
|
109
144
|
|
|
145
|
+
const selectFirstSelectableNode = (preserveExpanded = false) => {
|
|
146
|
+
if (!props.firstSelectableKey) {
|
|
147
|
+
return
|
|
148
|
+
}
|
|
110
149
|
nextTick(() => {
|
|
111
|
-
|
|
150
|
+
ensureRootVisibleByKey(props.firstSelectableKey)
|
|
151
|
+
const parentKeys = getParentKeys(props.firstSelectableKey!) as string[]
|
|
112
152
|
expandedKeys.value = preserveExpanded
|
|
113
153
|
? Array.from(new Set([...expandedKeys.value, ...parentKeys])) // 取并集
|
|
114
154
|
: parentKeys
|
|
115
|
-
selectedKeys.value = [
|
|
155
|
+
selectedKeys.value = [props.firstSelectableKey!]
|
|
116
156
|
})
|
|
117
157
|
}
|
|
118
158
|
|
|
@@ -123,6 +163,7 @@ const onSelected = async (keys: Key[], { node }: { node: EventDataNode }) => {
|
|
|
123
163
|
}
|
|
124
164
|
const key = node?.dataRef?.key
|
|
125
165
|
if (key) {
|
|
166
|
+
ensureRootVisibleByKey(key as string)
|
|
126
167
|
selectedKeys.value = [key as string]
|
|
127
168
|
}
|
|
128
169
|
else {
|
|
@@ -142,10 +183,14 @@ onBeforeUnmount(() => {
|
|
|
142
183
|
})
|
|
143
184
|
|
|
144
185
|
watch(
|
|
145
|
-
() =>
|
|
186
|
+
() => localTreeData.value,
|
|
146
187
|
async () => {
|
|
147
|
-
|
|
148
|
-
|
|
188
|
+
keyToNodeMap = props.keyNodeMap
|
|
189
|
+
const visibleSet = new Set<string>()
|
|
190
|
+
;(localTreeData.value || []).slice(0, MAX_ROOT).forEach((node) => {
|
|
191
|
+
visibleSet.add(node.key as string)
|
|
192
|
+
})
|
|
193
|
+
visibleRootKeys.value = visibleSet
|
|
149
194
|
initTreeState()
|
|
150
195
|
if (!isPreservingTreeState.value) {
|
|
151
196
|
selectFirstSelectableNode(true)
|
|
@@ -157,30 +202,34 @@ watch(
|
|
|
157
202
|
)
|
|
158
203
|
|
|
159
204
|
watchDebounced(
|
|
160
|
-
searchValue, (value: string) => {
|
|
161
|
-
let expanded: any[]
|
|
205
|
+
searchValue, async (value: string) => {
|
|
206
|
+
let expanded: any[] = []
|
|
162
207
|
if (_.isEmpty(value)) {
|
|
208
|
+
searchMatches.value = []
|
|
209
|
+
currentMatchIndex.value = 0
|
|
163
210
|
expanded = getParentKeys(selectedKeys.value[0]) || []
|
|
164
|
-
filteredTreeData.value = _treeData.value
|
|
165
211
|
}
|
|
166
212
|
else {
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
213
|
+
const matchedParents: string[][] = []
|
|
214
|
+
const matches: string[] = []
|
|
215
|
+
for (const node of keyToNodeMap.values()) {
|
|
216
|
+
if (hasSearchMatch((node as any).title)) {
|
|
217
|
+
const key = node.key as string
|
|
218
|
+
matches.push(key)
|
|
219
|
+
ensureRootVisibleByKey(key)
|
|
220
|
+
matchedParents.push(getParentKeys(key) || [])
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
searchMatches.value = matches
|
|
224
|
+
currentMatchIndex.value = matches.length ? 0 : 0
|
|
225
|
+
expanded = matchedParents.flat()
|
|
179
226
|
}
|
|
180
227
|
expandedKeys.value = expanded
|
|
181
|
-
nextTick(
|
|
182
|
-
|
|
183
|
-
|
|
228
|
+
await nextTick()
|
|
229
|
+
expandedKeys.value = _.uniq(expanded.flat(1)) as any as string[]
|
|
230
|
+
if (!_.isEmpty(value) && searchMatches.value.length) {
|
|
231
|
+
await scrollToMatch(currentMatchIndex.value)
|
|
232
|
+
}
|
|
184
233
|
}, { debounce: 500 })
|
|
185
234
|
|
|
186
235
|
watch(
|
|
@@ -219,6 +268,7 @@ async function updateTreeWithPreservedState(updateDataFn: () => Promise<void>) {
|
|
|
219
268
|
expandedKeys.value = prevExpanded.filter(key => keyToNodeMap.has(key as string))
|
|
220
269
|
|
|
221
270
|
if (prevSelected && keyToNodeMap.has(prevSelected)) {
|
|
271
|
+
ensureRootVisibleByKey(prevSelected)
|
|
222
272
|
selectedKeys.value = [prevSelected]
|
|
223
273
|
}
|
|
224
274
|
else {
|
|
@@ -241,35 +291,159 @@ defineExpose({
|
|
|
241
291
|
getParentKeys,
|
|
242
292
|
getDepth,
|
|
243
293
|
})
|
|
294
|
+
|
|
295
|
+
const canLoadMoreRoot = computed(() => visibleRootKeys.value.size < fullRootKeys.value.length)
|
|
296
|
+
const loadMoreRoot = () => {
|
|
297
|
+
if (!fullRootKeys.value.length) {
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
const currentSize = visibleRootKeys.value.size
|
|
301
|
+
if (currentSize >= fullRootKeys.value.length) {
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
const next = new Set(visibleRootKeys.value)
|
|
305
|
+
fullRootKeys.value.slice(currentSize, currentSize + MAX_ROOT).forEach((key) => {
|
|
306
|
+
next.add(key)
|
|
307
|
+
})
|
|
308
|
+
visibleRootKeys.value = next
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const maybeAutoLoadMoreRoot = () => {
|
|
312
|
+
if (isAutoLoadingRoots.value || !canLoadMoreRoot.value) {
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
const container = treeContainerRef.value
|
|
316
|
+
if (!container) {
|
|
317
|
+
return
|
|
318
|
+
}
|
|
319
|
+
const nearBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - 80
|
|
320
|
+
if (!nearBottom) {
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
isAutoLoadingRoots.value = true
|
|
324
|
+
loadMoreRoot()
|
|
325
|
+
requestAnimationFrame(() => {
|
|
326
|
+
isAutoLoadingRoots.value = false
|
|
327
|
+
})
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const handleTreeScroll = _.throttle(maybeAutoLoadMoreRoot, 300)
|
|
331
|
+
|
|
332
|
+
async function _loadData(node: EventDataNode) {
|
|
333
|
+
if (!props.loadData) {
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
if (node.children && node.children.length) {
|
|
337
|
+
return
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const children: DataNode[] = await props.loadData(node.dataRef as BomNode, node)
|
|
341
|
+
|
|
342
|
+
if (children.length) {
|
|
343
|
+
children.forEach(item => item.key = node.key + (item.key ?? item.id) as string)
|
|
344
|
+
const { treeData, nodeMap } = convertBomDataToTree(children as any, props.config.nodeConfig.rule) as any
|
|
345
|
+
nodeMap.entries().forEach(([key, value]: any[]) => {
|
|
346
|
+
keyToNodeMap.set(key as string, {
|
|
347
|
+
...value,
|
|
348
|
+
parentKeys: [...(getParentKeys(node.key as string) || []), node.key as string],
|
|
349
|
+
})
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
node.dataRef!.children = treeData
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
node.isLeaf = true
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function focusNode(key: string) {
|
|
360
|
+
// 1. 用 antd Tree 自带滚动
|
|
361
|
+
treeRef.value?.scrollTo({ key, align: 'center' })
|
|
362
|
+
|
|
363
|
+
// 2. 下一帧再找 DOM,避免滚动未完成
|
|
364
|
+
requestAnimationFrame(() => {
|
|
365
|
+
const el = document.querySelector(
|
|
366
|
+
`.tree-node-tittle[data-key="${key}"]`,
|
|
367
|
+
) as HTMLElement | null
|
|
368
|
+
|
|
369
|
+
if (!el) {
|
|
370
|
+
return
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// 3. 触发一次可感知的焦点动画
|
|
374
|
+
el.classList.remove('focus-flash')
|
|
375
|
+
void el.offsetWidth
|
|
376
|
+
el.classList.add('focus-flash')
|
|
377
|
+
|
|
378
|
+
setTimeout(() => {
|
|
379
|
+
el.classList.remove('focus-flash')
|
|
380
|
+
}, 2400)
|
|
381
|
+
})
|
|
382
|
+
}
|
|
244
383
|
</script>
|
|
245
384
|
|
|
246
385
|
<template>
|
|
247
386
|
<div class="flex flex-col h-100%">
|
|
248
387
|
<div class="search-box">
|
|
249
388
|
<HdGrayInput
|
|
250
|
-
v-model:value="searchValue"
|
|
389
|
+
v-model:value.trim="searchValue"
|
|
251
390
|
:placeholder="props.config?.filter?.placeholder || '输入关键词进行筛选(如名称/编号)'"
|
|
252
391
|
class="search-input"
|
|
253
392
|
>
|
|
254
393
|
<template #prefix>
|
|
255
394
|
<div class="i-icon-search w-16px h-16px" />
|
|
256
395
|
</template>
|
|
396
|
+
<template #suffix>
|
|
397
|
+
<div v-if="searchValue" class="match-hint mr-8px font-size-12px">
|
|
398
|
+
{{ searchMatches.length ? currentMatchIndex + 1 : 0 }} / {{ searchMatches.length }}
|
|
399
|
+
</div>
|
|
400
|
+
<div v-if="searchValue" class="match-nav" :class="{ 'is-disabled': !searchMatches.length }">
|
|
401
|
+
<a-button
|
|
402
|
+
class="match-nav-btn mr-4px"
|
|
403
|
+
shape="circle"
|
|
404
|
+
size="small"
|
|
405
|
+
:disabled="!searchMatches.length"
|
|
406
|
+
@click.stop.prevent="focusPreviousMatch"
|
|
407
|
+
>
|
|
408
|
+
↑
|
|
409
|
+
</a-button>
|
|
410
|
+
<a-button
|
|
411
|
+
class="match-nav-btn"
|
|
412
|
+
shape="circle"
|
|
413
|
+
size="small"
|
|
414
|
+
:disabled="!searchMatches.length"
|
|
415
|
+
@click.stop.prevent="focusNextMatch"
|
|
416
|
+
>
|
|
417
|
+
↓
|
|
418
|
+
</a-button>
|
|
419
|
+
</div>
|
|
420
|
+
</template>
|
|
257
421
|
</HdGrayInput>
|
|
258
422
|
</div>
|
|
259
|
-
<div
|
|
260
|
-
|
|
423
|
+
<div
|
|
424
|
+
:id="treeContainerId"
|
|
425
|
+
ref="treeContainerRef"
|
|
426
|
+
class="flex-grow tree-wrapper mt-12px"
|
|
427
|
+
>
|
|
428
|
+
<div v-if="!_.isEmpty(displayTreeData)">
|
|
261
429
|
<a-tree
|
|
430
|
+
ref="treeRef"
|
|
262
431
|
:height="treeContainerHeight"
|
|
432
|
+
:tree-data="displayTreeData"
|
|
263
433
|
:expanded-keys="expandedKeys"
|
|
264
434
|
:auto-expand-parent="autoExpandParent"
|
|
265
|
-
:tree-data="filteredTreeData"
|
|
266
435
|
:block-node="true"
|
|
267
436
|
:selected-keys="selectedKeys"
|
|
437
|
+
:load-data="_loadData"
|
|
438
|
+
@scroll="handleTreeScroll"
|
|
268
439
|
@expand="onExpand"
|
|
269
440
|
@select="onSelected"
|
|
270
441
|
>
|
|
271
442
|
<template #title="{ title, dataRef }">
|
|
272
|
-
<div
|
|
443
|
+
<div
|
|
444
|
+
:class="`tree-node-tittle flex items-center ${dataRef.selectable ? 'selectable' : 'not-selectable'}`"
|
|
445
|
+
:data-key="dataRef.key"
|
|
446
|
+
>
|
|
273
447
|
<div
|
|
274
448
|
v-if="!!dataRef.icon"
|
|
275
449
|
class="icon w-20px h-20px mr-4px min-w-20px"
|
|
@@ -305,7 +479,7 @@ defineExpose({
|
|
|
305
479
|
</template>
|
|
306
480
|
</a-tree>
|
|
307
481
|
</div>
|
|
308
|
-
<div v-show="_.isEmpty(
|
|
482
|
+
<div v-show="_.isEmpty(displayTreeData)" class="w-100% h-100% flex justify-center items-center">
|
|
309
483
|
<span>暂无数据</span>
|
|
310
484
|
</div>
|
|
311
485
|
</div>
|
|
@@ -313,6 +487,7 @@ defineExpose({
|
|
|
313
487
|
</template>
|
|
314
488
|
|
|
315
489
|
<style scoped>
|
|
490
|
+
@charset "UTF-8";
|
|
316
491
|
.tree-wrapper {
|
|
317
492
|
background-color: #fff;
|
|
318
493
|
min-height: 0;
|
|
@@ -322,6 +497,12 @@ defineExpose({
|
|
|
322
497
|
padding: 0 16px;
|
|
323
498
|
}
|
|
324
499
|
|
|
500
|
+
:deep(.match-nav) .ant-btn:hover, :deep(.match-nav) .ant-btn:focus {
|
|
501
|
+
color: #1E3B9D;
|
|
502
|
+
border-color: #1E3B9D;
|
|
503
|
+
background: #fff;
|
|
504
|
+
}
|
|
505
|
+
|
|
325
506
|
:deep(.ant-tree-list) .ant-tree-treenode-motion {
|
|
326
507
|
width: 100%;
|
|
327
508
|
}
|
|
@@ -415,4 +596,34 @@ defineExpose({
|
|
|
415
596
|
white-space: nowrap;
|
|
416
597
|
vertical-align: middle;
|
|
417
598
|
}
|
|
599
|
+
|
|
600
|
+
.tree-node-tittle {
|
|
601
|
+
position: relative;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/* 焦点提示:整行描边框 */
|
|
605
|
+
.tree-node-tittle.focus-flash::after {
|
|
606
|
+
content: "";
|
|
607
|
+
position: absolute;
|
|
608
|
+
inset: -4px -8px;
|
|
609
|
+
border-radius: 8px;
|
|
610
|
+
pointer-events: none;
|
|
611
|
+
box-shadow: 0 0 0 1px rgba(30, 59, 157, 0.25), 0 0 8px rgba(30, 59, 157, 0.25);
|
|
612
|
+
animation: focusSoftGlow 1.6s ease-out 2;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
@keyframes focusSoftGlow {
|
|
616
|
+
0% {
|
|
617
|
+
opacity: 0;
|
|
618
|
+
transform: scale(0.98);
|
|
619
|
+
}
|
|
620
|
+
30% {
|
|
621
|
+
opacity: 1;
|
|
622
|
+
transform: scale(1);
|
|
623
|
+
}
|
|
624
|
+
100% {
|
|
625
|
+
opacity: 0;
|
|
626
|
+
transform: scale(1.01);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
418
629
|
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Dropdown, Input, Tree } from "ant-design-vue";
|
|
2
|
+
import { MoreOutlined } from "@ant-design/icons-vue";
|
|
3
|
+
import { withInstall } from "../../utils/install.js";
|
|
4
|
+
import BomTree from "./index.vue";
|
|
5
|
+
export * from "./index.vue";
|
|
6
|
+
const antdComponents = Object.fromEntries(
|
|
7
|
+
[Input, Tree, Dropdown, MoreOutlined].map((comp) => [comp.name, comp])
|
|
8
|
+
);
|
|
9
|
+
export const HdBomTree = withInstall(BomTree, antdComponents);
|
|
10
|
+
export default HdBomTree;
|