@netang/quasar 0.1.21 → 0.1.23

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 (219) hide show
  1. package/.editorconfig +12 -12
  2. package/_docs/docs/components/field-table.md +58 -58
  3. package/_docs/docs/components/field-tree.md +21 -21
  4. package/_docs/docs/components/table.md +24 -24
  5. package/_docs/docs/utils/table.md +196 -196
  6. package/components/editor-code/index.vue +325 -325
  7. package/components/field-table/index.vue +1227 -1227
  8. package/components/field-tree/index.vue +757 -759
  9. package/components/private/components/move-to-tree/index.vue +2 -2
  10. package/components/private/edit-power-data/index.vue +846 -846
  11. package/components/private/table-visible-columns-button/index.vue +109 -109
  12. package/components/render/index.vue +127 -127
  13. package/components/search-item/index.vue +212 -212
  14. package/components/select/index.vue +177 -177
  15. package/components/table/index.vue +471 -471
  16. package/components/table-splitter/index.vue +377 -377
  17. package/components/table-summary/index.vue +107 -107
  18. package/components/tree/index.vue +1636 -0
  19. package/docs/404.html +33 -33
  20. package/docs/assets/404.html-60b35caa.js +1 -1
  21. package/docs/assets/404.html-d1e63d77.js +1 -1
  22. package/docs/assets/alert.html-b2a2a72f.js +5 -5
  23. package/docs/assets/alert.html-ba46d137.js +1 -1
  24. package/docs/assets/app-9f30aa4b.js +6 -6
  25. package/docs/assets/area.html-01b9b58d.js +42 -42
  26. package/docs/assets/area.html-9a4fce6a.js +1 -1
  27. package/docs/assets/arr.html-145d27e7.js +1 -1
  28. package/docs/assets/arr.html-674e65ab.js +11 -11
  29. package/docs/assets/auth.html-579fa830.js +1 -1
  30. package/docs/assets/auth.html-8544ed95.js +8 -8
  31. package/docs/assets/bus.html-c71254aa.js +1 -1
  32. package/docs/assets/bus.html-dc7d3d19.js +6 -6
  33. package/docs/assets/column-title.html-c735cb5a.js +3 -3
  34. package/docs/assets/column-title.html-e9316762.js +1 -1
  35. package/docs/assets/confirm.html-ddfdc27f.js +10 -10
  36. package/docs/assets/confirm.html-ef3e2bef.js +1 -1
  37. package/docs/assets/copy.html-d20345b6.js +1 -1
  38. package/docs/assets/copy.html-ef8c8571.js +13 -13
  39. package/docs/assets/data.html-6432175d.js +30 -30
  40. package/docs/assets/data.html-a3b05d5b.js +1 -1
  41. package/docs/assets/dialog.html-1f698e5a.js +1 -1
  42. package/docs/assets/dialog.html-62902b83.js +68 -68
  43. package/docs/assets/dialog.html-baea77c9.js +1 -1
  44. package/docs/assets/dialog.html-bb082fc4.js +1 -1
  45. package/docs/assets/dict.html-1311da3d.js +23 -23
  46. package/docs/assets/dict.html-b96fbf0c.js +1 -1
  47. package/docs/assets/dictOptions.html-7c4f40a5.js +1 -1
  48. package/docs/assets/dictOptions.html-fb99d175.js +5 -5
  49. package/docs/assets/dragger.html-668d3efa.js +1 -1
  50. package/docs/assets/dragger.html-749d585a.js +1 -1
  51. package/docs/assets/editor-code.html-6ab26ea9.js +1 -1
  52. package/docs/assets/editor-code.html-d196205d.js +1 -1
  53. package/docs/assets/empty.html-1c139131.js +1 -1
  54. package/docs/assets/empty.html-1e9c441d.js +1 -1
  55. package/docs/assets/field-date.html-069fdb13.js +1 -1
  56. package/docs/assets/field-date.html-ad204aa9.js +1 -1
  57. package/docs/assets/field-table.html-ce480f03.js +1 -1
  58. package/docs/assets/field-table.html-d9236160.js +1 -1
  59. package/docs/assets/field-text.html-7277c62f.js +1 -1
  60. package/docs/assets/field-text.html-ccb4cecf.js +1 -1
  61. package/docs/assets/field-tree.html-519bfb45.js +1 -1
  62. package/docs/assets/field-tree.html-fdc748d6.js +1 -1
  63. package/docs/assets/form.html-2b562c37.js +2 -2
  64. package/docs/assets/form.html-75104cd5.js +1 -1
  65. package/docs/assets/framework-204010b2.js +5 -5
  66. package/docs/assets/getData.html-990e3787.js +1 -1
  67. package/docs/assets/getData.html-bb72025f.js +34 -34
  68. package/docs/assets/getFile.html-42368004.js +1 -1
  69. package/docs/assets/getFile.html-99abd054.js +3 -3
  70. package/docs/assets/getImage.html-3429c5a1.js +1 -1
  71. package/docs/assets/getImage.html-4d886d83.js +3 -3
  72. package/docs/assets/getTime.html-7435f922.js +1 -1
  73. package/docs/assets/getTime.html-b37f49eb.js +20 -20
  74. package/docs/assets/img.html-7d1da657.js +1 -1
  75. package/docs/assets/img.html-fbea1105.js +1 -1
  76. package/docs/assets/index.html-1695dd7c.js +1 -1
  77. package/docs/assets/index.html-65a4aa67.js +1 -1
  78. package/docs/assets/index.html-7b98d5bd.js +1 -1
  79. package/docs/assets/index.html-c01f2648.js +1 -1
  80. package/docs/assets/input-number.html-0b250d2a.js +1 -1
  81. package/docs/assets/input-number.html-a8eb0378.js +1 -1
  82. package/docs/assets/list-menu-item.html-7f1b4611.js +1 -1
  83. package/docs/assets/list-menu-item.html-84ed5ab8.js +1 -1
  84. package/docs/assets/list-menu.html-28b4163f.js +1 -1
  85. package/docs/assets/list-menu.html-cb6ba95b.js +1 -1
  86. package/docs/assets/loading.html-dae9e39d.js +6 -6
  87. package/docs/assets/loading.html-dc74c9e6.js +1 -1
  88. package/docs/assets/notify.html-e6c4c514.js +1 -1
  89. package/docs/assets/notify.html-f2c4d914.js +8 -8
  90. package/docs/assets/power-page.html-32e02f82.js +1 -1
  91. package/docs/assets/power-page.html-485e77da.js +1 -1
  92. package/docs/assets/power.html-d258cc19.js +93 -93
  93. package/docs/assets/power.html-e490bd32.js +1 -1
  94. package/docs/assets/previewImage.html-6a6b4245.js +1 -1
  95. package/docs/assets/previewImage.html-c5b7e945.js +2 -2
  96. package/docs/assets/price.html-1882c548.js +19 -19
  97. package/docs/assets/price.html-94d3f5be.js +1 -1
  98. package/docs/assets/price.html-d213df0f.js +1 -1
  99. package/docs/assets/price.html-deaf880f.js +1 -1
  100. package/docs/assets/render.html-8efcbdd4.js +1 -1
  101. package/docs/assets/render.html-df228e38.js +1 -1
  102. package/docs/assets/rule.html-2cd57fc2.js +13 -13
  103. package/docs/assets/rule.html-61662001.js +1 -1
  104. package/docs/assets/ruleValid.html-04fe2552.js +1 -1
  105. package/docs/assets/ruleValid.html-e0a776af.js +14 -14
  106. package/docs/assets/search-0782d0d1.svg +1 -1
  107. package/docs/assets/search-item.html-3f75394c.js +1 -1
  108. package/docs/assets/search-item.html-4e942ecd.js +1 -1
  109. package/docs/assets/search.html-2807043e.js +1 -1
  110. package/docs/assets/search.html-c24f8806.js +1 -1
  111. package/docs/assets/select.html-00d0607c.js +1 -1
  112. package/docs/assets/select.html-de7731f5.js +1 -1
  113. package/docs/assets/splitter.html-56f51a70.js +1 -1
  114. package/docs/assets/splitter.html-f5c836d7.js +1 -1
  115. package/docs/assets/style-161e43ab.css +1 -1
  116. package/docs/assets/symbols.html-a6aea4bf.js +1 -1
  117. package/docs/assets/symbols.html-b1f65bad.js +21 -21
  118. package/docs/assets/table-column-fixed.html-3a69e7b2.js +1 -1
  119. package/docs/assets/table-column-fixed.html-e763c38b.js +1 -1
  120. package/docs/assets/table-pagination.html-236934d3.js +1 -1
  121. package/docs/assets/table-pagination.html-c37ee2ac.js +1 -1
  122. package/docs/assets/table-splitter.html-07eab15c.js +1 -1
  123. package/docs/assets/table-splitter.html-7670ee65.js +1 -1
  124. package/docs/assets/table-summary.html-04db434f.js +1 -1
  125. package/docs/assets/table-summary.html-943c65a0.js +1 -1
  126. package/docs/assets/table.html-36253ad7.js +1 -1
  127. package/docs/assets/table.html-7f9c5d1b.js +38 -38
  128. package/docs/assets/table.html-93d53dc8.js +1 -1
  129. package/docs/assets/table.html-ac99b9cb.js +1 -1
  130. package/docs/assets/thumbnail.html-bab1976b.js +1 -1
  131. package/docs/assets/thumbnail.html-eb64e5e8.js +1 -1
  132. package/docs/assets/timestamp.html-4e54f79b.js +13 -13
  133. package/docs/assets/timestamp.html-d0e1b88a.js +1 -1
  134. package/docs/assets/toast.html-58ecbe21.js +1 -1
  135. package/docs/assets/toast.html-c9b9d36b.js +6 -6
  136. package/docs/assets/toolbar.html-83d9f97c.js +1 -1
  137. package/docs/assets/toolbar.html-ff7b8c92.js +1 -1
  138. package/docs/assets/tree.html-d07cbe79.js +23 -23
  139. package/docs/assets/tree.html-ea04193e.js +1 -1
  140. package/docs/assets/uploader-query.html-05590718.js +1 -1
  141. package/docs/assets/uploader-query.html-3175bac5.js +1 -1
  142. package/docs/assets/uploader.html-36da4394.js +2 -2
  143. package/docs/assets/uploader.html-6b5f3079.js +1 -1
  144. package/docs/assets/uploader.html-b9340b57.js +1 -1
  145. package/docs/assets/uploader.html-bc1c22e3.js +1 -1
  146. package/docs/assets/value-format.html-8ae3d47d.js +1 -1
  147. package/docs/assets/value-format.html-afa99b3d.js +1 -1
  148. package/docs/components/column-title.html +35 -35
  149. package/docs/components/data.html +62 -62
  150. package/docs/components/dialog.html +33 -33
  151. package/docs/components/dragger.html +33 -33
  152. package/docs/components/editor-code.html +33 -33
  153. package/docs/components/empty.html +33 -33
  154. package/docs/components/field-date.html +33 -33
  155. package/docs/components/field-table.html +33 -33
  156. package/docs/components/field-text.html +33 -33
  157. package/docs/components/field-tree.html +33 -33
  158. package/docs/components/img.html +33 -33
  159. package/docs/components/input-number.html +33 -33
  160. package/docs/components/list-menu-item.html +33 -33
  161. package/docs/components/list-menu.html +33 -33
  162. package/docs/components/power-page.html +33 -33
  163. package/docs/components/price.html +33 -33
  164. package/docs/components/render.html +33 -33
  165. package/docs/components/search-item.html +33 -33
  166. package/docs/components/search.html +33 -33
  167. package/docs/components/select.html +33 -33
  168. package/docs/components/splitter.html +33 -33
  169. package/docs/components/table-column-fixed.html +33 -33
  170. package/docs/components/table-pagination.html +33 -33
  171. package/docs/components/table-splitter.html +33 -33
  172. package/docs/components/table-summary.html +33 -33
  173. package/docs/components/table.html +33 -33
  174. package/docs/components/thumbnail.html +33 -33
  175. package/docs/components/toolbar.html +33 -33
  176. package/docs/components/uploader-query.html +33 -33
  177. package/docs/components/uploader.html +33 -33
  178. package/docs/components/value-format.html +33 -33
  179. package/docs/index.html +33 -33
  180. package/docs/utils/alert.html +37 -37
  181. package/docs/utils/area.html +74 -74
  182. package/docs/utils/arr.html +43 -43
  183. package/docs/utils/auth.html +40 -40
  184. package/docs/utils/bus.html +38 -38
  185. package/docs/utils/confirm.html +42 -42
  186. package/docs/utils/copy.html +45 -45
  187. package/docs/utils/dialog.html +100 -100
  188. package/docs/utils/dict.html +55 -55
  189. package/docs/utils/dictOptions.html +37 -37
  190. package/docs/utils/form.html +34 -34
  191. package/docs/utils/getData.html +66 -66
  192. package/docs/utils/getFile.html +35 -35
  193. package/docs/utils/getImage.html +35 -35
  194. package/docs/utils/getTime.html +52 -52
  195. package/docs/utils/index.html +33 -33
  196. package/docs/utils/loading.html +38 -38
  197. package/docs/utils/notify.html +40 -40
  198. package/docs/utils/power.html +125 -125
  199. package/docs/utils/previewImage.html +34 -34
  200. package/docs/utils/price.html +51 -51
  201. package/docs/utils/rule.html +45 -45
  202. package/docs/utils/ruleValid.html +46 -46
  203. package/docs/utils/symbols.html +53 -53
  204. package/docs/utils/table.html +70 -70
  205. package/docs/utils/timestamp.html +45 -45
  206. package/docs/utils/toast.html +38 -38
  207. package/docs/utils/tree.html +55 -55
  208. package/docs/utils/uploader.html +34 -34
  209. package/package.json +1 -1
  210. package/sass/common.scss +179 -179
  211. package/sass/variables.scss +138 -138
  212. package/utils/$form.js +68 -68
  213. package/utils/$power.js +1225 -1225
  214. package/utils/$render.js +75 -75
  215. package/utils/$table.js +1171 -1171
  216. package/utils/$tree.js +655 -712
  217. package/utils/index.js +62 -62
  218. package/utils/timestamp.js +18 -18
  219. package/utils/useSearch.js +496 -496
