@netang/quasar 0.2.31 → 0.2.32

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 -1492
  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 +1348 -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 +562 -562
@@ -1,755 +1,755 @@
1
- <template>
2
- <q-field
3
- class="n-field-tree"
4
- :model-value="showValue"
5
- :disable="disable"
6
- :readonly="readonly"
7
- :clearable="clearable"
8
- for="field-tree"
9
- @focus="onFieldFocus"
10
- @blur="onFieldBlur"
11
- @clear="onFieldClear"
12
- v-bind="$attrs"
13
- >
14
- <template v-slot:control>
15
-
16
- <template v-if="multiple">
17
-
18
- <template v-if="treeTickedNodes.length">
19
-
20
- <!-- 多选插槽 -->
21
- <slot
22
- name="selected"
23
- :ticked="treeTickedNodes"
24
- :remove="onRemoveItem"
25
- v-if="$slots.ticked"
26
- />
27
-
28
- <!-- 显示折叠的值数量 -->
29
- <q-chip
30
- dense
31
- :label="`+${treeTickedNodes.length}`"
32
- v-else-if="collapseTags"
33
- />
34
-
35
- <!-- 多选标签 -->
36
- <template v-else>
37
- <q-chip
38
- v-for="(item, index) in treeTickedNodes"
39
- :key="`item-${index}`"
40
- :label="item.label"
41
- dense
42
- :removable="! readonly && ! disable"
43
- @remove="onRemoveItem(index)"
44
- />
45
- </template>
46
- </template>
47
-
48
- <!-- 占位符-->
49
- <span class="n-placeholder" v-else-if="placeholder">{{placeholder}}</span>
50
- </template>
51
-
52
- <!-- 显示文字 -->
53
- <span v-else-if="showValue">{{showValue}}</span>
54
-
55
- <!-- 占位符-->
56
- <span class="n-placeholder" v-else-if="placeholder">{{placeholder}}</span>
57
-
58
- <!-- 筛选输入框 -->
59
- <input
60
- ref="inputRef"
61
- class="q-field__input q-placeholder col q-field__input--padding"
62
- v-model="inputValue"
63
- v-if="filter"
64
- />
65
- </template>
66
-
67
- <template v-slot:before v-if="$slots.before">
68
- <slot name="before" />
69
- </template>
70
- <template v-slot:prepend v-if="$slots.prepend">
71
- <slot name="prepend" />
72
- </template>
73
- <template v-slot:append v-if="$slots.append">
74
- <slot name="append" />
75
- </template>
76
- <template v-slot:after v-if="$slots.after">
77
- <slot name="after" />
78
- </template>
79
-
80
- <!-- 弹出层代理 -->
81
- <q-popup-proxy
82
- ref="popupRef"
83
- no-refocus
84
- no-focus
85
- fit
86
- @focus="onFieldFocus"
87
- @show="onPopupShow"
88
- @before-hide="showPopup = false"
89
- v-if="! readonly && ! disable"
90
- >
91
- <q-card>
92
- <!-- 树 -->
93
- <n-tree
94
- ref="treeRef"
95
- style="min-width:260px;max-height:380px;"
96
- :filter="inputValue"
97
- :nodes="currentTreeNodes"
98
- :node-key="nodeKey"
99
- :label-key="labelKey"
100
- :ticked="treeTicked"
101
- @update:ticked="emitModelValue"
102
- v-model:expanded="treeExpanded"
103
- :tick-strategy="multiple ? (strict ? 'strict' : 'leaf') : 'none'"
104
- :accordion="accordion"
105
- v-bind="treeProps"
106
- v-if="showTree"
107
- >
108
- <template v-slot:default-header="{ node }">
109
- <div
110
- :class="{
111
- 'text-primary': checkTreeNodeActive(node),
112
- }"
113
- @click="onNode($event, node)"
114
- >{{node.label}}</div>
115
- </template>
116
- </n-tree>
117
-
118
- <!-- loading -->
119
- <div class="flex flex-center" style="height:100px" v-else>
120
- <q-spinner
121
- color="primary"
122
- size="3em"
123
- />
124
- </div>
125
-
126
- </q-card>
127
- </q-popup-proxy>
128
- </q-field>
129
- </template>
130
-
131
- <script>
132
- import { ref, computed, watch, onUpdated, nextTick } from 'vue'
133
-
134
- import $n_has from 'lodash/has'
135
- import $n_uniq from 'lodash/uniq'
136
- import $n_concat from 'lodash/concat'
137
- import $n_isFunction from 'lodash/isFunction'
138
-
139
- import $n_indexOf from '@netang/utils/indexOf'
140
- import $n_isRequired from '@netang/utils/isRequired'
141
- import $n_isValidArray from '@netang/utils/isValidArray'
142
- import $n_isValidValue from '@netang/utils/isValidValue'
143
- import $n_isValidObject from '@netang/utils/isValidObject'
144
- import $n_runAsync from '@netang/utils/runAsync'
145
-
146
- export default {
147
-
148
- /**
149
- * 标识
150
- */
151
- name: 'NFieldTree',
152
-
153
- /**
154
- * 声明属性
155
- */
156
- props: {
157
- // 值 v-model
158
- modelValue: {
159
- required: true,
160
- },
161
- // 树展开节点
162
- expanded: Array, // v-model:expanded
163
- // 节点数组
164
- // Array: 初始节点数据数据组
165
- // Function: 获取初始节点数据的方法
166
- nodes: [ Array, Function ],
167
- // 唯一的节点键值
168
- nodeKey: {
169
- type: String,
170
- default: 'id',
171
- },
172
- // 标签字段
173
- labelKey: {
174
- type: String,
175
- default: 'label',
176
- },
177
- // 是否可选任意一级(true:可选任意一级, false: 仅能选叶子节点)
178
- strict: Boolean,
179
- // 是否多选
180
- multiple: Boolean,
181
- // 手风琴模式
182
- accordion: Boolean,
183
- // 是否显示选中值的完整路径
184
- showAllLevels: {
185
- type: Boolean,
186
- default: true,
187
- },
188
- // 是否开启筛选
189
- filter: Boolean,
190
- // 是否折叠标签(多选模式有效)
191
- collapseTags: Boolean,
192
- // 占位符
193
- placeholder: String,
194
- // 是否可清除
195
- clearable: Boolean,
196
- // 是否禁用
197
- disable: Boolean,
198
- // 是否只读
199
- readonly: Boolean,
200
- // 树声明属性
201
- treeProps: Object,
202
- },
203
-
204
- /**
205
- * 声明事件
206
- */
207
- emits: [
208
- 'update:modelValue',
209
- ],
210
-
211
- /**
212
- * 组合式
213
- */
214
- setup(props, { emit }) {
215
-
216
- // ==========【数据】============================================================================================
217
-
218
- // 是否为初始加载树节点树
219
- const isDefaultLoadNodes = $n_isFunction(props.nodes)
220
-
221
- // 树节点是否已加载
222
- let __treeNodesLoaded = false
223
-
224
- // 输入框节点
225
- const inputRef = ref(null)
226
-
227
- // 输入框值
228
- const inputValue = ref('')
229
-
230
- // 弹出层节点
231
- const popupRef = ref(null)
232
-
233
- // 是否显示弹出层
234
- const showPopup = ref(false)
235
-
236
- // 是否显示树(如果为非初始加载树节点树, 则直接显示)
237
- const showTree = ref(! isDefaultLoadNodes)
238
-
239
- // 树节点
240
- const treeRef = ref(null)
241
-
242
- // 当前树节点数据
243
- const currentTreeNodes = ref(isDefaultLoadNodes ? [] : props.nodes)
244
-
245
- // tree all
246
- const treeAll = ref(getTreeAll())
247
-
248
- // 树展开数据
249
- const treeExpanded = ref(getExpanded())
250
-
251
- // 树选择数据
252
- const treeTicked = ref(formatModelValue(props.modelValue))
253
-
254
- // 如果为初始加载树节点树
255
- if (isDefaultLoadNodes) {
256
-
257
- // 初始加载节点
258
- defaultLoadNodes()
259
- .finally()
260
- }
261
-
262
- // ==========【计算属性】=========================================================================================
263
-
264
- /**
265
- * 树选择节点数据
266
- */
267
- const treeTickedNodes = computed(function () {
268
-
269
- const lists = []
270
-
271
- const hasTreeAll = $n_isValidObject(treeAll.value)
272
-
273
- for (const treeKey of treeTicked.value) {
274
- lists.push({
275
- id: treeKey,
276
- label: hasTreeAll && $n_has(treeAll.value, treeKey) ?
277
- treeAll.value[treeKey][props.showAllLevels ? 'path' : 'label']
278
- : treeKey
279
- })
280
- }
281
-
282
- return lists
283
- })
284
-
285
- /**
286
- * 显示值
287
- */
288
- const showValue = computed(function () {
289
-
290
- // 如果有已选数据
291
- return $n_isValidArray(treeTickedNodes.value)
292
- // 取已选数据第一条的标签
293
- ? treeTickedNodes.value[0].label
294
- : ''
295
- })
296
-
297
- // ==========【监听数据】=========================================================================================
298
-
299
- /**
300
- * 监听声明节点
301
- */
302
- watch(() => props.nodes, defaultLoadNodes)
303
-
304
- /**
305
- * 监听声明值
306
- */
307
- watch(() => props.modelValue, function(val) {
308
-
309
- // 设置选中数据
310
- treeTicked.value = formatModelValue(val)
311
-
312
- // 设置输入框焦点
313
- setInputFocus()
314
-
315
- // 设置输入框文字选中
316
- setInputSelection()
317
- })
318
-
319
- /**
320
- * 监听输入框值
321
- */
322
- watch(inputValue, function (val) {
323
- if (
324
- // 如果弹出层是隐藏的
325
- ! showPopup.value
326
- // 如果输入框有值
327
- && $n_isValidValue(val)
328
- ) {
329
- // 显示弹出层
330
- popupRef.value.show()
331
- }
332
- })
333
-
334
- // ==========【方法】=============================================================================================
335
-
336
- /**
337
- * 获取树子节点
338
- */
339
- function _getTreeChildren(all, data, pid, pPath) {
340
- for (const item of data) {
341
-
342
- const label = item[props.labelKey]
343
-
344
- const path = pPath ? (pPath + ' / ' + label) : label
345
-
346
- all[item[props.nodeKey]] = {
347
- id: item[props.nodeKey],
348
- pid,
349
- label,
350
- children: item.children,
351
- path,
352
- }
353
-
354
- // 如果是父节点
355
- if ($n_isValidArray(item.children)) {
356
- _getTreeChildren(all, item.children, item.id, path)
357
- }
358
- }
359
- }
360
-
361
- /**
362
- * 获取 tree all
363
- */
364
- function getTreeAll() {
365
-
366
- const all = {}
367
-
368
- // 如果当前树节点数据为有效数组
369
- if ($n_isValidArray(currentTreeNodes.value)) {
370
- _getTreeChildren(all, currentTreeNodes.value, 0, '')
371
- }
372
-
373
- return all
374
- }
375
-
376
- /**
377
- * 获取展开节点数组
378
- */
379
- function getExpanded() {
380
-
381
- let expanded = []
382
-
383
- if (
384
- // 如果是单选
385
- ! props.multiple
386
- // 如果有值
387
- && $n_isRequired(props.modelValue)
388
- // 如果有 tree all
389
- && $n_isValidObject(treeAll.value)
390
- // 存在节点
391
- && $n_has(treeAll.value, props.modelValue)
392
- ) {
393
- // 获取父节点
394
- function getParent({ id, pid, children }) {
395
-
396
- // 如果是父级节点
397
- if ($n_isValidArray(children)) {
398
- // 设为展开
399
- expanded.push(id)
400
- }
401
-
402
- // 如果有父节点, 则继续向上寻找
403
- if (pid && $n_has(treeAll.value, pid)) {
404
- getParent(treeAll.value[pid])
405
- }
406
- }
407
-
408
- getParent(treeAll.value[props.modelValue])
409
-
410
- if (props.expanded) {
411
- expanded = $n_uniq($n_concat(expanded, props.expanded))
412
- }
413
- }
414
-
415
- return expanded
416
- }
417
-
418
- /**
419
- * 格式化传值
420
- */
421
- function formatModelValue(val) {
422
-
423
- // 如果是多选
424
- if (props.multiple) {
425
- return $n_isValidArray(val) ? val : []
426
- }
427
-
428
- // 如果为有效值
429
- if ($n_isRequired(val)) {
430
- return [ val ]
431
- }
432
-
433
- return []
434
- }
435
-
436
- /**
437
- * 触发更新值
438
- */
439
- function emitModelValue(val) {
440
-
441
- // 触发更新值
442
- emit('update:modelValue', val)
443
- }
444
-
445
- /**
446
- * 点击节点
447
- */
448
- function onNode(e, { id, children }) {
449
-
450
- // 如果是多选
451
- if (props.multiple) {
452
-
453
- // 如果是父节点
454
- if ($n_isValidArray(children)) {
455
-
456
- // 则无任何操作
457
- return
458
- }
459
-
460
- // 克隆已选树数据
461
- const _ticked = [...treeTicked.value]
462
-
463
- // 获取值在树数据中的索引
464
- const index = $n_indexOf(_ticked, id)
465
-
466
- // 如果在数据中
467
- if (index > -1) {
468
- // 则删除
469
- _ticked.splice(index, 1)
470
- // 否则
471
- } else {
472
- // 添加
473
- _ticked.push(id)
474
- }
475
-
476
- // 触发更新值
477
- // 设置树选择数据
478
- emitModelValue(_ticked)
479
-
480
- // 否则是单选
481
- } else {
482
-
483
- if (
484
- // 如果是父节点
485
- $n_isValidArray(children)
486
- // 如果仅可选择叶子节点
487
- && ! props.strict
488
- ) {
489
- // 则无任何操作
490
- return
491
- }
492
-
493
- // 触发更新值
494
- // 设置树选择数据
495
- emitModelValue(id)
496
-
497
- // 则停止冒泡
498
- e.preventDefault()
499
- e.stopPropagation()
500
-
501
- // 则关闭弹出层
502
- popupRef.value.hide()
503
- }
504
- }
505
-
506
- /**
507
- * 移除单个节点
508
- */
509
- function onRemoveItem(index) {
510
-
511
- // 克隆已选树数据
512
- const _ticked = [...treeTicked.value]
513
-
514
- // 删除该节点
515
- _ticked.splice(index, 1)
516
-
517
- // 触发更新值
518
- // 设置树选择数据
519
- emitModelValue(_ticked)
520
- }
521
-
522
- /**
523
- * 字段获取焦点触发
524
- */
525
- function onFieldFocus(e) {
526
-
527
- // 停止冒泡
528
- e.stopPropagation()
529
-
530
- // 设置输入框焦点
531
- setInputFocus()
532
-
533
- // window.scrollTo(window.pageXOffset || window.scrollX || document.body.scrollLeft || 0, 0)
534
- }
535
-
536
- /**
537
- * 字段失去焦点触发
538
- */
539
- function onFieldBlur(e) {
540
-
541
- // 停止冒泡
542
- e.stopPropagation()
543
-
544
- if (
545
- // 如果开启筛选
546
- props.filter
547
- // 如果没有显示弹出层
548
- && ! showPopup.value
549
- ) {
550
- // 清空输入框值
551
- inputValue.value = ''
552
- }
553
- }
554
-
555
- /**
556
- * 字段清空触发
557
- */
558
- function onFieldClear() {
559
-
560
- // 触发更新值
561
- // 清空树数据
562
- emitModelValue(props.multiple ? [] : '')
563
-
564
- // 隐藏弹出层
565
- popupRef.value.hide()
566
- }
567
-
568
- /**
569
- * 弹出层显示回调
570
- */
571
- async function onPopupShow() {
572
-
573
- // 显示弹出层
574
- showPopup.value = true
575
-
576
- // 设置输入框焦点
577
- setInputFocus()
578
-
579
- if (
580
- // 如果树已加载过了
581
- __treeNodesLoaded
582
- // 如果树已显示
583
- || showTree.value
584
- ) {
585
- // 则无任何操作
586
- return
587
- }
588
-
589
- // 初始加载节点
590
- await defaultLoadNodes()
591
- }
592
-
593
- /**
594
- * 设置输入框文字选中
595
- */
596
- function setInputSelection() {
597
- if (
598
- // 如果开启筛选
599
- props.filter
600
- // 如果有输入框节点
601
- && inputRef.value
602
- // 如果输入框有值
603
- && inputValue.value.length
604
- ) {
605
- // 全选文字
606
- inputRef.value.select()
607
- // inputRef.value.setSelectionRange(0, inputValue.value.length)
608
- }
609
- }
610
-
611
- /**
612
- * 设置输入框焦点
613
- */
614
- function setInputFocus() {
615
- if (
616
- // 如果开启筛选
617
- props.filter
618
- // 如果有输入框节点
619
- && inputRef.value
620
- ) {
621
- inputRef.value.focus()
622
- }
623
- }
624
-
625
- /**
626
- * 树节点是否激活
627
- */
628
- function checkTreeNodeActive({ id }) {
629
- return $n_indexOf(treeTicked.value, id) > -1
630
- }
631
-
632
- /**
633
- * 初始加载节点
634
- */
635
- async function defaultLoadNodes() {
636
-
637
- // 如果是初始加载树节点树
638
- if ($n_isFunction(props.nodes)) {
639
-
640
- // 隐藏树
641
- showTree.value = false
642
-
643
- // 下次 DOM 更新
644
- await nextTick()
645
-
646
- // 通过自定义方法获取树节点数组
647
- const resNodes = await $n_runAsync(props.nodes)()
648
-
649
- if ($n_isValidArray(resNodes)) {
650
-
651
- // 设置当前树节点数组
652
- currentTreeNodes.value = resNodes
653
-
654
- // 设置 tree all
655
- treeAll.value = getTreeAll()
656
-
657
- // 设置开数据
658
- treeExpanded.value = getExpanded()
659
-
660
- } else {
661
-
662
- // 设置当前树节点数组
663
- currentTreeNodes.value = []
664
-
665
- // 设置 tree all
666
- treeAll.value = getTreeAll()
667
- }
668
-
669
- // 否则为节点数组数据
670
- } else {
671
-
672
- // 设置当前树节点数组
673
- currentTreeNodes.value = $n_isValidArray(props.nodes) ? props.nodes : []
674
-
675
- // 设置 tree all
676
- treeAll.value = getTreeAll()
677
- }
678
-
679
- // 设置显示树
680
- showTree.value = true
681
-
682
- // 树已加载
683
- __treeNodesLoaded = true
684
- }
685
-
686
- // ==========【生命周期】=========================================================================================
687
-
688
- /**
689
- * 在组件因为响应式状态变更而更新其 DOM 树之后调用
690
- */
691
- onUpdated(function () {
692
- if ($n_has(popupRef.value, 'currentComponent.ref.updatePosition')) {
693
- popupRef.value.currentComponent.ref.updatePosition()
694
- }
695
- })
696
-
697
- // ==========【返回】=============================================================================================
698
-
699
- return {
700
- // 显示值
701
- showValue,
702
- // 输入框节点
703
- inputRef,
704
- // 输入框值
705
- inputValue,
706
- // 弹出层节点
707
- popupRef,
708
- // 是否显示弹出层
709
- showPopup,
710
- // 是否显示树
711
- showTree,
712
- // 树节点
713
- treeRef,
714
- // 树选择数据
715
- treeTicked,
716
- // 树选择节点数据
717
- treeTickedNodes,
718
- // 树展开数据
719
- treeExpanded,
720
- // 当前树节点数据
721
- currentTreeNodes,
722
-
723
- // 触发更新值
724
- emitModelValue,
725
- // 点击节点
726
- onNode,
727
- // 移除单个
728
- onRemoveItem,
729
-
730
- // 字段获取焦点触发
731
- onFieldFocus,
732
- // 字段失去焦点触发
733
- onFieldBlur,
734
- // 字段清空触发
735
- onFieldClear,
736
-
737
- // 弹出层显示回调
738
- onPopupShow,
739
-
740
- // 节点是否激活
741
- checkTreeNodeActive,
742
- }
743
- },
744
- }
745
- </script>
746
-
747
- <style lang="scss">
748
- .n-field-tree {
749
- .q-field__input--padding {
750
- padding-left: 4px;
751
- min-width: 50px !important;
752
- cursor: text;
753
- }
754
- }
755
- </style>
1
+ <template>
2
+ <q-field
3
+ class="n-field-tree"
4
+ :model-value="showValue"
5
+ :disable="disable"
6
+ :readonly="readonly"
7
+ :clearable="clearable"
8
+ for="field-tree"
9
+ @focus="onFieldFocus"
10
+ @blur="onFieldBlur"
11
+ @clear="onFieldClear"
12
+ v-bind="$attrs"
13
+ >
14
+ <template v-slot:control>
15
+
16
+ <template v-if="multiple">
17
+
18
+ <template v-if="treeTickedNodes.length">
19
+
20
+ <!-- 多选插槽 -->
21
+ <slot
22
+ name="selected"
23
+ :ticked="treeTickedNodes"
24
+ :remove="onRemoveItem"
25
+ v-if="$slots.ticked"
26
+ />
27
+
28
+ <!-- 显示折叠的值数量 -->
29
+ <q-chip
30
+ dense
31
+ :label="`+${treeTickedNodes.length}`"
32
+ v-else-if="collapseTags"
33
+ />
34
+
35
+ <!-- 多选标签 -->
36
+ <template v-else>
37
+ <q-chip
38
+ v-for="(item, index) in treeTickedNodes"
39
+ :key="`item-${index}`"
40
+ :label="item.label"
41
+ dense
42
+ :removable="! readonly && ! disable"
43
+ @remove="onRemoveItem(index)"
44
+ />
45
+ </template>
46
+ </template>
47
+
48
+ <!-- 占位符-->
49
+ <span class="n-placeholder" v-else-if="placeholder">{{placeholder}}</span>
50
+ </template>
51
+
52
+ <!-- 显示文字 -->
53
+ <span v-else-if="showValue">{{showValue}}</span>
54
+
55
+ <!-- 占位符-->
56
+ <span class="n-placeholder" v-else-if="placeholder">{{placeholder}}</span>
57
+
58
+ <!-- 筛选输入框 -->
59
+ <input
60
+ ref="inputRef"
61
+ class="q-field__input q-placeholder col q-field__input--padding"
62
+ v-model="inputValue"
63
+ v-if="filter"
64
+ />
65
+ </template>
66
+
67
+ <template v-slot:before v-if="$slots.before">
68
+ <slot name="before" />
69
+ </template>
70
+ <template v-slot:prepend v-if="$slots.prepend">
71
+ <slot name="prepend" />
72
+ </template>
73
+ <template v-slot:append v-if="$slots.append">
74
+ <slot name="append" />
75
+ </template>
76
+ <template v-slot:after v-if="$slots.after">
77
+ <slot name="after" />
78
+ </template>
79
+
80
+ <!-- 弹出层代理 -->
81
+ <q-popup-proxy
82
+ ref="popupRef"
83
+ no-refocus
84
+ no-focus
85
+ fit
86
+ @focus="onFieldFocus"
87
+ @show="onPopupShow"
88
+ @before-hide="showPopup = false"
89
+ v-if="! readonly && ! disable"
90
+ >
91
+ <q-card>
92
+ <!-- 树 -->
93
+ <n-tree
94
+ ref="treeRef"
95
+ style="min-width:260px;max-height:380px;"
96
+ :filter="inputValue"
97
+ :nodes="currentTreeNodes"
98
+ :node-key="nodeKey"
99
+ :label-key="labelKey"
100
+ :ticked="treeTicked"
101
+ @update:ticked="emitModelValue"
102
+ v-model:expanded="treeExpanded"
103
+ :tick-strategy="multiple ? (strict ? 'strict' : 'leaf') : 'none'"
104
+ :accordion="accordion"
105
+ v-bind="treeProps"
106
+ v-if="showTree"
107
+ >
108
+ <template v-slot:default-header="{ node }">
109
+ <div
110
+ :class="{
111
+ 'text-primary': checkTreeNodeActive(node),
112
+ }"
113
+ @click="onNode($event, node)"
114
+ >{{node.label}}</div>
115
+ </template>
116
+ </n-tree>
117
+
118
+ <!-- loading -->
119
+ <div class="flex flex-center" style="height:100px" v-else>
120
+ <q-spinner
121
+ color="primary"
122
+ size="3em"
123
+ />
124
+ </div>
125
+
126
+ </q-card>
127
+ </q-popup-proxy>
128
+ </q-field>
129
+ </template>
130
+
131
+ <script>
132
+ import { ref, computed, watch, onUpdated, nextTick } from 'vue'
133
+
134
+ import $n_has from 'lodash/has'
135
+ import $n_uniq from 'lodash/uniq'
136
+ import $n_concat from 'lodash/concat'
137
+ import $n_isFunction from 'lodash/isFunction'
138
+
139
+ import $n_indexOf from '@netang/utils/indexOf'
140
+ import $n_isRequired from '@netang/utils/isRequired'
141
+ import $n_isValidArray from '@netang/utils/isValidArray'
142
+ import $n_isValidValue from '@netang/utils/isValidValue'
143
+ import $n_isValidObject from '@netang/utils/isValidObject'
144
+ import $n_runAsync from '@netang/utils/runAsync'
145
+
146
+ export default {
147
+
148
+ /**
149
+ * 标识
150
+ */
151
+ name: 'NFieldTree',
152
+
153
+ /**
154
+ * 声明属性
155
+ */
156
+ props: {
157
+ // 值 v-model
158
+ modelValue: {
159
+ required: true,
160
+ },
161
+ // 树展开节点
162
+ expanded: Array, // v-model:expanded
163
+ // 节点数组
164
+ // Array: 初始节点数据数据组
165
+ // Function: 获取初始节点数据的方法
166
+ nodes: [ Array, Function ],
167
+ // 唯一的节点键值
168
+ nodeKey: {
169
+ type: String,
170
+ default: 'id',
171
+ },
172
+ // 标签字段
173
+ labelKey: {
174
+ type: String,
175
+ default: 'label',
176
+ },
177
+ // 是否可选任意一级(true:可选任意一级, false: 仅能选叶子节点)
178
+ strict: Boolean,
179
+ // 是否多选
180
+ multiple: Boolean,
181
+ // 手风琴模式
182
+ accordion: Boolean,
183
+ // 是否显示选中值的完整路径
184
+ showAllLevels: {
185
+ type: Boolean,
186
+ default: true,
187
+ },
188
+ // 是否开启筛选
189
+ filter: Boolean,
190
+ // 是否折叠标签(多选模式有效)
191
+ collapseTags: Boolean,
192
+ // 占位符
193
+ placeholder: String,
194
+ // 是否可清除
195
+ clearable: Boolean,
196
+ // 是否禁用
197
+ disable: Boolean,
198
+ // 是否只读
199
+ readonly: Boolean,
200
+ // 树声明属性
201
+ treeProps: Object,
202
+ },
203
+
204
+ /**
205
+ * 声明事件
206
+ */
207
+ emits: [
208
+ 'update:modelValue',
209
+ ],
210
+
211
+ /**
212
+ * 组合式
213
+ */
214
+ setup(props, { emit }) {
215
+
216
+ // ==========【数据】============================================================================================
217
+
218
+ // 是否为初始加载树节点树
219
+ const isDefaultLoadNodes = $n_isFunction(props.nodes)
220
+
221
+ // 树节点是否已加载
222
+ let __treeNodesLoaded = false
223
+
224
+ // 输入框节点
225
+ const inputRef = ref(null)
226
+
227
+ // 输入框值
228
+ const inputValue = ref('')
229
+
230
+ // 弹出层节点
231
+ const popupRef = ref(null)
232
+
233
+ // 是否显示弹出层
234
+ const showPopup = ref(false)
235
+
236
+ // 是否显示树(如果为非初始加载树节点树, 则直接显示)
237
+ const showTree = ref(! isDefaultLoadNodes)
238
+
239
+ // 树节点
240
+ const treeRef = ref(null)
241
+
242
+ // 当前树节点数据
243
+ const currentTreeNodes = ref(isDefaultLoadNodes ? [] : props.nodes)
244
+
245
+ // tree all
246
+ const treeAll = ref(getTreeAll())
247
+
248
+ // 树展开数据
249
+ const treeExpanded = ref(getExpanded())
250
+
251
+ // 树选择数据
252
+ const treeTicked = ref(formatModelValue(props.modelValue))
253
+
254
+ // 如果为初始加载树节点树
255
+ if (isDefaultLoadNodes) {
256
+
257
+ // 初始加载节点
258
+ defaultLoadNodes()
259
+ .finally()
260
+ }
261
+
262
+ // ==========【计算属性】=========================================================================================
263
+
264
+ /**
265
+ * 树选择节点数据
266
+ */
267
+ const treeTickedNodes = computed(function () {
268
+
269
+ const lists = []
270
+
271
+ const hasTreeAll = $n_isValidObject(treeAll.value)
272
+
273
+ for (const treeKey of treeTicked.value) {
274
+ lists.push({
275
+ id: treeKey,
276
+ label: hasTreeAll && $n_has(treeAll.value, treeKey) ?
277
+ treeAll.value[treeKey][props.showAllLevels ? 'path' : 'label']
278
+ : treeKey
279
+ })
280
+ }
281
+
282
+ return lists
283
+ })
284
+
285
+ /**
286
+ * 显示值
287
+ */
288
+ const showValue = computed(function () {
289
+
290
+ // 如果有已选数据
291
+ return $n_isValidArray(treeTickedNodes.value)
292
+ // 取已选数据第一条的标签
293
+ ? treeTickedNodes.value[0].label
294
+ : ''
295
+ })
296
+
297
+ // ==========【监听数据】=========================================================================================
298
+
299
+ /**
300
+ * 监听声明节点
301
+ */
302
+ watch(() => props.nodes, defaultLoadNodes)
303
+
304
+ /**
305
+ * 监听声明值
306
+ */
307
+ watch(() => props.modelValue, function(val) {
308
+
309
+ // 设置选中数据
310
+ treeTicked.value = formatModelValue(val)
311
+
312
+ // 设置输入框焦点
313
+ setInputFocus()
314
+
315
+ // 设置输入框文字选中
316
+ setInputSelection()
317
+ })
318
+
319
+ /**
320
+ * 监听输入框值
321
+ */
322
+ watch(inputValue, function (val) {
323
+ if (
324
+ // 如果弹出层是隐藏的
325
+ ! showPopup.value
326
+ // 如果输入框有值
327
+ && $n_isValidValue(val)
328
+ ) {
329
+ // 显示弹出层
330
+ popupRef.value.show()
331
+ }
332
+ })
333
+
334
+ // ==========【方法】=============================================================================================
335
+
336
+ /**
337
+ * 获取树子节点
338
+ */
339
+ function _getTreeChildren(all, data, pid, pPath) {
340
+ for (const item of data) {
341
+
342
+ const label = item[props.labelKey]
343
+
344
+ const path = pPath ? (pPath + ' / ' + label) : label
345
+
346
+ all[item[props.nodeKey]] = {
347
+ id: item[props.nodeKey],
348
+ pid,
349
+ label,
350
+ children: item.children,
351
+ path,
352
+ }
353
+
354
+ // 如果是父节点
355
+ if ($n_isValidArray(item.children)) {
356
+ _getTreeChildren(all, item.children, item.id, path)
357
+ }
358
+ }
359
+ }
360
+
361
+ /**
362
+ * 获取 tree all
363
+ */
364
+ function getTreeAll() {
365
+
366
+ const all = {}
367
+
368
+ // 如果当前树节点数据为有效数组
369
+ if ($n_isValidArray(currentTreeNodes.value)) {
370
+ _getTreeChildren(all, currentTreeNodes.value, 0, '')
371
+ }
372
+
373
+ return all
374
+ }
375
+
376
+ /**
377
+ * 获取展开节点数组
378
+ */
379
+ function getExpanded() {
380
+
381
+ let expanded = []
382
+
383
+ if (
384
+ // 如果是单选
385
+ ! props.multiple
386
+ // 如果有值
387
+ && $n_isRequired(props.modelValue)
388
+ // 如果有 tree all
389
+ && $n_isValidObject(treeAll.value)
390
+ // 存在节点
391
+ && $n_has(treeAll.value, props.modelValue)
392
+ ) {
393
+ // 获取父节点
394
+ function getParent({ id, pid, children }) {
395
+
396
+ // 如果是父级节点
397
+ if ($n_isValidArray(children)) {
398
+ // 设为展开
399
+ expanded.push(id)
400
+ }
401
+
402
+ // 如果有父节点, 则继续向上寻找
403
+ if (pid && $n_has(treeAll.value, pid)) {
404
+ getParent(treeAll.value[pid])
405
+ }
406
+ }
407
+
408
+ getParent(treeAll.value[props.modelValue])
409
+
410
+ if (props.expanded) {
411
+ expanded = $n_uniq($n_concat(expanded, props.expanded))
412
+ }
413
+ }
414
+
415
+ return expanded
416
+ }
417
+
418
+ /**
419
+ * 格式化传值
420
+ */
421
+ function formatModelValue(val) {
422
+
423
+ // 如果是多选
424
+ if (props.multiple) {
425
+ return $n_isValidArray(val) ? val : []
426
+ }
427
+
428
+ // 如果为有效值
429
+ if ($n_isRequired(val)) {
430
+ return [ val ]
431
+ }
432
+
433
+ return []
434
+ }
435
+
436
+ /**
437
+ * 触发更新值
438
+ */
439
+ function emitModelValue(val) {
440
+
441
+ // 触发更新值
442
+ emit('update:modelValue', val)
443
+ }
444
+
445
+ /**
446
+ * 点击节点
447
+ */
448
+ function onNode(e, { id, children }) {
449
+
450
+ // 如果是多选
451
+ if (props.multiple) {
452
+
453
+ // 如果是父节点
454
+ if ($n_isValidArray(children)) {
455
+
456
+ // 则无任何操作
457
+ return
458
+ }
459
+
460
+ // 克隆已选树数据
461
+ const _ticked = [...treeTicked.value]
462
+
463
+ // 获取值在树数据中的索引
464
+ const index = $n_indexOf(_ticked, id)
465
+
466
+ // 如果在数据中
467
+ if (index > -1) {
468
+ // 则删除
469
+ _ticked.splice(index, 1)
470
+ // 否则
471
+ } else {
472
+ // 添加
473
+ _ticked.push(id)
474
+ }
475
+
476
+ // 触发更新值
477
+ // 设置树选择数据
478
+ emitModelValue(_ticked)
479
+
480
+ // 否则是单选
481
+ } else {
482
+
483
+ if (
484
+ // 如果是父节点
485
+ $n_isValidArray(children)
486
+ // 如果仅可选择叶子节点
487
+ && ! props.strict
488
+ ) {
489
+ // 则无任何操作
490
+ return
491
+ }
492
+
493
+ // 触发更新值
494
+ // 设置树选择数据
495
+ emitModelValue(id)
496
+
497
+ // 则停止冒泡
498
+ e.preventDefault()
499
+ e.stopPropagation()
500
+
501
+ // 则关闭弹出层
502
+ popupRef.value.hide()
503
+ }
504
+ }
505
+
506
+ /**
507
+ * 移除单个节点
508
+ */
509
+ function onRemoveItem(index) {
510
+
511
+ // 克隆已选树数据
512
+ const _ticked = [...treeTicked.value]
513
+
514
+ // 删除该节点
515
+ _ticked.splice(index, 1)
516
+
517
+ // 触发更新值
518
+ // 设置树选择数据
519
+ emitModelValue(_ticked)
520
+ }
521
+
522
+ /**
523
+ * 字段获取焦点触发
524
+ */
525
+ function onFieldFocus(e) {
526
+
527
+ // 停止冒泡
528
+ e.stopPropagation()
529
+
530
+ // 设置输入框焦点
531
+ setInputFocus()
532
+
533
+ // window.scrollTo(window.pageXOffset || window.scrollX || document.body.scrollLeft || 0, 0)
534
+ }
535
+
536
+ /**
537
+ * 字段失去焦点触发
538
+ */
539
+ function onFieldBlur(e) {
540
+
541
+ // 停止冒泡
542
+ e.stopPropagation()
543
+
544
+ if (
545
+ // 如果开启筛选
546
+ props.filter
547
+ // 如果没有显示弹出层
548
+ && ! showPopup.value
549
+ ) {
550
+ // 清空输入框值
551
+ inputValue.value = ''
552
+ }
553
+ }
554
+
555
+ /**
556
+ * 字段清空触发
557
+ */
558
+ function onFieldClear() {
559
+
560
+ // 触发更新值
561
+ // 清空树数据
562
+ emitModelValue(props.multiple ? [] : '')
563
+
564
+ // 隐藏弹出层
565
+ popupRef.value.hide()
566
+ }
567
+
568
+ /**
569
+ * 弹出层显示回调
570
+ */
571
+ async function onPopupShow() {
572
+
573
+ // 显示弹出层
574
+ showPopup.value = true
575
+
576
+ // 设置输入框焦点
577
+ setInputFocus()
578
+
579
+ if (
580
+ // 如果树已加载过了
581
+ __treeNodesLoaded
582
+ // 如果树已显示
583
+ || showTree.value
584
+ ) {
585
+ // 则无任何操作
586
+ return
587
+ }
588
+
589
+ // 初始加载节点
590
+ await defaultLoadNodes()
591
+ }
592
+
593
+ /**
594
+ * 设置输入框文字选中
595
+ */
596
+ function setInputSelection() {
597
+ if (
598
+ // 如果开启筛选
599
+ props.filter
600
+ // 如果有输入框节点
601
+ && inputRef.value
602
+ // 如果输入框有值
603
+ && inputValue.value.length
604
+ ) {
605
+ // 全选文字
606
+ inputRef.value.select()
607
+ // inputRef.value.setSelectionRange(0, inputValue.value.length)
608
+ }
609
+ }
610
+
611
+ /**
612
+ * 设置输入框焦点
613
+ */
614
+ function setInputFocus() {
615
+ if (
616
+ // 如果开启筛选
617
+ props.filter
618
+ // 如果有输入框节点
619
+ && inputRef.value
620
+ ) {
621
+ inputRef.value.focus()
622
+ }
623
+ }
624
+
625
+ /**
626
+ * 树节点是否激活
627
+ */
628
+ function checkTreeNodeActive({ id }) {
629
+ return $n_indexOf(treeTicked.value, id) > -1
630
+ }
631
+
632
+ /**
633
+ * 初始加载节点
634
+ */
635
+ async function defaultLoadNodes() {
636
+
637
+ // 如果是初始加载树节点树
638
+ if ($n_isFunction(props.nodes)) {
639
+
640
+ // 隐藏树
641
+ showTree.value = false
642
+
643
+ // 下次 DOM 更新
644
+ await nextTick()
645
+
646
+ // 通过自定义方法获取树节点数组
647
+ const resNodes = await $n_runAsync(props.nodes)()
648
+
649
+ if ($n_isValidArray(resNodes)) {
650
+
651
+ // 设置当前树节点数组
652
+ currentTreeNodes.value = resNodes
653
+
654
+ // 设置 tree all
655
+ treeAll.value = getTreeAll()
656
+
657
+ // 设置开数据
658
+ treeExpanded.value = getExpanded()
659
+
660
+ } else {
661
+
662
+ // 设置当前树节点数组
663
+ currentTreeNodes.value = []
664
+
665
+ // 设置 tree all
666
+ treeAll.value = getTreeAll()
667
+ }
668
+
669
+ // 否则为节点数组数据
670
+ } else {
671
+
672
+ // 设置当前树节点数组
673
+ currentTreeNodes.value = $n_isValidArray(props.nodes) ? props.nodes : []
674
+
675
+ // 设置 tree all
676
+ treeAll.value = getTreeAll()
677
+ }
678
+
679
+ // 设置显示树
680
+ showTree.value = true
681
+
682
+ // 树已加载
683
+ __treeNodesLoaded = true
684
+ }
685
+
686
+ // ==========【生命周期】=========================================================================================
687
+
688
+ /**
689
+ * 在组件因为响应式状态变更而更新其 DOM 树之后调用
690
+ */
691
+ onUpdated(function () {
692
+ if ($n_has(popupRef.value, 'currentComponent.ref.updatePosition')) {
693
+ popupRef.value.currentComponent.ref.updatePosition()
694
+ }
695
+ })
696
+
697
+ // ==========【返回】=============================================================================================
698
+
699
+ return {
700
+ // 显示值
701
+ showValue,
702
+ // 输入框节点
703
+ inputRef,
704
+ // 输入框值
705
+ inputValue,
706
+ // 弹出层节点
707
+ popupRef,
708
+ // 是否显示弹出层
709
+ showPopup,
710
+ // 是否显示树
711
+ showTree,
712
+ // 树节点
713
+ treeRef,
714
+ // 树选择数据
715
+ treeTicked,
716
+ // 树选择节点数据
717
+ treeTickedNodes,
718
+ // 树展开数据
719
+ treeExpanded,
720
+ // 当前树节点数据
721
+ currentTreeNodes,
722
+
723
+ // 触发更新值
724
+ emitModelValue,
725
+ // 点击节点
726
+ onNode,
727
+ // 移除单个
728
+ onRemoveItem,
729
+
730
+ // 字段获取焦点触发
731
+ onFieldFocus,
732
+ // 字段失去焦点触发
733
+ onFieldBlur,
734
+ // 字段清空触发
735
+ onFieldClear,
736
+
737
+ // 弹出层显示回调
738
+ onPopupShow,
739
+
740
+ // 节点是否激活
741
+ checkTreeNodeActive,
742
+ }
743
+ },
744
+ }
745
+ </script>
746
+
747
+ <style lang="scss">
748
+ .n-field-tree {
749
+ .q-field__input--padding {
750
+ padding-left: 4px;
751
+ min-width: 50px !important;
752
+ cursor: text;
753
+ }
754
+ }
755
+ </style>