@handaotech-design/bom 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +13 -0
  2. package/build.config.ts +27 -0
  3. package/dist/es/all-components.d.ts +3 -0
  4. package/dist/es/all-components.js +10 -0
  5. package/dist/es/assets/icons/operation-selected.svg +3 -0
  6. package/dist/es/assets/icons/operation.svg +3 -0
  7. package/dist/es/assets/icons/process-path-selected.svg +3 -0
  8. package/dist/es/assets/icons/process-path.svg +3 -0
  9. package/dist/es/assets/icons/process-plan-selected.svg +3 -0
  10. package/dist/es/assets/icons/process-plan.svg +3 -0
  11. package/dist/es/assets/icons/remove-minus.svg +4 -0
  12. package/dist/es/assets/icons/remove-plus.svg +5 -0
  13. package/dist/es/assets/icons/search.svg +4 -0
  14. package/dist/es/components/bom-tree/index.d.ts +3 -0
  15. package/dist/es/components/bom-tree/index.js +5 -0
  16. package/dist/es/components/bom-tree/index.vue +358 -0
  17. package/dist/es/components/bom-workbench/index.d.ts +3 -0
  18. package/dist/es/components/bom-workbench/index.js +5 -0
  19. package/dist/es/components/bom-workbench/index.vue +98 -0
  20. package/dist/es/components/gray-input/index.d.ts +3 -0
  21. package/dist/es/components/gray-input/index.js +5 -0
  22. package/dist/es/components/gray-input/index.vue +44 -0
  23. package/dist/es/components/index.d.ts +4 -0
  24. package/dist/es/components/index.js +4 -0
  25. package/dist/es/components/left-right/index.d.ts +3 -0
  26. package/dist/es/components/left-right/index.js +5 -0
  27. package/dist/es/components/left-right/index.vue +142 -0
  28. package/dist/es/defaults.d.ts +4 -0
  29. package/dist/es/defaults.js +3 -0
  30. package/dist/es/hooks/index.d.ts +1 -0
  31. package/dist/es/hooks/index.js +1 -0
  32. package/dist/es/hooks/use-ppboms.d.ts +19 -0
  33. package/dist/es/hooks/use-ppboms.js +81 -0
  34. package/dist/es/index.d.ts +7 -0
  35. package/dist/es/index.js +7 -0
  36. package/dist/es/models/bom.d.ts +35 -0
  37. package/dist/es/models/bom.js +1 -0
  38. package/dist/es/models/common.d.ts +5 -0
  39. package/dist/es/models/common.js +5 -0
  40. package/dist/es/models/index.d.ts +2 -0
  41. package/dist/es/models/index.js +2 -0
  42. package/dist/es/shared/keys.d.ts +1 -0
  43. package/dist/es/shared/keys.js +1 -0
  44. package/dist/es/shared/make-installer.d.ts +4 -0
  45. package/dist/es/shared/make-installer.js +13 -0
  46. package/dist/es/shims-vue.d.ts +5 -0
  47. package/dist/es/tokens/index.d.ts +0 -0
  48. package/dist/es/tokens/index.js +0 -0
  49. package/dist/es/types/components.d.ts +17 -0
  50. package/dist/es/utils/bom.d.ts +3 -0
  51. package/dist/es/utils/bom.js +29 -0
  52. package/dist/es/utils/config.d.ts +1 -0
  53. package/dist/es/utils/config.js +11 -0
  54. package/dist/es/utils/index.d.ts +4 -0
  55. package/dist/es/utils/index.js +4 -0
  56. package/dist/es/utils/rule-engine.d.ts +20 -0
  57. package/dist/es/utils/rule-engine.js +50 -0
  58. package/dist/es/utils/template.d.ts +1 -0
  59. package/dist/es/utils/template.js +11 -0
  60. package/dist/lib/all-components.d.ts +3 -0
  61. package/dist/lib/all-components.js +12 -0
  62. package/dist/lib/assets/icons/operation-selected.svg +3 -0
  63. package/dist/lib/assets/icons/operation.svg +3 -0
  64. package/dist/lib/assets/icons/process-path-selected.svg +3 -0
  65. package/dist/lib/assets/icons/process-path.svg +3 -0
  66. package/dist/lib/assets/icons/process-plan-selected.svg +3 -0
  67. package/dist/lib/assets/icons/process-plan.svg +3 -0
  68. package/dist/lib/assets/icons/remove-minus.svg +4 -0
  69. package/dist/lib/assets/icons/remove-plus.svg +5 -0
  70. package/dist/lib/assets/icons/search.svg +4 -0
  71. package/dist/lib/components/bom-tree/index.d.ts +3 -0
  72. package/dist/lib/components/bom-tree/index.js +26 -0
  73. package/dist/lib/components/bom-tree/index.vue +358 -0
  74. package/dist/lib/components/bom-workbench/index.d.ts +3 -0
  75. package/dist/lib/components/bom-workbench/index.js +26 -0
  76. package/dist/lib/components/bom-workbench/index.vue +98 -0
  77. package/dist/lib/components/gray-input/index.d.ts +3 -0
  78. package/dist/lib/components/gray-input/index.js +26 -0
  79. package/dist/lib/components/gray-input/index.vue +44 -0
  80. package/dist/lib/components/index.d.ts +4 -0
  81. package/dist/lib/components/index.js +49 -0
  82. package/dist/lib/components/left-right/index.d.ts +3 -0
  83. package/dist/lib/components/left-right/index.js +26 -0
  84. package/dist/lib/components/left-right/index.vue +142 -0
  85. package/dist/lib/defaults.d.ts +4 -0
  86. package/dist/lib/defaults.js +10 -0
  87. package/dist/lib/hooks/index.d.ts +1 -0
  88. package/dist/lib/hooks/index.js +16 -0
  89. package/dist/lib/hooks/use-ppboms.d.ts +19 -0
  90. package/dist/lib/hooks/use-ppboms.js +94 -0
  91. package/dist/lib/index.d.ts +7 -0
  92. package/dist/lib/index.js +61 -0
  93. package/dist/lib/models/bom.d.ts +35 -0
  94. package/dist/lib/models/bom.js +7 -0
  95. package/dist/lib/models/common.d.ts +5 -0
  96. package/dist/lib/models/common.js +11 -0
  97. package/dist/lib/models/index.d.ts +2 -0
  98. package/dist/lib/models/index.js +27 -0
  99. package/dist/lib/shared/keys.d.ts +1 -0
  100. package/dist/lib/shared/keys.js +7 -0
  101. package/dist/lib/shared/make-installer.d.ts +4 -0
  102. package/dist/lib/shared/make-installer.js +20 -0
  103. package/dist/lib/shims-vue.d.ts +5 -0
  104. package/dist/lib/tokens/index.d.ts +0 -0
  105. package/dist/lib/tokens/index.js +1 -0
  106. package/dist/lib/types/components.d.ts +17 -0
  107. package/dist/lib/utils/bom.d.ts +3 -0
  108. package/dist/lib/utils/bom.js +40 -0
  109. package/dist/lib/utils/config.d.ts +1 -0
  110. package/dist/lib/utils/config.js +18 -0
  111. package/dist/lib/utils/index.d.ts +4 -0
  112. package/dist/lib/utils/index.js +49 -0
  113. package/dist/lib/utils/rule-engine.d.ts +20 -0
  114. package/dist/lib/utils/rule-engine.js +57 -0
  115. package/dist/lib/utils/template.d.ts +1 -0
  116. package/dist/lib/utils/template.js +19 -0
  117. package/dist/style.css +86 -0
  118. package/package.json +65 -0
  119. package/rollup.config.ts +47 -0
  120. package/src/all-components.ts +12 -0
  121. package/src/assets/icons/operation-selected.svg +3 -0
  122. package/src/assets/icons/operation.svg +3 -0
  123. package/src/assets/icons/process-path-selected.svg +3 -0
  124. package/src/assets/icons/process-path.svg +3 -0
  125. package/src/assets/icons/process-plan-selected.svg +3 -0
  126. package/src/assets/icons/process-plan.svg +3 -0
  127. package/src/assets/icons/remove-minus.svg +4 -0
  128. package/src/assets/icons/remove-plus.svg +5 -0
  129. package/src/assets/icons/search.svg +4 -0
  130. package/src/components/bom-tree/index.ts +5 -0
  131. package/src/components/bom-tree/index.vue +377 -0
  132. package/src/components/bom-workbench/index.ts +5 -0
  133. package/src/components/bom-workbench/index.vue +97 -0
  134. package/src/components/gray-input/index.ts +5 -0
  135. package/src/components/gray-input/index.vue +40 -0
  136. package/src/components/index.ts +4 -0
  137. package/src/components/left-right/index.ts +5 -0
  138. package/src/components/left-right/index.vue +149 -0
  139. package/src/defaults.ts +3 -0
  140. package/src/hooks/index.ts +1 -0
  141. package/src/hooks/use-ppboms.ts +97 -0
  142. package/src/index.ts +9 -0
  143. package/src/models/bom.ts +43 -0
  144. package/src/models/common.ts +6 -0
  145. package/src/models/index.ts +2 -0
  146. package/src/shared/keys.ts +1 -0
  147. package/src/shared/make-installer.ts +21 -0
  148. package/src/shims-vue.d.ts +5 -0
  149. package/src/tokens/index.ts +0 -0
  150. package/src/types/components.d.ts +17 -0
  151. package/src/utils/bom.ts +33 -0
  152. package/src/utils/config.ts +11 -0
  153. package/src/utils/index.ts +4 -0
  154. package/src/utils/rule-engine.ts +83 -0
  155. package/src/utils/template.ts +13 -0
  156. package/tsconfig.json +7 -0
  157. package/unocss.config.ts +55 -0
  158. package/vite-env.d.ts +1 -0
  159. package/vite.config.ts +79 -0