@@ -0,0 +1,1636 @@
1
+ <template>
2
+ <q-virtual-scroll
3
+ :class="classes"
4
+ :items="currentChildren"
5
+ :virtual-scroll-slice-ratio-before="virtualScrollSliceRatioBefore"
6
+ :virtual-scroll-slice-ratio-after="virtualScrollSliceRatioAfter"
7
+ :virtual-scroll-item-size="virtualScrollItemSize"
8
+ separator
9
+ v-bind="virtualScrollProps"
10
+ v-slot="{ item, index }"
11
+ >
12
+ <div
13
+ :class="item.classes"
14
+ :key="item.key"
15
+ :style="{ paddingLeft: (item.level * 16) + 'px' }"
16
+ @mousedown.self="onMouseDown($event, item)"
17
+ @mouseup="onDragEnd"
18
+ @dragstart.stop="onDragStart($event, item)"
19
+ @dragenter.stop="onDragEnter($event, item, false)"
20
+ @dragover.stop="onDragEnter($event, item, true)"
21
+ @dragleave.stop="onDragLeave($event, item)"
22
+ @dragend.stop="onDragEnd"
23
+ :draggable="draggable"
24
+ >
25
+ <div class="q-focus-helper" @click="onClick(item.node, item.m, $event)"></div>
26
+
27
+ <!-- 旋转器 -->
28
+ <q-spinner
29
+ class="q-tree__spinner q-ml-sm"
30
+ :color="computedControlColor"
31
+ v-if="item.m.lazy === 'loading'"
32
+ />
33
+
34
+ <!-- 箭头 -->
35
+ <q-icon
36
+ class="q-tree__arrow q-ml-sm"
37
+ :name="computedIcon"
38
+ :class="`${item.m.expanded ? 'q-tree__arrow--rotate' : ''}`"
39
+ @click="onExpandClick(item.node, item.m, $event)"
40
+ v-if="item.isParent === true"
41
+ />
42
+ <div class="n-tree__arrow--noop q-ml-sm" v-else></div>
43
+
44
+ <!-- 复选框-->
45
+ <q-checkbox
46
+ class="q-tree__tickbox"
47
+ :model-value="item.m.indeterminate === true ? null : item.m.ticked"
48
+ :color="computedControlColor"
49
+ :dark="isDark"
50
+ :disable="item.m.tickable !== true"
51
+ dense
52
+ keep-color
53
+ @keydown="stopAndPrevent"
54
+ @update:model-value="v => { onTickedClick(item.m, v) }"
55
+ v-show="! multiple"
56
+ v-if="item.m.hasTicking === true && item.m.noTick !== true"
57
+ />
58
+
59
+ <!-- 节点内容 -->
60
+ <div
61
+ class="n-tree__node-content"
62
+ @click="onClick(item.node, item.m, $event)"
63
+ >
64
+ <!-- 默认头部插槽 -->
65
+ <slot
66
+ name="default-header"
67
+ :node="item.node"
68
+ :expanded="item.m.expanded"
69
+ :ticked="item.m.indeterminate === true ? null : item.m.ticked"
70
+ :key="item.key"
71
+ :dark="isDark"
72
+ v-if="hasSlotDefault"
73
+ />
74
+ <!-- 否则为文字 -->
75
+ <span v-else>{{ item.label }}</span>
76
+ </div>
77
+ </div>
78
+ </q-virtual-scroll>
79
+ </template>
80
+
81
+ <script>
82
+ import {
83
+ ref, computed, watch,
84
+ nextTick, getCurrentInstance, onBeforeUpdate, onMounted, onUnmounted
85
+ } from 'vue'
86
+
87
+ import { stopAndPrevent } from 'quasar/src/utils/event.js'
88
+
89
+ const tickStrategyOptions = [ 'none', 'strict', 'leaf', 'leaf-filtered' ]
90
+
91
+ import $n_has from 'lodash/has'
92
+ import $n_get from 'lodash/get'
93
+ import $n_isFunction from 'lodash/isFunction'
94
+ import $n_findIndex from 'lodash/findIndex'
95
+
96
+ import $n_isValidArray from '@netang/utils/isValidArray'
97
+ import $n_indexOf from '@netang/utils/indexOf'
98
+ import $n_on from '@netang/utils/on'
99
+ import $n_off from '@netang/utils/off'
100
+
101
+ export default {
102
+
103
+ /**
104
+ * 标识
105
+ */
106
+ name: 'NTree',
107
+
108
+ /**
109
+ * 声明属性
110
+ */
111
+ props: {
112
+ dark: {
113
+ type: Boolean,
114
+ default: null
115
+ },
116
+ nodes: {
117
+ type: [ Array, Object ],
118
+ required: true
119
+ },
120
+ nodeKey: {
121
+ type: String,
122
+ required: true
123
+ },
124
+ labelKey: {
125
+ type: String,
126
+ default: 'label'
127
+ },
128
+ childrenKey: {
129
+ type: String,
130
+ default: 'children'
131
+ },
132
+
133
+ dense: Boolean,
134
+
135
+ color: String,
136
+ controlColor: String,
137
+ textColor: String,
138
+ selectedColor: String,
139
+
140
+ icon: String,
141
+
142
+ tickStrategy: {
143
+ type: String,
144
+ default: 'none',
145
+ validator: v => tickStrategyOptions.includes(v)
146
+ },
147
+ ticked: Array, // v-model:ticked
148
+ expanded: Array, // v-model:expanded
149
+ selected: {}, // v-model:selected
150
+
151
+ noSelectionUnset: Boolean,
152
+
153
+ defaultExpandAll: Boolean,
154
+ accordion: Boolean,
155
+
156
+ filter: String,
157
+ filterMethod: Function,
158
+
159
+ duration: Number,
160
+ noConnectors: Boolean,
161
+ noTransition: Boolean,
162
+
163
+ noNodesLabel: String,
164
+ noResultsLabel: String,
165
+
166
+ // 【覆盖声明】-------------------------------------------------------
167
+
168
+ // 是否开启拖拽
169
+ draggable: Boolean,
170
+ // 是否禁止上下推拽
171
+ noDragUpDown: Boolean,
172
+ // 判断节点能否被拖拽
173
+ allowDrag: Function,
174
+ // 拖拽时判定目标节点能否被放置, type 参数: top / inner / bottom / none ( 目标节点上方 / 目标节点内部 / 目标节点下方 / 无任何操作 )
175
+ allowDrop: Function,
176
+ // 是否多选
177
+ multiple: Boolean,
178
+ // 可见区域中要在其之前渲染的行数的比率
179
+ virtualScrollSliceRatioBefore: {
180
+ type: [ Number, String ],
181
+ default: 1,
182
+ },
183
+ // 可见区域中要在其之后渲染的行数的比率
184
+ virtualScrollSliceRatioAfter: {
185
+ type: [ Number, String ],
186
+ default: 1,
187
+ },
188
+ // 以像素为单位的默认行大小
189
+ virtualScrollItemSize: {
190
+ type: [ Number, String ],
191
+ default: 29,
192
+ },
193
+ // 虚拟滚动声明
194
+ virtualScrollProps: Object,
195
+ },
196
+
197
+ /**
198
+ * 声明事件
199
+ */
200
+ emits: [
201
+ 'update:expanded',
202
+ 'update:ticked',
203
+ 'update:selected',
204
+ 'lazyLoad',
205
+ 'afterShow',
206
+ 'afterHide',
207
+
208
+ 'nodeDragStart',
209
+ 'nodeDragEnter',
210
+ 'nodeDragOver',
211
+ 'nodeDragLeave',
212
+ 'nodeDragEnd',
213
+ ],
214
+
215
+ /**
216
+ * 组合式
217
+ */
218
+ setup (props, { slots, emit }) {
219
+
220
+ const { proxy } = getCurrentInstance()
221
+ const { $q } = proxy
222
+
223
+ // ==========【数据】=========================================================================================
224
+
225
+ // 懒加载
226
+ const lazy = ref({})
227
+ // 已选节点数组
228
+ const innerTicked = ref(props.ticked || [])
229
+ // 已展开节点数组
230
+ const innerExpanded = ref(props.expanded || [])
231
+
232
+ let blurTargets = {}
233
+
234
+ // 是否拖拽中
235
+ const dragging = ref(false)
236
+ // 拖拽中节点
237
+ const draggingNode = ref(null)
238
+ // 放置节点
239
+ const dropNode = ref(null)
240
+
241
+ const dragClasses = ref({})
242
+ const ctrlDown = ref(false)
243
+ const shiftDown = ref(false)
244
+ const shiftDownNodeKey = ref(null)
245
+
246
+ // ==========【计算属性】=========================================================================================
247
+
248
+ /**
249
+ * 是否暗黑模式
250
+ */
251
+ const isDark = computed(() => (
252
+ props.dark === null
253
+ ? $q.dark.isActive
254
+ : props.dark
255
+ ))
256
+
257
+ /**
258
+ * 类名
259
+ */
260
+ const classes = computed(() =>
261
+ `n-tree n-tree--${ props.dense === true ? 'dense' : 'standard' }`
262
+ // + (props.noConnectors === true ? ' q-tree--no-connectors' : '')
263
+ + (isDark.value === true ? ' n-tree--dark' : '')
264
+ + (props.color !== void 0 ? ` text-${ props.color }` : '')
265
+ )
266
+
267
+ /**
268
+ * 是否有已选数据
269
+ */
270
+ const hasSelection = computed(() => props.selected !== void 0)
271
+
272
+ /**
273
+ * 图标
274
+ */
275
+ const computedIcon = computed(() => props.icon || $q.iconSet.tree.icon)
276
+
277
+ /**
278
+ * 控件颜色(复选框 / 旋转器)
279
+ */
280
+ const computedControlColor = computed(() => props.controlColor || props.color)
281
+
282
+ /**
283
+ * 文本颜色类名
284
+ */
285
+ // const textColorClass = computed(() => (
286
+ // props.textColor !== void 0
287
+ // ? ` text-${ props.textColor }`
288
+ // : ''
289
+ // ))
290
+
291
+ /**
292
+ * 已选颜色类名
293
+ */
294
+ // const selectedColorClass = computed(() => {
295
+ // const color = props.selectedColor || props.color
296
+ // return color ? ` text-${ color }` : ''
297
+ // })
298
+
299
+ /**
300
+ * 过滤节点方法
301
+ */
302
+ const computedFilterMethod = computed(() => (
303
+ props.filterMethod !== void 0
304
+ ? props.filterMethod
305
+ : function (node, filter) {
306
+ return node[ props.labelKey ]
307
+ && node[ props.labelKey ].toLowerCase().indexOf(filter.toLowerCase()) > -1
308
+ }
309
+ ))
310
+
311
+ /**
312
+ * meta
313
+ */
314
+ const meta = computed(() => {
315
+ const meta = {}
316
+
317
+ const travel = (node, parent) => {
318
+ const tickStrategy = node.tickStrategy || (parent ? parent.tickStrategy : props.tickStrategy)
319
+ const
320
+ key = node[ props.nodeKey ],
321
+ isParent = node[ props.childrenKey ] && node[ props.childrenKey ].length > 0,
322
+ selectable = node.disabled !== true && hasSelection.value === true && node.selectable !== false,
323
+ expandable = node.disabled !== true && node.expandable !== false,
324
+ hasTicking = tickStrategy !== 'none',
325
+ strictTicking = tickStrategy === 'strict',
326
+ leafFilteredTicking = tickStrategy === 'leaf-filtered',
327
+ leafTicking = tickStrategy === 'leaf' || tickStrategy === 'leaf-filtered'
328
+
329
+ let tickable = node.disabled !== true && node.tickable !== false
330
+ if (leafTicking === true && tickable === true && parent && parent.tickable !== true) {
331
+ tickable = false
332
+ }
333
+
334
+ let localLazy = node.lazy
335
+ if (
336
+ localLazy === true
337
+ && lazy.value[ key ] !== void 0
338
+ && Array.isArray(node[ props.childrenKey ]) === true
339
+ ) {
340
+ localLazy = lazy.value[ key ]
341
+ }
342
+
343
+ const m = {
344
+ key,
345
+ parent,
346
+ isParent,
347
+ lazy: localLazy,
348
+ disabled: node.disabled,
349
+ // link: node.disabled !== true && (selectable === true || (expandable === true && (isParent === true || localLazy === true))),
350
+ children: [],
351
+ matchesFilter: props.filter ? computedFilterMethod.value(node, props.filter) : true,
352
+
353
+ selected: key === props.selected && selectable === true,
354
+ selectable,
355
+ expanded: isParent === true ? innerExpanded.value.includes(key) : false,
356
+ expandable,
357
+ noTick: node.noTick === true || (strictTicking !== true && localLazy && localLazy !== 'loaded'),
358
+ tickable,
359
+ tickStrategy,
360
+ hasTicking,
361
+ strictTicking,
362
+ leafFilteredTicking,
363
+ leafTicking,
364
+ ticked: strictTicking === true
365
+ ? innerTicked.value.includes(key)
366
+ : (isParent === true ? false : innerTicked.value.includes(key)),
367
+
368
+ childrens: [],
369
+ }
370
+
371
+ meta[ key ] = m
372
+
373
+ if (isParent === true) {
374
+ m.childrens = node[ props.childrenKey ]
375
+ m.children = node[ props.childrenKey ].map(n => travel(n, m))
376
+
377
+ if (props.filter) {
378
+ if (m.matchesFilter !== true) {
379
+ m.matchesFilter = m.children.some(n => n.matchesFilter)
380
+ }
381
+ else if (
382
+ m.noTick !== true
383
+ && m.disabled !== true
384
+ && m.tickable === true
385
+ && leafFilteredTicking === true
386
+ && m.children.every(n => n.matchesFilter !== true || n.noTick === true || n.tickable !== true) === true
387
+ ) {
388
+ m.tickable = false
389
+ }
390
+ }
391
+
392
+ if (m.matchesFilter === true) {
393
+ if (m.noTick !== true && strictTicking !== true && m.children.every(n => n.noTick) === true) {
394
+ m.noTick = true
395
+ }
396
+
397
+ if (leafTicking) {
398
+ m.ticked = false
399
+ m.indeterminate = m.children.some(node => node.indeterminate === true)
400
+ m.tickable = m.tickable === true && m.children.some(node => node.tickable)
401
+
402
+ if (m.indeterminate !== true) {
403
+ const sel = m.children
404
+ .reduce((acc, meta) => (meta.ticked === true ? acc + 1 : acc), 0)
405
+
406
+ if (sel === m.children.length) {
407
+ m.ticked = true
408
+ }
409
+ else if (sel > 0) {
410
+ m.indeterminate = true
411
+ }
412
+ }
413
+
414
+ if (m.indeterminate === true) {
415
+ m.indeterminateNextState = m.children
416
+ .every(meta => meta.tickable !== true || meta.ticked !== true)
417
+ }
418
+ }
419
+ }
420
+ }
421
+
422
+ return m
423
+ }
424
+
425
+ props.nodes.forEach(node => travel(node, null))
426
+ return meta
427
+ })
428
+
429
+ // ==========【监听数据】=========================================================================================
430
+
431
+ /**
432
+ * 监听已选节点数据
433
+ */
434
+ watch(() => props.ticked, val => {
435
+ innerTicked.value = val
436
+ })
437
+
438
+ /**
439
+ * 监听已展开节点数据
440
+ */
441
+ watch(() => props.expanded, val => {
442
+ innerExpanded.value = val
443
+ })
444
+
445
+ /**
446
+ * 通过指定 key 获取树节点
447
+ */
448
+ function getNodeByKey (key) {
449
+ const reduce = [].reduce
450
+
451
+ const find = (result, node) => {
452
+ if (result || !node) {
453
+ return result
454
+ }
455
+ if (Array.isArray(node) === true) {
456
+ return reduce.call(Object(node), find, result)
457
+ }
458
+ if (node[ props.nodeKey ] === key) {
459
+ return node
460
+ }
461
+ if (node[ props.childrenKey ]) {
462
+ return find(null, node[ props.childrenKey ])
463
+ }
464
+ }
465
+
466
+ return find(null, props.nodes)
467
+ }
468
+
469
+ /**
470
+ * 获取已选树节点数组
471
+ */
472
+ function getTickedNodes () {
473
+ return innerTicked.value.map(key => getNodeByKey(key))
474
+ }
475
+
476
+ /**
477
+ * 获取已展开树节点数组
478
+ */
479
+ function getExpandedNodes () {
480
+ return innerExpanded.value.map(key => getNodeByKey(key))
481
+ }
482
+
483
+ /**
484
+ * 确定节点是否展开
485
+ */
486
+ function isExpanded (key) {
487
+ return key && meta.value[ key ]
488
+ ? meta.value[ key ].expanded
489
+ : false
490
+ }
491
+
492
+ /**
493
+ * 用于折叠树的所有分支
494
+ */
495
+ function collapseAll () {
496
+ if (props.expanded !== void 0) {
497
+ emit('update:expanded', [])
498
+ }
499
+ else {
500
+ innerExpanded.value = []
501
+ }
502
+ }
503
+
504
+ /**
505
+ * 用于展开树的所有分支
506
+ */
507
+ function expandAll () {
508
+ const
509
+ expanded = innerExpanded.value,
510
+ travel = node => {
511
+ if (node[ props.childrenKey ] && node[ props.childrenKey ].length > 0) {
512
+ if (node.expandable !== false && node.disabled !== true) {
513
+ expanded.push(node[ props.nodeKey ])
514
+ node[ props.childrenKey ].forEach(travel)
515
+ }
516
+ }
517
+ }
518
+
519
+ props.nodes.forEach(travel)
520
+
521
+ if (props.expanded !== void 0) {
522
+ emit('update:expanded', expanded)
523
+ }
524
+ else {
525
+ innerExpanded.value = expanded
526
+ }
527
+ }
528
+
529
+ /**
530
+ * 在给定键的节点处展开树
531
+ */
532
+ function setExpanded (key, state, node = getNodeByKey(key), m = meta.value[ key ]) {
533
+ if (m.lazy && m.lazy !== 'loaded') {
534
+ if (m.lazy === 'loading') {
535
+ return
536
+ }
537
+
538
+ lazy.value[ key ] = 'loading'
539
+ if (Array.isArray(node[ props.childrenKey ]) !== true) {
540
+ node[ props.childrenKey ] = []
541
+ }
542
+ emit('lazyLoad', {
543
+ node,
544
+ key,
545
+ done: children => {
546
+ lazy.value[ key ] = 'loaded'
547
+ node[ props.childrenKey ] = Array.isArray(children) === true ? children : []
548
+ nextTick(() => {
549
+ const localMeta = meta.value[ key ]
550
+ if (localMeta && localMeta.isParent === true) {
551
+ localSetExpanded(key, true)
552
+ }
553
+ })
554
+ },
555
+ fail: () => {
556
+ delete lazy.value[ key ]
557
+ if (node[ props.childrenKey ].length === 0) {
558
+ delete node[ props.childrenKey ]
559
+ }
560
+ }
561
+ })
562
+ }
563
+ else if (m.isParent === true && m.expandable === true) {
564
+ localSetExpanded(key, state)
565
+ }
566
+ }
567
+
568
+ function localSetExpanded (key, state) {
569
+ let target = innerExpanded.value
570
+ const shouldEmit = props.expanded !== void 0
571
+
572
+ if (shouldEmit === true) {
573
+ target = target.slice()
574
+ }
575
+
576
+ if (state) {
577
+ if (props.accordion) {
578
+ if (meta.value[ key ]) {
579
+ const collapse = []
580
+ if (meta.value[ key ].parent) {
581
+ meta.value[ key ].parent.children.forEach(m => {
582
+ if (m.key !== key && m.expandable === true) {
583
+ collapse.push(m.key)
584
+ }
585
+ })
586
+ }
587
+ else {
588
+ props.nodes.forEach(node => {
589
+ const k = node[ props.nodeKey ]
590
+ if (k !== key) {
591
+ collapse.push(k)
592
+ }
593
+ })
594
+ }
595
+ if (collapse.length > 0) {
596
+ target = target.filter(k => collapse.includes(k) === false)
597
+ }
598
+ }
599
+ }
600
+
601
+ target = target.concat([ key ])
602
+ .filter((key, index, self) => self.indexOf(key) === index)
603
+ }
604
+ else {
605
+ target = target.filter(k => k !== key)
606
+ }
607
+
608
+ if (shouldEmit === true) {
609
+ emit('update:expanded', target)
610
+ }
611
+ else {
612
+ innerExpanded.value = target
613
+ }
614
+ }
615
+
616
+ /**
617
+ * 用于检查是否选中节点的复选框
618
+ */
619
+ function isTicked (key) {
620
+ return key && meta.value[ key ]
621
+ ? meta.value[ key ].ticked
622
+ : false
623
+ }
624
+
625
+ /**
626
+ * 添加节点复选框
627
+ */
628
+ function setTicked (keys, state) {
629
+ let target = innerTicked.value
630
+ const shouldEmit = props.ticked !== void 0
631
+
632
+ if (shouldEmit === true) {
633
+ target = target.slice()
634
+ }
635
+
636
+ if (state) {
637
+ target = target.concat(keys)
638
+ .filter((key, index, self) => self.indexOf(key) === index)
639
+ }
640
+ else {
641
+ target = target.filter(k => keys.includes(k) === false)
642
+ }
643
+
644
+ if (shouldEmit === true) {
645
+ emit('update:ticked', target)
646
+ }
647
+ }
648
+
649
+ // function getSlotScope (node, meta, key) {
650
+ // const scope = { tree: proxy, node, key, color: props.color, dark: isDark.value }
651
+ //
652
+ // injectProp(
653
+ // scope,
654
+ // 'expanded',
655
+ // () => { return meta.expanded },
656
+ // val => { val !== meta.expanded && setExpanded(key, val) }
657
+ // )
658
+ //
659
+ // injectProp(
660
+ // scope,
661
+ // 'ticked',
662
+ // () => { return meta.ticked },
663
+ // val => { val !== meta.ticked && setTicked([ key ], val) }
664
+ // )
665
+ //
666
+ // return scope
667
+ // }
668
+
669
+ // function getChildren (nodes) {
670
+ // return (
671
+ // props.filter
672
+ // ? nodes.filter(n => meta.value[ n[ props.nodeKey ] ].matchesFilter)
673
+ // : nodes
674
+ // ).map(child => getNode(child))
675
+ // }
676
+
677
+ // function getNodeMedia (node) {
678
+ // if (node.icon !== void 0) {
679
+ // return h(QIcon, {
680
+ // class: 'q-tree__icon q-mr-sm',
681
+ // name: node.icon,
682
+ // color: node.iconColor
683
+ // })
684
+ // }
685
+ // const src = node.img || node.avatar
686
+ // if (src) {
687
+ // return h('img', {
688
+ // class: `q-tree__${ node.img ? 'img' : 'avatar' } q-mr-sm`,
689
+ // src
690
+ // })
691
+ // }
692
+ // }
693
+
694
+ function onShow () {
695
+ emit('afterShow')
696
+ }
697
+
698
+ function onHide () {
699
+ emit('afterHide')
700
+ }
701
+
702
+ // function getNode (node) {
703
+ // const
704
+ // key = node[ props.nodeKey ],
705
+ // m = meta.value[ key ],
706
+ // header = node.header
707
+ // ? slots[ `header-${ node.header }` ] || slots[ 'default-header' ]
708
+ // : slots[ 'default-header' ]
709
+ //
710
+ // const children = m.isParent === true
711
+ // ? getChildren(node[ props.childrenKey ])
712
+ // : []
713
+ //
714
+ // const isParent = children.length > 0 || (m.lazy && m.lazy !== 'loaded')
715
+ //
716
+ // let body = node.body
717
+ // ? slots[ `body-${ node.body }` ] || slots[ 'default-body' ]
718
+ // : slots[ 'default-body' ]
719
+ // const slotScope = header !== void 0 || body !== void 0
720
+ // ? getSlotScope(node, m, key)
721
+ // : null
722
+ //
723
+ // if (body !== void 0) {
724
+ // body = h('div', { class: 'q-tree__node-body relative-position' }, [
725
+ // h('div', { class: textColorClass.value }, [
726
+ // body(slotScope)
727
+ // ])
728
+ // ])
729
+ // }
730
+ //
731
+ // return h('div', {
732
+ // key,
733
+ // class: 'q-tree__node relative-position'
734
+ // + ` q-tree__node--${ isParent === true ? 'parent' : 'child' }`
735
+ // }, [
736
+ // h('div', {
737
+ // class: 'q-tree__node-header relative-position row no-wrap items-center'
738
+ // // + (m.link === true ? ' q-tree__node--link q-hoverable q-focusable' : '')
739
+ // + (m.selected === true ? ' n-tree__node--selected' : '')
740
+ // + (m.disabled === true ? ' q-tree__node--disabled' : ''),
741
+ // tabindex: m.link === true ? 0 : -1,
742
+ // onClick: (e) => {
743
+ // onClick(node, m, e)
744
+ // },
745
+ // onKeypress (e) {
746
+ // if (shouldIgnoreKey(e) !== true) {
747
+ // if (e.keyCode === 13) { onClick(node, m, e, true) }
748
+ // else if (e.keyCode === 32) { onExpandClick(node, m, e, true) }
749
+ // }
750
+ // }
751
+ // }, [
752
+ // h('div', {
753
+ // class: 'q-focus-helper',
754
+ // tabindex: -1,
755
+ // ref: el => { blurTargets[ m.key ] = el }
756
+ // }),
757
+ //
758
+ // m.lazy === 'loading'
759
+ // ? h(QSpinner, {
760
+ // class: 'q-tree__spinner',
761
+ // color: computedControlColor.value
762
+ // })
763
+ // : (
764
+ // isParent === true
765
+ // ? h(QIcon, {
766
+ // class: 'q-tree__arrow'
767
+ // + (m.expanded === true ? ' q-tree__arrow--rotate' : ''),
768
+ // name: computedIcon.value,
769
+ // onClick (e) { onExpandClick(node, m, e) }
770
+ // })
771
+ // : null
772
+ // ),
773
+ //
774
+ // m.hasTicking === true && m.noTick !== true
775
+ // ? h(QCheckbox, {
776
+ // class: 'q-tree__tickbox',
777
+ // modelValue: m.indeterminate === true ? null : m.ticked,
778
+ // color: computedControlColor.value,
779
+ // dark: isDark.value,
780
+ // dense: true,
781
+ // keepColor: true,
782
+ // disable: m.tickable !== true,
783
+ // onKeydown: stopAndPrevent,
784
+ // 'onUpdate:modelValue': v => {
785
+ // onTickedClick(m, v)
786
+ // }
787
+ // })
788
+ // : null,
789
+ //
790
+ // h('div', {
791
+ // class: 'q-tree__node-header-content col row no-wrap items-center'
792
+ // + (m.selected === true ? selectedColorClass.value : textColorClass.value)
793
+ // }, [
794
+ // header
795
+ // ? header(slotScope)
796
+ // : [
797
+ // getNodeMedia(node),
798
+ // h('div', node[ props.labelKey ])
799
+ // ]
800
+ // ])
801
+ // ]),
802
+ //
803
+ // isParent === true
804
+ // ? (
805
+ // props.noTransition === true
806
+ // ? h('div', {
807
+ // class: 'q-tree__node-collapsible' + textColorClass.value,
808
+ // key: `${ key }__q`
809
+ // }, [
810
+ // body,
811
+ // h('div', {
812
+ // class: 'q-tree__children'
813
+ // + (m.disabled === true ? ' q-tree__node--disabled' : '')
814
+ // }, m.expanded ? children : null)
815
+ // ])
816
+ //
817
+ // : h(QSlideTransition, {
818
+ // duration: props.duration,
819
+ // onShow,
820
+ // onHide
821
+ // }, () => withDirectives(
822
+ // h('div', {
823
+ // class: 'q-tree__node-collapsible' + textColorClass.value,
824
+ // key: `${ key }__q`
825
+ // }, [
826
+ // body,
827
+ // h('div', {
828
+ // class: 'q-tree__children'
829
+ // + (m.disabled === true ? ' q-tree__node--disabled' : '')
830
+ // }, children)
831
+ // ]),
832
+ // [ [ vShow, m.expanded ] ]
833
+ // ))
834
+ // )
835
+ // : body
836
+ // ])
837
+ // }
838
+
839
+ /**
840
+ * 失去焦点
841
+ */
842
+ function blur (key) {
843
+ const blurTarget = blurTargets[ key ]
844
+ blurTarget && blurTarget.focus()
845
+ }
846
+
847
+ /**
848
+ * 点击节点
849
+ */
850
+ function onClick (node, meta, e, keyboard) {
851
+
852
+ keyboard !== true && meta.selectable !== false && blur(meta.key)
853
+
854
+ // 如果有 v-model:selected
855
+ if (hasSelection.value && meta.selectable) {
856
+
857
+ if (props.noSelectionUnset === false) {
858
+ emit('update:selected', meta.key !== props.selected ? meta.key : null)
859
+
860
+ } else if (meta.key !== props.selected) {
861
+ emit('update:selected', meta.key === void 0 ? null : meta.key)
862
+ }
863
+
864
+ } else {
865
+
866
+ // 如果是父节点
867
+ if (meta.isParent && ! props.multiple) {
868
+ // 则展开点击
869
+ onExpandClick(node, meta, e, keyboard)
870
+
871
+ // 如果有复选框
872
+ } else if (meta.hasTicking === true && meta.noTick !== true) {
873
+
874
+ // 如果有复选框
875
+ if (props.multiple) {
876
+
877
+ // 如果按下 shfit
878
+ if (shiftDown.value) {
879
+
880
+ // 如果没有选中节点
881
+ if (! innerTicked.value.length) {
882
+ updateTicked([meta.key])
883
+ return
884
+ }
885
+
886
+ const {
887
+ length,
888
+ } = innerTicked.value
889
+
890
+ // 如果只选中一个节点 && 该节点为当前点击的节点
891
+ if (length === 1 && innerTicked.value[0] === meta.key) {
892
+ return
893
+ }
894
+
895
+ // 如果没有 shift 按下节点
896
+ if (shiftDownNodeKey.value === null) {
897
+ // 则设置最后一个选中节点为 shift 按下节点
898
+ shiftDownNodeKey.value = innerTicked.value[length - 1]
899
+ }
900
+
901
+ // 获取最后一个选中节点
902
+ const lastTickedKey = shiftDownNodeKey.value
903
+
904
+ let lastIndex
905
+ let selectedIndex
906
+
907
+ for (let i = currentChildren.value.length - 1; i >= 0 ; i--) {
908
+ const item = currentChildren.value[i]
909
+ if (item.key === lastTickedKey) {
910
+ lastIndex = i
911
+ } else if (item.key === meta.key) {
912
+ selectedIndex = i
913
+ }
914
+ }
915
+
916
+ let startIndex
917
+ let endIndex
918
+
919
+ // 如果选择数据在最后一条数据前
920
+ if (selectedIndex < lastIndex) {
921
+ startIndex = selectedIndex
922
+ endIndex = lastIndex
923
+
924
+ // 如果选择数据在最后一条数据后
925
+ } else if (selectedIndex > lastIndex) {
926
+ startIndex = lastIndex
927
+ endIndex = selectedIndex
928
+ }
929
+
930
+ const newTicked = []
931
+ for (let i = startIndex; i <= endIndex; i++) {
932
+ const item = currentChildren.value[i]
933
+ if (item.m.tickable === true) {
934
+ newTicked.push(item.key)
935
+ }
936
+ }
937
+ updateTicked(newTicked)
938
+
939
+ // 如果按下 ctrl
940
+ } else if (ctrlDown.value) {
941
+ setTicked([meta.key], true)
942
+
943
+ // 否则只是点击
944
+ } else {
945
+ updateTicked([meta.key])
946
+ }
947
+ return
948
+ }
949
+
950
+ // 否则复选框点击
951
+ const isTicked = meta.indeterminate === true ? null : meta.ticked
952
+ onTickedClick(meta, ! isTicked)
953
+ }
954
+ }
955
+
956
+ if (typeof node.handler === 'function') {
957
+ node.handler(node)
958
+ }
959
+ }
960
+
961
+ /**
962
+ * 展开点击
963
+ */
964
+ function onExpandClick (node, meta, e, keyboard) {
965
+ if (e !== void 0) {
966
+ stopAndPrevent(e)
967
+ }
968
+ keyboard !== true && meta.selectable !== false && blur(meta.key)
969
+ setExpanded(meta.key, !meta.expanded, node, meta)
970
+ }
971
+
972
+ /**
973
+ * 复选框点击
974
+ */
975
+ function onTickedClick (meta, state) {
976
+ if (meta.indeterminate === true) {
977
+ state = meta.indeterminateNextState
978
+ }
979
+ if (meta.strictTicking) {
980
+ setTicked([ meta.key ], state)
981
+ }
982
+ else if (meta.leafTicking) {
983
+ const keys = []
984
+ const travel = meta => {
985
+ if (meta.isParent) {
986
+ if (state !== true && meta.noTick !== true && meta.tickable === true) {
987
+ keys.push(meta.key)
988
+ }
989
+ if (meta.leafTicking === true) {
990
+ meta.children.forEach(travel)
991
+ }
992
+ }
993
+ else if (
994
+ meta.noTick !== true
995
+ && meta.tickable === true
996
+ && (meta.leafFilteredTicking !== true || meta.matchesFilter === true)
997
+ ) {
998
+ keys.push(meta.key)
999
+ }
1000
+ }
1001
+ travel(meta)
1002
+ setTicked(keys, state)
1003
+ }
1004
+ }
1005
+
1006
+ /**
1007
+ * 默认展开全部
1008
+ */
1009
+ props.defaultExpandAll === true && expandAll()
1010
+
1011
+ // expose public methods
1012
+ // Object.assign(proxy, {
1013
+ // getNodeByKey,
1014
+ // getTickedNodes,
1015
+ // getExpandedNodes,
1016
+ // isExpanded,
1017
+ // collapseAll,
1018
+ // expandAll,
1019
+ // setExpanded,
1020
+ // isTicked,
1021
+ // setTicked
1022
+ // })
1023
+
1024
+ // return () => {
1025
+ // const children = getChildren(props.nodes)
1026
+ //
1027
+ // return h(
1028
+ // 'div', {
1029
+ // class: classes.value
1030
+ // },
1031
+ // children.length === 0
1032
+ // ? (
1033
+ // props.filter
1034
+ // ? props.noResultsLabel || $q.lang.tree.noResults
1035
+ // : props.noNodesLabel || $q.lang.tree.noNodes
1036
+ // )
1037
+ // : children
1038
+ // )
1039
+ // }
1040
+
1041
+ // ==========【覆盖部分】=========================================================================================
1042
+
1043
+ /**
1044
+ * 是否有默认头部插槽
1045
+ */
1046
+ const hasSlotDefault = computed(function () {
1047
+ return $n_has(slots, 'default-header')
1048
+ })
1049
+
1050
+ /**
1051
+ * 获取节点
1052
+ */
1053
+ function getNode(nodes, level, parents) {
1054
+
1055
+ const lists = []
1056
+
1057
+ // 如果有过滤方法
1058
+ if (props.filter) {
1059
+ nodes = nodes.filter(n => meta.value[ n[ props.nodeKey ] ].matchesFilter)
1060
+ }
1061
+
1062
+ for (const node of nodes) {
1063
+
1064
+ const key = node[ props.nodeKey ]
1065
+ const m = meta.value[ key ]
1066
+
1067
+ // 孩子节点
1068
+ const children = m.isParent === true
1069
+ ? node[ props.childrenKey ]
1070
+ : []
1071
+
1072
+ // 是否为父节点
1073
+ const isParent = children.length > 0 || (m.lazy && m.lazy !== 'loaded')
1074
+
1075
+ // 拖拽类名
1076
+ const dragCss = $n_get(dragClasses.value, key)
1077
+
1078
+ // 当前节点类名
1079
+ const classes = 'n-tree__node relative-position row no-wrap items-center q-tree__node--link q-hoverable'
1080
+ // + (m.link === true ? ' q-tree__node--link q-hoverable q-focusable' : '')
1081
+ + (m.selected === true || props.multiple && m.ticked === true ? ' n-tree__node--selected' : '')
1082
+ + (m.disabled === true ? ' q-tree__node--disabled' : '')
1083
+ + (dragCss ? ' ' + dragCss : '')
1084
+
1085
+ lists.push({
1086
+ classes,
1087
+ key,
1088
+ label: node[ props.labelKey ],
1089
+ m,
1090
+ isParent,
1091
+ level,
1092
+ parents,
1093
+ node,
1094
+ })
1095
+
1096
+ // 如果当前节点在展开节点中
1097
+ if (
1098
+ isParent
1099
+ && $n_indexOf(innerExpanded.value, key) > -1
1100
+ ) {
1101
+ const childNodes = getNode(node[ props.childrenKey ], level + 1, [...parents, key])
1102
+ if (childNodes.length) {
1103
+ lists.push(...childNodes)
1104
+ }
1105
+ }
1106
+ }
1107
+
1108
+ return lists
1109
+ }
1110
+
1111
+ /**
1112
+ * 当前节点
1113
+ */
1114
+ const currentChildren = computed(function () {
1115
+ if ($n_isValidArray(props.nodes)) {
1116
+ return getNode(props.nodes, 0, [])
1117
+ }
1118
+ return []
1119
+ })
1120
+
1121
+ // -------------------------------------------------------------------------------------------------
1122
+
1123
+ /**
1124
+ * 更新节点复选框
1125
+ */
1126
+ function updateTicked (keys) {
1127
+ if (props.ticked !== void 0) {
1128
+ emit('update:ticked', keys)
1129
+ } else {
1130
+ innerTicked.value = keys
1131
+ }
1132
+ }
1133
+
1134
+ /**
1135
+ * 按键按下
1136
+ */
1137
+ function onKeydown({ keyCode, shiftKey }) {
1138
+
1139
+ // 如果是 shift
1140
+ if (keyCode === 16 || shiftKey) {
1141
+ ctrlDown.value = false
1142
+ shiftDown.value = true
1143
+
1144
+ // 如果是 ctrl
1145
+ } else if (keyCode === 17) {
1146
+ ctrlDown.value = true
1147
+ shiftDown.value = false
1148
+ shiftDownNodeKey.value = null
1149
+
1150
+ // 否则其他
1151
+ } else {
1152
+ onKeyup()
1153
+ }
1154
+ }
1155
+
1156
+ /**
1157
+ * 按键弹起
1158
+ */
1159
+ function onKeyup() {
1160
+ ctrlDown.value = false
1161
+ shiftDown.value = false
1162
+ shiftDownNodeKey.value = null
1163
+ }
1164
+
1165
+ /**
1166
+ * 鼠标点击
1167
+ */
1168
+ function onMouseDown() {
1169
+ // 如果开启拖拽
1170
+ if (props.draggable) {
1171
+ // 设置拖拽中
1172
+ dragging.value = true
1173
+ }
1174
+ }
1175
+
1176
+ /**
1177
+ * 拖拽开始
1178
+ */
1179
+ function onDragStart(e, item) {
1180
+
1181
+ // 如果开启拖拽
1182
+ if (props.draggable) {
1183
+
1184
+ // 判断节点能否被拖拽
1185
+ if (
1186
+ $n_isFunction(props.allowDrag)
1187
+ && ! props.allowDrag(item.node)
1188
+ ) {
1189
+ e.preventDefault()
1190
+ return false
1191
+ }
1192
+
1193
+ e.dataTransfer.effectAllowed = 'move'
1194
+
1195
+ // wrap in try catch to address IE's error when first param is 'text/plain'
1196
+ try {
1197
+ // setData is required for draggable to work in FireFox
1198
+ // the content has to be '' so dragging a node out of the tree won't open a new tab in FireFox
1199
+ e.dataTransfer.setData('text/plain', '')
1200
+ } catch {}
1201
+
1202
+ // 设置拖拽中
1203
+ dragging.value = true
1204
+
1205
+ // 设置拖拽中节点
1206
+ draggingNode.value = item
1207
+
1208
+ // 设置节点类名
1209
+ dragClasses.value[item.key] = 'dragging--before'
1210
+
1211
+ emit('nodeDragStart', item.node, e)
1212
+ }
1213
+ }
1214
+
1215
+ /**
1216
+ * 拖拽进入
1217
+ */
1218
+ function onDragEnter(e, item, isOver) {
1219
+
1220
+ // 如果开启拖拽
1221
+ if (props.draggable) {
1222
+
1223
+ dropNode.value = null
1224
+
1225
+ // 如果拖拽中
1226
+ if (dragging.value) {
1227
+
1228
+ const {
1229
+ key: fromKey,
1230
+ } = draggingNode.value
1231
+
1232
+ // 设置节点类名
1233
+ if (! isOver) {
1234
+ dragClasses.value[draggingNode.value.key] = 'dragging'
1235
+ }
1236
+
1237
+ let type
1238
+
1239
+ if (
1240
+ // 如果被拖动节点 === 拖动只节点
1241
+ fromKey === item.key
1242
+ // 如果拖动的节点 在 被拖动节点的父级节点中
1243
+ || $n_indexOf(item.parents, fromKey) > -1
1244
+ ) {
1245
+ type = 'none'
1246
+
1247
+ } else {
1248
+
1249
+ // 如果禁止上下拖拽
1250
+ if (props.noDragUpDown) {
1251
+ type = 'inner'
1252
+
1253
+ } else {
1254
+
1255
+ const {
1256
+ top,
1257
+ height,
1258
+ } = e.target.getBoundingClientRect()
1259
+
1260
+ const dY = e.clientY - top
1261
+
1262
+ // 目标节点上方
1263
+ if (dY <= height * 0.3) {
1264
+
1265
+ type = 'top'
1266
+
1267
+ // 目标节点内部
1268
+ } else if (dY <= height * (0.7)) {
1269
+
1270
+ type = 'inner'
1271
+
1272
+ // 目标节点下方
1273
+ } else {
1274
+ type = 'bottom'
1275
+ }
1276
+ }
1277
+
1278
+ // 拖拽时判定目标节点能否被放置, type 参数有三种情况: top / inner / bottom ( 目标节点上方 / 目标节点内部 / 目标节点下方 )
1279
+ if ($n_isFunction(props.allowDrop)) {
1280
+ const res = props.allowDrop(draggingNode.value.node, item.node, type)
1281
+ if (res !== true) {
1282
+ type = 'none'
1283
+ }
1284
+ }
1285
+
1286
+ if (type !== 'none') {
1287
+ dragClasses.value[item.key] = `drag-over--${type}`
1288
+ }
1289
+ }
1290
+
1291
+ // 为需要移动的元素设置 dragstart 事件
1292
+ // e.dataTransfer.effectAllowed = 'move'
1293
+
1294
+ // 设置放置节点
1295
+ dropNode.value = {
1296
+ item,
1297
+ type,
1298
+ }
1299
+
1300
+ emit(isOver ? 'nodeDragOver' : 'nodeDragEnter', draggingNode.value.node, item.node, e)
1301
+ }
1302
+ }
1303
+ }
1304
+
1305
+ /**
1306
+ * 拖拽结束
1307
+ */
1308
+ function onDragEnd(e) {
1309
+
1310
+ // 如果开启拖拽
1311
+ if (props.draggable) {
1312
+
1313
+ if (draggingNode.value) {
1314
+
1315
+ const draggingNodeKey = draggingNode.value.key
1316
+
1317
+ dragClasses.value[draggingNodeKey] = ''
1318
+
1319
+ if (dropNode.value) {
1320
+
1321
+ const dropType = dropNode.value.type
1322
+
1323
+ // 执行放置节点
1324
+ function doDrop() {
1325
+
1326
+ // 放置类型 !== 'none'
1327
+ if (dropType !== 'none') {
1328
+
1329
+ const dropNodeKey = dropNode.value.item.key
1330
+
1331
+ // 删除拖拽中的节点
1332
+ function remove() {
1333
+ const {
1334
+ parent
1335
+ } = draggingNode.value.m
1336
+ const children = parent ? parent.childrens : props.nodes
1337
+ const index = $n_findIndex(children, e => e[props.nodeKey] === draggingNodeKey)
1338
+ if (index > -1) {
1339
+ children.splice(index, 1)
1340
+ }
1341
+ }
1342
+
1343
+ // 如果放置目标节点内部
1344
+ if (dropType === 'inner') {
1345
+ if (dropNode.value.item.m.childrens.length) {
1346
+
1347
+ // 判断该节点是否在内部
1348
+ if ($n_findIndex(dropNode.value.item.m.childrens, e => e[props.nodeKey] === draggingNodeKey) > -1) {
1349
+ // console.log('不用拖拽,在节点内部')
1350
+ return false
1351
+ }
1352
+
1353
+ remove()
1354
+ dropNode.value.item.m.childrens.push(draggingNode.value.node)
1355
+ return dropNode.value.item.m.childrens
1356
+ }
1357
+
1358
+ remove()
1359
+ dropNode.value.item.node[props.childrenKey] = [ draggingNode.value.node ]
1360
+ return dropNode.value.item.node[props.childrenKey]
1361
+
1362
+ // 否则放置目标节点上下
1363
+ } else {
1364
+ const dropParent = dropNode.value.item.m.parent
1365
+ const dropChildren = dropParent ? dropParent.childrens : props.nodes
1366
+ let dropIndex = $n_findIndex(dropChildren, e => e[props.nodeKey] === dropNodeKey)
1367
+ if (dropIndex > -1) {
1368
+
1369
+ // 如果移动至上方
1370
+ if (dropType === 'top') {
1371
+ if (dropIndex > 0) {
1372
+ const dropTopItem = dropChildren[dropIndex - 1]
1373
+ if (dropTopItem[props.nodeKey] === draggingNodeKey) {
1374
+ // console.log('不用拖拽,拖到上方的就是被拖拽节点自己')
1375
+ return false
1376
+ }
1377
+ }
1378
+
1379
+ // 否则移动到下方
1380
+ } else if (dropIndex + 1 < dropChildren.length) {
1381
+ const dropTopItem = dropChildren[dropIndex + 1]
1382
+ if (dropTopItem[props.nodeKey] === draggingNodeKey) {
1383
+ // console.log('不用拖拽,拖到下方的就是被拖拽节点自己')
1384
+ return false
1385
+ }
1386
+ }
1387
+
1388
+ remove()
1389
+ dropIndex = $n_findIndex(dropChildren, e => e[props.nodeKey] === dropNodeKey)
1390
+ if (dropIndex > -1) {
1391
+ dropChildren.splice(dropType === 'top' ? dropIndex : dropIndex + 1, 0, draggingNode.value.node)
1392
+ return dropChildren
1393
+ }
1394
+ }
1395
+ }
1396
+ }
1397
+ return false
1398
+ }
1399
+
1400
+ emit('nodeDragEnd', draggingNode.value.node, dropNode.value.item.node, dropType, doDrop, e)
1401
+
1402
+ } else {
1403
+ emit('nodeDragEnd', draggingNode.value.node, null, 'none', () => props.nodes, e)
1404
+ }
1405
+ }
1406
+
1407
+ draggingNode.value = null
1408
+ dropNode.value = null
1409
+ dragging.value = false
1410
+ }
1411
+ }
1412
+
1413
+ /**
1414
+ * 拖拽离开
1415
+ */
1416
+ function onDragLeave(e, item) {
1417
+
1418
+ // 如果开启拖拽
1419
+ if (props.draggable) {
1420
+ if (draggingNode.value) {
1421
+ if (item.key === draggingNode.value.key) {
1422
+ return
1423
+ }
1424
+ dragClasses.value[item.key] = ''
1425
+
1426
+ emit('nodeDragLeave', draggingNode.value.node, item.node, e)
1427
+ }
1428
+ }
1429
+ }
1430
+
1431
+ // ==========【生命周期】=========================================================================================
1432
+
1433
+ /**
1434
+ * 更新 DOM 之前调用
1435
+ */
1436
+ onBeforeUpdate(function () {
1437
+ blurTargets = {}
1438
+ })
1439
+
1440
+ /**
1441
+ * 实例被挂载后调用
1442
+ */
1443
+ onMounted(function() {
1444
+ $n_on(document, 'keydown', onKeydown)
1445
+ $n_on(document, 'keyup', onKeyup)
1446
+ })
1447
+
1448
+ /**
1449
+ * 实例被卸载后调用
1450
+ */
1451
+ onUnmounted(function () {
1452
+ $n_off(document, 'keydown', onKeydown)
1453
+ $n_off(document, 'keyup', onKeyup)
1454
+ })
1455
+
1456
+ return {
1457
+ classes,
1458
+ computedIcon,
1459
+ computedControlColor,
1460
+ isDark,
1461
+ onClick,
1462
+ onExpandClick,
1463
+ onTickedClick,
1464
+ getNodeByKey,
1465
+ getExpandedNodes,
1466
+ getTickedNodes,
1467
+ isExpanded,
1468
+ setExpanded,
1469
+ isTicked,
1470
+ setTicked,
1471
+ expandAll,
1472
+ collapseAll,
1473
+ stopAndPrevent,
1474
+
1475
+ hasSlotDefault,
1476
+ currentChildren,
1477
+
1478
+ onMouseDown,
1479
+ onDragStart,
1480
+ onDragEnter,
1481
+ onDragLeave,
1482
+ onDragEnd,
1483
+ }
1484
+ }
1485
+ }
1486
+ </script>
1487
+
1488
+ <style lang="scss">
1489
+ @import "@/assets/sass/variables.scss";
1490
+
1491
+ .n-tree {
1492
+ color: $grey;
1493
+
1494
+ &__arrow--noop {
1495
+ width: 16px;
1496
+ height: 16px;
1497
+ margin-right: 4px;
1498
+ }
1499
+
1500
+ &__node {
1501
+ margin: 4px;
1502
+ border-radius: 4px;
1503
+ //transition: all .3s;
1504
+
1505
+ // 已选
1506
+ &--selected:not(.drag-over--inner) {
1507
+ .q-focus-helper {
1508
+ background: currentColor;
1509
+ opacity: 0.15 !important;
1510
+ }
1511
+ }
1512
+
1513
+ // 拖拽前
1514
+ &.dragging--before {
1515
+ .q-focus-helper {
1516
+ display: none;
1517
+ }
1518
+ }
1519
+
1520
+ // 拖拽中
1521
+ &.dragging {
1522
+ .q-focus-helper {
1523
+ background: transparent !important;
1524
+ opacity: 1 !important;
1525
+ border: 1px solid var(--q-primary);
1526
+ &:before {
1527
+ background-color: var(--q-primary);
1528
+ opacity: 0.1 !important;
1529
+ box-sizing: border-box;
1530
+ }
1531
+ &:after {
1532
+ opacity: 0 !important;
1533
+ }
1534
+ }
1535
+ }
1536
+
1537
+ // 拖拽至上方
1538
+ &.drag-over--top {
1539
+ .n-tree__node-content {
1540
+ &:before {
1541
+ top: 0;
1542
+ margin-top: -5px;
1543
+ }
1544
+ border-top-color: var(--q-primary);
1545
+ }
1546
+ }
1547
+
1548
+ // 拖拽至内部
1549
+ &.drag-over--inner {
1550
+ color: rgba(255, 255, 255, 0.6);
1551
+ .q-focus-helper {
1552
+ background-color: var(--q-primary) !important;
1553
+ opacity: 1;
1554
+ }
1555
+ .n-tree__node-content {
1556
+ color: #ffffff;
1557
+ }
1558
+ }
1559
+
1560
+ // 拖拽至底部
1561
+ &.drag-over--bottom {
1562
+ .n-tree__node-content {
1563
+ &:before {
1564
+ bottom: 0;
1565
+ margin-bottom: -5px;
1566
+ }
1567
+ border-bottom-color: var(--q-primary);
1568
+ }
1569
+ }
1570
+
1571
+ &.drag-over--top,
1572
+ &.drag-over--bottom {
1573
+ .n-tree__node-content {
1574
+ &:before {
1575
+ content: "";
1576
+ position: absolute;
1577
+ left: 0;
1578
+ margin-left: -8px;
1579
+ border-radius: 50%;
1580
+ width: 8px;
1581
+ height: 8px;
1582
+ border: 2px solid var(--q-primary);
1583
+ }
1584
+ }
1585
+
1586
+ .q-focus-helper {
1587
+ background-color: var(--q-primary);
1588
+ opacity: 0.15;
1589
+ }
1590
+ }
1591
+
1592
+ // 拖拽内容
1593
+ &-content {
1594
+ position: relative;
1595
+ flex: 1;
1596
+ padding: 2px 5px;
1597
+ margin-left: -5px;
1598
+ border-top: 2px transparent solid;
1599
+ border-bottom: 2px transparent solid;
1600
+ color: #000000;
1601
+ }
1602
+ }
1603
+
1604
+ &--dark {
1605
+ .n-tree__node-content {
1606
+ color: #ffffff;
1607
+ }
1608
+
1609
+ .n-tree__node {
1610
+
1611
+ // 已选
1612
+ &--selected:not(.drag-over--inner) {
1613
+ .q-focus-helper {
1614
+ opacity: 0.3 !important;
1615
+ }
1616
+ }
1617
+
1618
+ // 拖拽中
1619
+ &.dragging {
1620
+ .q-focus-helper {
1621
+ &:before {
1622
+ opacity: 0.3 !important;
1623
+ }
1624
+ }
1625
+ }
1626
+
1627
+ &.drag-over--top,
1628
+ &.drag-over--bottom {
1629
+ .q-focus-helper {
1630
+ opacity: 0.3 !important;
1631
+ }
1632
+ }
1633
+ }
1634
+ }
1635
+ }
1636
+ </style>