@netang/quasar 0.2.32 → 0.2.34

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