@@ -0,0 +1,377 @@
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 { watchDebounced } from '@vueuse/shared'
5
+ import * as _ from 'lodash-es'
6
+ import type { DataNode } from 'ant-design-vue/es/vc-tree-select/interface'
7
+ import type { EventDataNode } from 'ant-design-vue/es/tree'
8
+ import HdGrayInput from '../gray-input'
9
+ import type { BomTreeConfig } from '../../models'
10
+
11
+ const props = defineProps<Props>()
12
+ const emits = defineEmits(['select'])
13
+ const COMPONENT_NAME = 'HdBomTree'
14
+ defineOptions({
15
+ name: COMPONENT_NAME,
16
+ })
17
+
18
+ type TreeNodeWithMeta = DataNode & {
19
+ parentKeys: string[]
20
+ }
21
+
22
+ interface Props {
23
+ treeData: TreeProps['treeData']
24
+ config: BomTreeConfig
25
+ }
26
+
27
+ const expandedKeys = ref<(string | number)[]>([])
28
+ const searchValue = ref<string>('')
29
+ const autoExpandParent = ref<boolean>(true)
30
+ const { treeData: _treeData } = toRefs(props)
31
+ const filteredTreeData = ref<TreeProps['treeData']>([])
32
+ const treeContainerHeight = ref<number>(0)
33
+ const treeContainerId = 'treeContainer'
34
+ let keyToNodeMap = new Map<string, TreeNodeWithMeta>()
35
+ const selectedKeys = ref<string[]>([])
36
+ const initTreeState = async () => {
37
+ searchValue.value = ''
38
+ filteredTreeData.value = _treeData.value
39
+ }
40
+
41
+ const onExpand = (keys: Key[]) => {
42
+ expandedKeys.value = keys
43
+ autoExpandParent.value = false
44
+ }
45
+
46
+ const updateTreeContainerHeight = () => {
47
+ const treeContainer = document.getElementById(treeContainerId)
48
+ if (treeContainer) {
49
+ treeContainerHeight.value = treeContainer.clientHeight
50
+ }
51
+ }
52
+
53
+ const buildKeyToNodeMap = (treeData: TreeProps['treeData']) => {
54
+ const keyMap = new Map<string, TreeNodeWithMeta>()
55
+ const traverse = (nodes: TreeProps['treeData'] = [], parentKeys: string[] = []) => {
56
+ for (const node of nodes) {
57
+ if (parentKeys) {
58
+ keyMap.set(node.key as string, {
59
+ ...node,
60
+ parentKeys: parentKeys || [],
61
+ } as any)
62
+ }
63
+ if (!_.isEmpty(node.children)) {
64
+ traverse(node.children, [...parentKeys, node.key as string])
65
+ }
66
+ }
67
+ }
68
+ traverse(treeData)
69
+ return keyMap
70
+ }
71
+
72
+ const filterTreeData = (data: TreeProps['treeData'], filteredKeys: string[]) => {
73
+ const getNodes = (result: any, node: any) => {
74
+ if (_.includes(filteredKeys, node.key)) {
75
+ result.push({ ...node })
76
+ return result
77
+ }
78
+ if (Array.isArray(node.children)) {
79
+ const children = node.children.reduce(getNodes, [])
80
+ if (!_.isEmpty(children)) {
81
+ result.push({ ...node, children })
82
+ }
83
+ }
84
+ return result
85
+ }
86
+ return (data || []).reduce(getNodes, [])
87
+ }
88
+
89
+ // 获取直接父节点的 key
90
+ const getParentKey = (key: string): string[] | undefined => {
91
+ const treeNode = keyToNodeMap.get(key)
92
+ return treeNode?.parentKeys || []
93
+ }
94
+
95
+ const selectFirstSelectableNode = () => {
96
+ const firstNode: any = _.find(Array.from(keyToNodeMap.values()), node => (node as any)?.selectable)
97
+ if (!firstNode) {
98
+ return
99
+ }
100
+
101
+ nextTick(() => {
102
+ expandedKeys.value = getParentKey(firstNode.key as string) as any
103
+ selectedKeys.value = firstNode ? [firstNode.key] as string[] : []
104
+ })
105
+ }
106
+
107
+ type Key = string | number
108
+ const onSelected = (keys: Key[], { node }: { node: EventDataNode }) => {
109
+ const key = node?.dataRef?.key
110
+ if (key) {
111
+ selectedKeys.value = [key as string]
112
+ }
113
+ else {
114
+ selectedKeys.value = []
115
+ }
116
+ }
117
+
118
+ onMounted(async () => {
119
+ window.addEventListener('resize', updateTreeContainerHeight)
120
+ await nextTick(() => {
121
+ updateTreeContainerHeight()
122
+ })
123
+ })
124
+
125
+ onBeforeUnmount(() => {
126
+ window.removeEventListener('resize', updateTreeContainerHeight)
127
+ })
128
+
129
+ watch(
130
+ () => _treeData.value,
131
+ async () => {
132
+ if (!_.isEmpty(_treeData.value)) {
133
+ keyToNodeMap = buildKeyToNodeMap(_treeData.value)
134
+ await initTreeState()
135
+ selectFirstSelectableNode()
136
+ }
137
+ else {
138
+ keyToNodeMap.clear()
139
+ await initTreeState()
140
+ }
141
+ },
142
+ {
143
+ immediate: true,
144
+ },
145
+ )
146
+
147
+ watchDebounced(
148
+ searchValue, (value: string) => {
149
+ let expanded: any[]
150
+ if (_.isEmpty(value)) {
151
+ expanded = getParentKey(selectedKeys.value[0]) || []
152
+ filteredTreeData.value = _treeData.value
153
+ }
154
+ else {
155
+ const filteredNodes: TreeProps['treeData'] = []
156
+ expanded = (Array.from(keyToNodeMap.values()))
157
+ .map((item: any) => {
158
+ if (hasSearchMatch(item.title)) {
159
+ filteredNodes.push(item)
160
+ return getParentKey(item.key)
161
+ }
162
+ return null
163
+ })
164
+ .filter((item, i, self) => item && self.indexOf(item) === i)
165
+ const filteredKeys = filteredNodes.map(item => item.key)
166
+ filteredTreeData.value = _.isEmpty(filteredKeys) ? [] : filterTreeData(_treeData.value, filteredKeys as unknown as string[])
167
+ }
168
+ expandedKeys.value = expanded
169
+ nextTick(() => {
170
+ expandedKeys.value = _.uniq(expanded.flat(1)) as any as string[]
171
+ })
172
+ }, { debounce: 500 })
173
+
174
+ watch(
175
+ () => selectedKeys.value,
176
+ () => {
177
+ if (selectedKeys.value && selectedKeys.value.length) {
178
+ return emits('select', keyToNodeMap.get(selectedKeys.value[0]))
179
+ }
180
+ },
181
+ )
182
+
183
+ function getIconClass(icon: string, isSelected: boolean) {
184
+ return isSelected ? `i-icon-${icon}-selected` : `i-icon-${icon}`
185
+ }
186
+
187
+ function getTitlePart(title: string, partType: 'before' | 'after') {
188
+ const _searchValue = searchValue.value || ''
189
+ const index = title.indexOf(_searchValue)
190
+
191
+ return {
192
+ before: title.substring(0, index),
193
+ after: title.substring(index + _searchValue.length),
194
+ }[partType]
195
+ }
196
+
197
+ function hasSearchMatch(title: string) {
198
+ return searchValue.value && title.includes(searchValue.value)
199
+ }
200
+ </script>
201
+
202
+ <template>
203
+ <div class="flex flex-col h-100%">
204
+ <div class="search-box">
205
+ <HdGrayInput
206
+ v-model:value="searchValue"
207
+ :placeholder="props.config?.filter?.placeholder || '输入关键词进行筛选(如名称/编号)'"
208
+ class="search-input"
209
+ >
210
+ <template #prefix>
211
+ <div class="i-icon-search w-16px h-16px" />
212
+ </template>
213
+ </HdGrayInput>
214
+ </div>
215
+ <div :id="treeContainerId" class="flex-grow tree-wrapper mt-12px overflow-y-hidden">
216
+ <div v-if="!_.isEmpty(filteredTreeData)">
217
+ <a-tree
218
+ :height="treeContainerHeight"
219
+ :expanded-keys="expandedKeys"
220
+ :auto-expand-parent="autoExpandParent"
221
+ :tree-data="filteredTreeData"
222
+ :block-node="true"
223
+ :selected-keys="selectedKeys"
224
+ @expand="onExpand"
225
+ @select="onSelected"
226
+ >
227
+ <template #title="{ title, dataRef }">
228
+ <div :class="`tree-node-tittle flex items-center ${dataRef.selectable ? 'selectable' : 'not-selectable'}`">
229
+ <div
230
+ v-if="!!dataRef.icon"
231
+ class="icon w-20px h-20px mr-4px min-w-20px"
232
+ :class="[getIconClass(dataRef.icon, selectedKeys.includes(dataRef.key))]"
233
+ />
234
+ <a-dropdown :trigger="['contextmenu']">
235
+ <div v-if="hasSearchMatch(title)" class="tree-node-text">
236
+ <span>{{ getTitlePart(title, 'before') }}</span>
237
+ <span class="highlight" style="background-color: #FFD499">{{ searchValue }}</span>
238
+ <span>{{ getTitlePart(title, 'after') }}</span>
239
+ </div>
240
+ <div v-else class="tree-node-text">
241
+ {{ title }}
242
+ </div>
243
+ <template #overlay>
244
+ <slot name="right-click" :tree-node="dataRef" />
245
+ </template>
246
+ </a-dropdown>
247
+ </div>
248
+ </template>
249
+ <template #switcherIcon="{ dataRef }">
250
+ <div
251
+ v-if="expandedKeys.includes(dataRef.key)" class="i-icon-remove-minus icon w-16px h-16px"
252
+ style="font-size: 20px;"
253
+ />
254
+ <div v-else class="i-icon-remove-plus icon w-16px h-16px" />
255
+ </template>
256
+ </a-tree>
257
+ </div>
258
+ <div v-show="_.isEmpty(filteredTreeData)" class="w-100% h-100% flex justify-center items-center">
259
+ <span>暂无数据</span>
260
+ </div>
261
+ </div>
262
+ </div>
263
+ </template>
264
+
265
+ <style lang="scss" scoped>
266
+ .tree-wrapper {
267
+ background-color: #fff;
268
+ }
269
+
270
+ .search-box {
271
+ padding: 0 16px;
272
+ }
273
+
274
+ :deep(.ant-tree-list) {
275
+ .ant-tree-treenode-motion {
276
+ width: 100%;
277
+ }
278
+
279
+ .ant-tree-treenode {
280
+ width: 100%;
281
+ align-items: center;
282
+ color: #2C2C2C;
283
+ font-size: 16px;
284
+ display: flex;
285
+ justify-content: center;
286
+ padding: 4px 12px;
287
+
288
+ .ant-tree-indent-unit {
289
+ width: 10px;
290
+ }
291
+
292
+ .ant-tree-switcher {
293
+ width: 16px;
294
+ height: 16px;
295
+ padding: 0;
296
+ margin: 0 10px 0 0;
297
+ align-self: center;
298
+ line-height: 16px;
299
+ }
300
+
301
+ .ant-tree-node-content-wrapper {
302
+ transition: none;
303
+ white-space: nowrap;
304
+ text-overflow: ellipsis;
305
+ margin-bottom: 0;
306
+ display: inline-flex;
307
+ overflow: visible !important;
308
+ flex: 1;
309
+ min-width: 0;
310
+
311
+ &:hover {
312
+ background-color: #E9EBED;
313
+ }
314
+
315
+ .ant-tree-title {
316
+ text-overflow: ellipsis;
317
+ white-space: nowrap;
318
+ flex: 1;
319
+ min-width: 0;
320
+
321
+ .tree-node-tittle {
322
+ &.selectable {
323
+ cursor: pointer;
324
+ }
325
+
326
+ &.not-selectable {
327
+ cursor: auto;
328
+ }
329
+ }
330
+ }
331
+ }
332
+
333
+ &:hover {
334
+ background-color: #E9EBED;
335
+ }
336
+
337
+ .ant-tree-node-content-wrapper {
338
+ user-select: auto;
339
+ }
340
+
341
+ &.ant-tree-treenode-selected {
342
+ background-color: #EDF4FF;
343
+ border-radius: 4px;
344
+ color: #1E3B9D;
345
+ font-weight: 700;
346
+
347
+ .ant-tree-switcher, .ant-tree-node-content-wrapper {
348
+ background-color: #EDF4FF;
349
+ }
350
+ }
351
+ }
352
+
353
+ .ant-tree-node-content-wrapper {
354
+ align-items: center;
355
+ padding: 0;
356
+ }
357
+
358
+ .ant-tree-node-content-wrapper {
359
+ display: flex;
360
+ margin-bottom: -4px;
361
+
362
+ .ant-tree-title {
363
+ flex-grow: 1;
364
+ display: block;
365
+ }
366
+ }
367
+ }
368
+
369
+ .tree-node-text {
370
+ display: inline-block;
371
+ max-width: 100%;
372
+ overflow: hidden;
373
+ text-overflow: ellipsis;
374
+ white-space: nowrap;
375
+ vertical-align: middle;
376
+ }
377
+ </style>
@@ -0,0 +1,5 @@
1
+ import { withInstall } from '@handaotech-design/vue'
2
+ import BomWorkbench from './index.vue'
3
+ export * from './index.vue'
4
+ export const HdBomWorkbench = withInstall(BomWorkbench)
5
+ export default HdBomWorkbench
@@ -0,0 +1,97 @@
1
+ <script lang="ts" setup>
2
+ import { ref, watch } from 'vue'
3
+ import * as _ from 'lodash-es'
4
+ import type { BomNode, BomTreeConfig, Optional, WorkBenchLayoutConfig } from '../../models'
5
+ import { convertBomDataToTree } from '../../utils'
6
+ import HdLeftRight from '../left-right/index'
7
+ import HdBomTree from '../bom-tree/index'
8
+
9
+ const props = defineProps<Props>()
10
+ const COMPONENT_NAME = 'HdBomWorkbench'
11
+ defineOptions({
12
+ name: COMPONENT_NAME,
13
+ })
14
+
15
+ interface Props {
16
+ moduleKey: string
17
+ bomData: Optional<BomNode[]>
18
+ treeConfig: BomTreeConfig
19
+ layoutConfig?: WorkBenchLayoutConfig
20
+ }
21
+ const bomDataForTree = ref<BomNode[]>()
22
+ const selectedNode = ref<BomNode>()
23
+
24
+ async function onSelected(data: BomNode) {
25
+ selectedNode.value = data
26
+ }
27
+
28
+ watch(
29
+ () => props.bomData,
30
+ () => {
31
+ if (_.isEmpty(props.bomData)) {
32
+ selectedNode.value = undefined
33
+ bomDataForTree.value = undefined
34
+ }
35
+ else {
36
+ bomDataForTree.value = convertBomDataToTree(props.bomData!, props.treeConfig.nodeConfig.rule)
37
+ }
38
+ },
39
+ { immediate: true },
40
+ )
41
+ </script>
42
+
43
+ <template>
44
+ <HdLeftRight
45
+ v-if="bomDataForTree"
46
+ :module-key="props.moduleKey"
47
+ :max-left-width="props?.layoutConfig?.maxLeftWidth"
48
+ >
49
+ <template #left>
50
+ <div class="left-content">
51
+ <div class="bom-tree-container">
52
+ <HdBomTree
53
+ :tree-data="bomDataForTree"
54
+ :config="props.treeConfig"
55
+ @select="(data: BomNode) => onSelected(data)"
56
+ >
57
+ <template #right-click>
58
+ <slot name="tree-right-click" :bom-node="selectedNode" />
59
+ </template>
60
+ </HdBomTree>
61
+ </div>
62
+ </div>
63
+ </template>
64
+ <template #right>
65
+ <div class="m-20px">
66
+ <slot name="content" :bom-node="selectedNode" />
67
+ </div>
68
+ </template>
69
+ </HdLeftRight>
70
+ </template>
71
+
72
+ <style lang="scss" scoped>
73
+ .v-h-center {
74
+ width: 100%;
75
+ height: 100%;
76
+ display: flex;
77
+ justify-content: center;
78
+ align-items: center;
79
+ transition: all 1s ease;
80
+ }
81
+ .left-content {
82
+ display: flex;
83
+ flex-direction: column;
84
+ height: 100%;
85
+ padding: 20px 0 16px 0;
86
+
87
+ .bom-tree-container {
88
+ flex-grow: 1;
89
+ }
90
+ }
91
+ :deep(.spinning-wrapper) {
92
+ height: 100%;
93
+ .ant-spin-container {
94
+ height: 100%;
95
+ }
96
+ }
97
+ </style>
@@ -0,0 +1,5 @@
1
+ import { withInstall } from '@handaotech-design/vue'
2
+ import GaryInput from './index.vue'
3
+ export * from './index.vue'
4
+ export const HdGrayInput = withInstall(GaryInput)
5
+ export default HdGrayInput
@@ -0,0 +1,40 @@
1
+ <script setup lang="ts">
2
+ const COMPONENT_NAME = 'HdBomGrayInput'
3
+ defineOptions({
4
+ name: COMPONENT_NAME,
5
+ })
6
+ </script>
7
+
8
+ <template>
9
+ <span class="inline-block gray-a-input-wrapper">
10
+ <a-input
11
+ v-bind="$attrs"
12
+ >
13
+ <template v-for="(key, index) in Object.keys($slots)" :key="index" #[key]>
14
+ <slot :name="key" />
15
+ </template>
16
+ </a-input>
17
+ </span>
18
+ </template>
19
+
20
+ <style lang="scss" scoped>
21
+ .gray-a-input-wrapper {
22
+ width: 100%;
23
+ }
24
+ :deep(.ant-input),
25
+ :deep(.ant-input-affix-wrapper) {
26
+ background-color: #F5F5F5;
27
+ border-radius: 2px;
28
+ padding: 6px 12px;
29
+ font-size: 14px;
30
+ border-color: transparent;
31
+ &:hover {
32
+ border-color: #1E3B9D;
33
+ }
34
+ &:focus, &.ant-input-affix-wrapper-focused {
35
+ border-color: #1E3B9D;
36
+ background-color: #fff;
37
+ box-shadow: none;
38
+ }
39
+ }
40
+ </style>
@@ -0,0 +1,4 @@
1
+ export * from './bom-tree'
2
+ export * from './bom-workbench'
3
+ export * from './gray-input'
4
+ export * from './left-right'
@@ -0,0 +1,5 @@
1
+ import { withInstall } from '@handaotech-design/vue'
2
+ import LeftRight from './index.vue'
3
+ export * from './index.vue'
4
+ export const HdLeftRight = withInstall(LeftRight)
5
+ export default HdLeftRight
@@ -0,0 +1,149 @@
1
+ <script lang="ts" setup>
2
+ import { debounce, throttle } from 'lodash-es'
3
+ import { onMounted, onUnmounted, ref } from 'vue'
4
+
5
+ const props = defineProps({
6
+ moduleKey: {
7
+ type: String,
8
+ required: true,
9
+ },
10
+ maxLeftWidth: {
11
+ type: Number,
12
+ required: false,
13
+ },
14
+ })
15
+ const COMPONENT_NAME = 'HdLeftRight'
16
+ defineOptions({
17
+ name: COMPONENT_NAME,
18
+ })
19
+
20
+ // 默认宽度 & 本地存储
21
+ const defaultLeftWidth = 300
22
+ const savedWidth = localStorage.getItem(`${props.moduleKey}LeftPanelWidth`)
23
+ const initialWidth = savedWidth ? parseInt(savedWidth) : defaultLeftWidth
24
+
25
+ // 响应式宽度
26
+ const leftWidth = ref(initialWidth)
27
+ const isDragging = ref(false)
28
+ let startX = 0
29
+ let startWidth = 0
30
+
31
+ // 节流更新UI(60fps)
32
+ const throttledUpdate = throttle((width: number) => {
33
+ leftWidth.value = width
34
+ }, 16)
35
+
36
+ // 防抖保存到本地存储(300ms延迟)
37
+ const debouncedSave = debounce(
38
+ (val: number) => {
39
+ localStorage.setItem(`${props.moduleKey}LeftPanelWidth`, val.toString())
40
+ },
41
+ 300,
42
+ { trailing: true }, // 确保最后一次触发
43
+ )
44
+
45
+ // 处理拖动
46
+ const handleDrag = (e: MouseEvent) => {
47
+ if (!isDragging.value) {
48
+ return
49
+ }
50
+ const delta = e.clientX - startX
51
+ const newWidth = Math.max(
52
+ 200,
53
+ Math.min(props.maxLeftWidth ?? 500, startWidth + delta),
54
+ )
55
+
56
+ throttledUpdate(newWidth) // 节流更新UI
57
+ debouncedSave(newWidth) // 防抖保存
58
+ }
59
+
60
+ // 停止拖动
61
+ const stopDrag = () => {
62
+ isDragging.value = false
63
+ document.removeEventListener('mousemove', handleDrag)
64
+ document.removeEventListener('mouseup', stopDrag)
65
+
66
+ debouncedSave.flush() // 立即执行未完成的保存
67
+ }
68
+
69
+ // 开始拖动
70
+ const startDrag = (e: MouseEvent) => {
71
+ isDragging.value = true
72
+ startX = e.clientX
73
+ startWidth = leftWidth.value
74
+ document.addEventListener('mousemove', handleDrag)
75
+ document.addEventListener('mouseup', stopDrag)
76
+ }
77
+
78
+ // 清理
79
+ onUnmounted(() => {
80
+ document.removeEventListener('mousemove', handleDrag)
81
+ document.removeEventListener('mouseup', stopDrag)
82
+ throttledUpdate.cancel()
83
+ debouncedSave.cancel()
84
+ })
85
+
86
+ // 初始化
87
+ onMounted(() => {
88
+ leftWidth.value = initialWidth
89
+ })
90
+ </script>
91
+
92
+ <template>
93
+ <div class="wrapper">
94
+ <div class="left" :style="{ width: `${leftWidth}px` }">
95
+ <slot name="left" />
96
+ </div>
97
+ <div
98
+ class="divider"
99
+ :style="{ left: `${leftWidth}px` }"
100
+ @mousedown="startDrag"
101
+ >
102
+ <div class="vertical-line" />
103
+ </div>
104
+ <div class="right">
105
+ <slot name="right" />
106
+ </div>
107
+ </div>
108
+ </template>
109
+
110
+ <style scoped lang="scss">
111
+ .wrapper {
112
+ display: flex;
113
+ position: relative;
114
+ height: 100%;
115
+
116
+ .left {
117
+ height: 100%;
118
+ overflow: hidden;
119
+ }
120
+
121
+ .divider {
122
+ position: absolute;
123
+ height: 100%;
124
+ cursor: col-resize;
125
+ z-index: 1;
126
+ transition: background 0.2s;
127
+
128
+ .vertical-line {
129
+ width: 1px;
130
+ height: 100%;
131
+ background: #E9EBED;
132
+ }
133
+
134
+ &:hover {
135
+ .vertical-line {
136
+ background: #2e72d2;
137
+ width: 2px;
138
+ }
139
+ }
140
+ }
141
+
142
+ .right {
143
+ flex: 1;
144
+ overflow-y: auto;
145
+ overflow-x: hidden;
146
+ padding: 0 16px;
147
+ }
148
+ }
149
+ </style>
@@ -0,0 +1,3 @@
1
+ import { makeInstaller } from './shared/make-installer'
2
+ import AllComponents from './all-components'
3
+ export default makeInstaller([...AllComponents])
@@ -0,0 +1 @@
1
+ export * from './use-ppboms'