@netang/quasar 0.2.19 → 0.2.21

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