@handaotech-design/bom 0.0.49 → 0.0.50
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 +91 -198
- package/dist/es/components/bom-workbench/index.vue +2 -15
- package/dist/es/sdk/index.d.ts +1 -1
- package/dist/es/sdk/index.js +1 -1
- package/dist/lib/components/bom-tree/index.vue +91 -198
- package/dist/lib/components/bom-workbench/index.vue +2 -15
- package/dist/lib/sdk/index.d.ts +1 -1
- package/dist/lib/sdk/index.js +1 -1
- package/dist/style.css +0 -2
- package/package.json +1 -1
- package/dist/es/components/bom-tree-view/index.d.ts +0 -3
- package/dist/es/components/bom-tree-view/index.js +0 -10
- package/dist/es/components/bom-tree-view/index.vue +0 -420
- package/dist/lib/components/bom-tree-view/index.d.ts +0 -3
- package/dist/lib/components/bom-tree-view/index.js +0 -29
- package/dist/lib/components/bom-tree-view/index.vue +0 -420
|
@@ -1,420 +0,0 @@
|
|
|
1
|
-
<script lang="ts" setup>
|
|
2
|
-
import { nextTick, onBeforeUnmount, onMounted, ref, toRefs, watch } from 'vue'
|
|
3
|
-
import type { TreeProps } from 'ant-design-vue'
|
|
4
|
-
import { Dropdown, Tree } from 'ant-design-vue'
|
|
5
|
-
import { watchDebounced } from '@vueuse/shared'
|
|
6
|
-
import * as _ from 'lodash-es'
|
|
7
|
-
import type { DataNode } from 'ant-design-vue/es/vc-tree-select/interface'
|
|
8
|
-
import type { EventDataNode } from 'ant-design-vue/es/tree'
|
|
9
|
-
import { MoreOutlined } from '@ant-design/icons-vue'
|
|
10
|
-
import HdGrayInput from '../gray-input'
|
|
11
|
-
import type { BomNode, BomTreeConfig } from '../../models'
|
|
12
|
-
|
|
13
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
14
|
-
maintainable: false,
|
|
15
|
-
})
|
|
16
|
-
const emit = defineEmits(['select'])
|
|
17
|
-
const COMPONENT_NAME = 'HdBomTreeView'
|
|
18
|
-
defineOptions({
|
|
19
|
-
name: COMPONENT_NAME,
|
|
20
|
-
components: { ATree: Tree, ADropdown: Dropdown },
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
type TreeNodeWithMeta = DataNode & {
|
|
24
|
-
parentKeys: string[]
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
interface Props {
|
|
28
|
-
treeData: TreeProps['treeData']
|
|
29
|
-
config: BomTreeConfig
|
|
30
|
-
maintainable?: boolean
|
|
31
|
-
shouldSelect?: (node: DataNode) => Promise<boolean>
|
|
32
|
-
keyNodeMap: Map<string, BomNode>
|
|
33
|
-
firstSelectableKey?: string
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const expandedKeys = ref<(string | number)[]>([])
|
|
37
|
-
const searchValue = ref<string>('')
|
|
38
|
-
const autoExpandParent = ref<boolean>(true)
|
|
39
|
-
const { treeData: _treeData } = toRefs(props)
|
|
40
|
-
const filteredTreeData = ref<TreeProps['treeData']>([])
|
|
41
|
-
const treeContainerHeight = ref<number>(0)
|
|
42
|
-
const treeContainerId = 'treeContainer'
|
|
43
|
-
let keyToNodeMap = new Map<string, TreeNodeWithMeta>()
|
|
44
|
-
const selectedKeys = ref<string[]>([])
|
|
45
|
-
const isPreservingTreeState = ref<boolean>(false)
|
|
46
|
-
const initTreeState = () => {
|
|
47
|
-
searchValue.value = ''
|
|
48
|
-
selectedKeys.value = []
|
|
49
|
-
expandedKeys.value = []
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const onExpand = (keys: Key[]) => {
|
|
53
|
-
expandedKeys.value = keys
|
|
54
|
-
autoExpandParent.value = false
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const updateTreeContainerHeight = () => {
|
|
58
|
-
const treeContainer = document.getElementById(treeContainerId)
|
|
59
|
-
if (treeContainer) {
|
|
60
|
-
treeContainerHeight.value = treeContainer.clientHeight
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const buildKeyToNodeMap = (treeData: TreeProps['treeData']) => {
|
|
65
|
-
const keyMap = new Map<string, TreeNodeWithMeta>()
|
|
66
|
-
const traverse = (nodes: TreeProps['treeData'] = [], parentKeys: string[] = []) => {
|
|
67
|
-
for (const node of nodes) {
|
|
68
|
-
if (parentKeys) {
|
|
69
|
-
keyMap.set(node.key as string, {
|
|
70
|
-
...node,
|
|
71
|
-
parentKeys: parentKeys || [],
|
|
72
|
-
} as any)
|
|
73
|
-
}
|
|
74
|
-
if (!_.isEmpty(node.children)) {
|
|
75
|
-
traverse(node.children, [...parentKeys, node.key as string])
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
traverse(treeData)
|
|
80
|
-
return keyMap
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const filterTreeData = (data: TreeProps['treeData'], filteredKeys: string[]) => {
|
|
84
|
-
const getNodes = (result: any, node: any) => {
|
|
85
|
-
if (_.includes(filteredKeys, node.key)) {
|
|
86
|
-
result.push({ ...node })
|
|
87
|
-
return result
|
|
88
|
-
}
|
|
89
|
-
if (Array.isArray(node.children)) {
|
|
90
|
-
const children = node.children.reduce(getNodes, [])
|
|
91
|
-
if (!_.isEmpty(children)) {
|
|
92
|
-
result.push({ ...node, children })
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return result
|
|
96
|
-
}
|
|
97
|
-
return (data || []).reduce(getNodes, [])
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// 获取直接父节点的 key
|
|
101
|
-
const getParentKeys = (key: string): string[] | undefined => {
|
|
102
|
-
const treeNode = keyToNodeMap.get(key)
|
|
103
|
-
return treeNode?.parentKeys || []
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const selectFirstSelectableNode = (preserveExpanded = false) => {
|
|
107
|
-
const firstNode: any = _.find(Array.from(keyToNodeMap.values()), node => (node as any)?.selectable)
|
|
108
|
-
if (!firstNode) {
|
|
109
|
-
return
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
nextTick(() => {
|
|
113
|
-
const parentKeys = getParentKeys(firstNode.key as string) as string[]
|
|
114
|
-
expandedKeys.value = preserveExpanded
|
|
115
|
-
? Array.from(new Set([...expandedKeys.value, ...parentKeys])) // 取并集
|
|
116
|
-
: parentKeys
|
|
117
|
-
selectedKeys.value = [firstNode.key]
|
|
118
|
-
})
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
type Key = string | number
|
|
122
|
-
const onSelected = async (keys: Key[], { node }: { node: EventDataNode }) => {
|
|
123
|
-
if (_.isFunction(props.shouldSelect) && node?.dataRef && !(await props.shouldSelect(node.dataRef))) {
|
|
124
|
-
return
|
|
125
|
-
}
|
|
126
|
-
const key = node?.dataRef?.key
|
|
127
|
-
if (key) {
|
|
128
|
-
selectedKeys.value = [key as string]
|
|
129
|
-
}
|
|
130
|
-
else {
|
|
131
|
-
selectedKeys.value = []
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
onMounted(async () => {
|
|
136
|
-
window.addEventListener('resize', updateTreeContainerHeight)
|
|
137
|
-
await nextTick(() => {
|
|
138
|
-
updateTreeContainerHeight()
|
|
139
|
-
})
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
onBeforeUnmount(() => {
|
|
143
|
-
window.removeEventListener('resize', updateTreeContainerHeight)
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
watch(
|
|
147
|
-
() => _treeData.value,
|
|
148
|
-
async () => {
|
|
149
|
-
filteredTreeData.value = _treeData.value
|
|
150
|
-
keyToNodeMap = buildKeyToNodeMap(_treeData.value ?? [])
|
|
151
|
-
initTreeState()
|
|
152
|
-
// if (!isPreservingTreeState.value) {
|
|
153
|
-
// selectFirstSelectableNode(true)
|
|
154
|
-
// }
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
immediate: true,
|
|
158
|
-
},
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
watchDebounced(
|
|
162
|
-
searchValue, (value: string) => {
|
|
163
|
-
let expanded: any[]
|
|
164
|
-
if (_.isEmpty(value)) {
|
|
165
|
-
expanded = getParentKeys(selectedKeys.value[0]) || []
|
|
166
|
-
filteredTreeData.value = _treeData.value
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
const filteredNodes: TreeProps['treeData'] = []
|
|
170
|
-
expanded = (Array.from(keyToNodeMap.values()))
|
|
171
|
-
.map((item: any) => {
|
|
172
|
-
if (hasSearchMatch(item.title)) {
|
|
173
|
-
filteredNodes.push(item)
|
|
174
|
-
return getParentKeys(item.key)
|
|
175
|
-
}
|
|
176
|
-
return null
|
|
177
|
-
})
|
|
178
|
-
.filter((item, i, self) => item && self.indexOf(item) === i)
|
|
179
|
-
const filteredKeys = filteredNodes.map(item => item.key)
|
|
180
|
-
filteredTreeData.value = _.isEmpty(filteredKeys) ? [] : filterTreeData(_treeData.value, filteredKeys as unknown as string[])
|
|
181
|
-
}
|
|
182
|
-
expandedKeys.value = expanded
|
|
183
|
-
nextTick(() => {
|
|
184
|
-
expandedKeys.value = _.uniq(expanded.flat(1)) as any as string[]
|
|
185
|
-
})
|
|
186
|
-
}, { debounce: 500 })
|
|
187
|
-
|
|
188
|
-
watch(
|
|
189
|
-
() => selectedKeys.value,
|
|
190
|
-
() => {
|
|
191
|
-
if (selectedKeys.value && selectedKeys.value.length) {
|
|
192
|
-
return emit('select', keyToNodeMap.get(selectedKeys.value[0]))
|
|
193
|
-
}
|
|
194
|
-
},
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
function getIconClass(icon: string, isSelected: boolean) {
|
|
198
|
-
return isSelected ? `i-icon-${icon}-selected` : `i-icon-${icon}`
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function getTitlePart(title: string, partType: 'before' | 'after') {
|
|
202
|
-
const _searchValue = searchValue.value || ''
|
|
203
|
-
const index = title.indexOf(_searchValue)
|
|
204
|
-
|
|
205
|
-
return {
|
|
206
|
-
before: title.substring(0, index),
|
|
207
|
-
after: title.substring(index + _searchValue.length),
|
|
208
|
-
}[partType]
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function hasSearchMatch(title: string) {
|
|
212
|
-
return searchValue.value && title.includes(searchValue.value)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
async function updateTreeWithPreservedState(updateDataFn: () => Promise<void>) {
|
|
216
|
-
const prevSelected = selectedKeys.value?.[0]
|
|
217
|
-
const prevExpanded = [...expandedKeys.value]
|
|
218
|
-
isPreservingTreeState.value = true
|
|
219
|
-
await updateDataFn()
|
|
220
|
-
await nextTick(() => {
|
|
221
|
-
expandedKeys.value = prevExpanded.filter(key => keyToNodeMap.has(key as string))
|
|
222
|
-
|
|
223
|
-
if (prevSelected && keyToNodeMap.has(prevSelected)) {
|
|
224
|
-
selectedKeys.value = [prevSelected]
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
selectFirstSelectableNode(true)
|
|
228
|
-
}
|
|
229
|
-
isPreservingTreeState.value = false
|
|
230
|
-
})
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function getDepth(key: string): number {
|
|
234
|
-
const parentKeys = getParentKeys(key)
|
|
235
|
-
if (_.isEmpty(parentKeys)) {
|
|
236
|
-
return -1
|
|
237
|
-
}
|
|
238
|
-
return parentKeys!.length + 1
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
defineExpose({
|
|
242
|
-
updateTreeWithPreservedState,
|
|
243
|
-
getParentKeys,
|
|
244
|
-
getDepth,
|
|
245
|
-
})
|
|
246
|
-
</script>
|
|
247
|
-
|
|
248
|
-
<template>
|
|
249
|
-
<div class="flex flex-col h-100%">
|
|
250
|
-
<div class="search-box">
|
|
251
|
-
<HdGrayInput
|
|
252
|
-
v-model:value="searchValue"
|
|
253
|
-
:placeholder="props.config?.filter?.placeholder || '输入关键词进行筛选(如名称/编号)'"
|
|
254
|
-
class="search-input"
|
|
255
|
-
>
|
|
256
|
-
<template #prefix>
|
|
257
|
-
<div class="i-icon-search w-16px h-16px" />
|
|
258
|
-
</template>
|
|
259
|
-
</HdGrayInput>
|
|
260
|
-
</div>
|
|
261
|
-
<div :id="treeContainerId" class="flex-grow tree-wrapper mt-12px overflow-y-hidden">
|
|
262
|
-
<div v-if="!_.isEmpty(filteredTreeData)">
|
|
263
|
-
<a-tree
|
|
264
|
-
:height="treeContainerHeight"
|
|
265
|
-
:expanded-keys="expandedKeys"
|
|
266
|
-
:auto-expand-parent="autoExpandParent"
|
|
267
|
-
:tree-data="filteredTreeData"
|
|
268
|
-
:block-node="true"
|
|
269
|
-
:default-selected-keys="[props.firstSelectableKey]"
|
|
270
|
-
@expand="onExpand"
|
|
271
|
-
@select="onSelected"
|
|
272
|
-
>
|
|
273
|
-
<template #title="{ title, dataRef }">
|
|
274
|
-
<div :class="`tree-node-tittle flex items-center ${dataRef.selectable ? 'selectable' : 'not-selectable'}`">
|
|
275
|
-
<div
|
|
276
|
-
v-if="!!dataRef.icon"
|
|
277
|
-
class="icon w-20px h-20px mr-4px min-w-20px"
|
|
278
|
-
:class="[getIconClass(dataRef.icon, selectedKeys.includes(dataRef.key))]"
|
|
279
|
-
/>
|
|
280
|
-
<div v-if="hasSearchMatch(title)" class="tree-node-text">
|
|
281
|
-
<span>{{ getTitlePart(title, 'before') }}</span>
|
|
282
|
-
<span class="highlight" style="background-color: #FFD499">{{ searchValue }}</span>
|
|
283
|
-
<span>{{ getTitlePart(title, 'after') }}</span>
|
|
284
|
-
</div>
|
|
285
|
-
<div v-else class="tree-node-text">
|
|
286
|
-
{{ title }}
|
|
287
|
-
</div>
|
|
288
|
-
<div v-if="props.maintainable" class="click-menu-wrapper">
|
|
289
|
-
<a-dropdown
|
|
290
|
-
:trigger="['hover', 'click']"
|
|
291
|
-
:destroy-popup-on-hide="true"
|
|
292
|
-
>
|
|
293
|
-
<MoreOutlined class="menu-trigger" @click.stop />
|
|
294
|
-
<template #overlay>
|
|
295
|
-
<slot name="node-menu" :tree-node="dataRef" />
|
|
296
|
-
</template>
|
|
297
|
-
</a-dropdown>
|
|
298
|
-
</div>
|
|
299
|
-
</div>
|
|
300
|
-
</template>
|
|
301
|
-
<template #switcherIcon="{ dataRef }">
|
|
302
|
-
<div
|
|
303
|
-
v-if="expandedKeys.includes(dataRef.key)" class="i-icon-remove-minus icon w-16px h-16px"
|
|
304
|
-
style="font-size: 20px;"
|
|
305
|
-
/>
|
|
306
|
-
<div v-else class="i-icon-remove-plus icon w-16px h-16px" />
|
|
307
|
-
</template>
|
|
308
|
-
</a-tree>
|
|
309
|
-
</div>
|
|
310
|
-
<div v-show="_.isEmpty(filteredTreeData)" class="w-100% h-100% flex justify-center items-center">
|
|
311
|
-
<span>暂无数据</span>
|
|
312
|
-
</div>
|
|
313
|
-
</div>
|
|
314
|
-
</div>
|
|
315
|
-
</template>
|
|
316
|
-
|
|
317
|
-
<style scoped>
|
|
318
|
-
.tree-wrapper {
|
|
319
|
-
background-color: #fff;
|
|
320
|
-
min-height: 0;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
.search-box {
|
|
324
|
-
padding: 0 16px;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
:deep(.ant-tree-list) .ant-tree-treenode-motion {
|
|
328
|
-
width: 100%;
|
|
329
|
-
}
|
|
330
|
-
:deep(.ant-tree-list) .ant-tree-treenode {
|
|
331
|
-
width: 100%;
|
|
332
|
-
align-items: center;
|
|
333
|
-
color: #2C2C2C;
|
|
334
|
-
font-size: 16px;
|
|
335
|
-
display: flex;
|
|
336
|
-
justify-content: center;
|
|
337
|
-
padding: 4px 0 4px 12px;
|
|
338
|
-
}
|
|
339
|
-
:deep(.ant-tree-list) .ant-tree-treenode .ant-tree-indent-unit {
|
|
340
|
-
width: 10px;
|
|
341
|
-
}
|
|
342
|
-
:deep(.ant-tree-list) .ant-tree-treenode .ant-tree-switcher {
|
|
343
|
-
width: 16px;
|
|
344
|
-
height: 16px;
|
|
345
|
-
padding: 0;
|
|
346
|
-
margin: 0 10px 0 0;
|
|
347
|
-
align-self: center;
|
|
348
|
-
line-height: 16px;
|
|
349
|
-
}
|
|
350
|
-
:deep(.ant-tree-list) .ant-tree-treenode .ant-tree-node-content-wrapper {
|
|
351
|
-
transition: none;
|
|
352
|
-
white-space: nowrap;
|
|
353
|
-
text-overflow: ellipsis;
|
|
354
|
-
margin-bottom: 0;
|
|
355
|
-
display: inline-flex;
|
|
356
|
-
overflow: visible !important;
|
|
357
|
-
flex: 1;
|
|
358
|
-
min-width: 0;
|
|
359
|
-
}
|
|
360
|
-
:deep(.ant-tree-list) .ant-tree-treenode .ant-tree-node-content-wrapper:hover {
|
|
361
|
-
background-color: #E9EBED;
|
|
362
|
-
}
|
|
363
|
-
:deep(.ant-tree-list) .ant-tree-treenode .ant-tree-node-content-wrapper .ant-tree-title {
|
|
364
|
-
text-overflow: ellipsis;
|
|
365
|
-
white-space: nowrap;
|
|
366
|
-
flex: 1;
|
|
367
|
-
min-width: 0;
|
|
368
|
-
position: relative;
|
|
369
|
-
}
|
|
370
|
-
:deep(.ant-tree-list) .ant-tree-treenode .ant-tree-node-content-wrapper .ant-tree-title .tree-node-tittle.selectable {
|
|
371
|
-
cursor: pointer;
|
|
372
|
-
}
|
|
373
|
-
:deep(.ant-tree-list) .ant-tree-treenode .ant-tree-node-content-wrapper .ant-tree-title .tree-node-tittle.not-selectable {
|
|
374
|
-
cursor: auto;
|
|
375
|
-
}
|
|
376
|
-
:deep(.ant-tree-list) .ant-tree-treenode .ant-tree-node-content-wrapper .ant-tree-title .tree-node-tittle .click-menu-wrapper {
|
|
377
|
-
display: none;
|
|
378
|
-
margin-left: auto;
|
|
379
|
-
margin-right: 4px;
|
|
380
|
-
}
|
|
381
|
-
:deep(.ant-tree-list) .ant-tree-treenode .ant-tree-node-content-wrapper .ant-tree-title .tree-node-tittle:hover .click-menu-wrapper {
|
|
382
|
-
display: block;
|
|
383
|
-
}
|
|
384
|
-
:deep(.ant-tree-list) .ant-tree-treenode:hover {
|
|
385
|
-
background-color: #E9EBED;
|
|
386
|
-
}
|
|
387
|
-
:deep(.ant-tree-list) .ant-tree-treenode .ant-tree-node-content-wrapper {
|
|
388
|
-
user-select: auto;
|
|
389
|
-
}
|
|
390
|
-
:deep(.ant-tree-list) .ant-tree-treenode.ant-tree-treenode-selected {
|
|
391
|
-
background-color: #EDF4FF;
|
|
392
|
-
border-radius: 4px;
|
|
393
|
-
color: #1E3B9D;
|
|
394
|
-
font-weight: 700;
|
|
395
|
-
}
|
|
396
|
-
:deep(.ant-tree-list) .ant-tree-treenode.ant-tree-treenode-selected .ant-tree-switcher, :deep(.ant-tree-list) .ant-tree-treenode.ant-tree-treenode-selected .ant-tree-node-content-wrapper {
|
|
397
|
-
background-color: #EDF4FF;
|
|
398
|
-
}
|
|
399
|
-
:deep(.ant-tree-list) .ant-tree-node-content-wrapper {
|
|
400
|
-
align-items: center;
|
|
401
|
-
padding: 0;
|
|
402
|
-
}
|
|
403
|
-
:deep(.ant-tree-list) .ant-tree-node-content-wrapper {
|
|
404
|
-
display: flex;
|
|
405
|
-
margin-bottom: -4px;
|
|
406
|
-
}
|
|
407
|
-
:deep(.ant-tree-list) .ant-tree-node-content-wrapper .ant-tree-title {
|
|
408
|
-
flex-grow: 1;
|
|
409
|
-
display: block;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
.tree-node-text {
|
|
413
|
-
display: inline-block;
|
|
414
|
-
max-width: 100%;
|
|
415
|
-
overflow: hidden;
|
|
416
|
-
text-overflow: ellipsis;
|
|
417
|
-
white-space: nowrap;
|
|
418
|
-
vertical-align: middle;
|
|
419
|
-
}
|
|
420
|
-
</style>
|