@netang/quasar 0.1.36 → 0.1.38

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 (231) 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/dialog/img-viewer/index.vue +646 -0
  7. package/components/dialog/index.vue +372 -372
  8. package/components/editor-code/index.vue +325 -325
  9. package/components/field-table/index.vue +1227 -1227
  10. package/components/field-tree/index.vue +757 -757
  11. package/components/img/index.vue +211 -202
  12. package/components/mixed-table/index.vue +473 -473
  13. package/components/mixed-table-splitter/index.vue +377 -377
  14. package/components/private/components/move-to-tree/index.vue +154 -154
  15. package/components/private/edit-power-data/index.vue +846 -846
  16. package/components/private/table-visible-columns-button/index.vue +109 -109
  17. package/components/render/index.vue +127 -127
  18. package/components/search/index.vue +224 -224
  19. package/components/search-item/index.vue +212 -212
  20. package/components/select/index.vue +177 -177
  21. package/components/table/index.vue +513 -513
  22. package/components/table-column-fixed/index.vue +110 -110
  23. package/components/table-summary/index.vue +107 -107
  24. package/components/tree/index.vue +79 -31
  25. package/docs/404.html +33 -33
  26. package/docs/assets/404.html-60b35caa.js +1 -1
  27. package/docs/assets/404.html-d1e63d77.js +1 -1
  28. package/docs/assets/alert.html-b2a2a72f.js +5 -5
  29. package/docs/assets/alert.html-ba46d137.js +1 -1
  30. package/docs/assets/app-9f30aa4b.js +6 -6
  31. package/docs/assets/area.html-01b9b58d.js +42 -42
  32. package/docs/assets/area.html-9a4fce6a.js +1 -1
  33. package/docs/assets/arr.html-145d27e7.js +1 -1
  34. package/docs/assets/arr.html-674e65ab.js +11 -11
  35. package/docs/assets/auth.html-579fa830.js +1 -1
  36. package/docs/assets/auth.html-8544ed95.js +8 -8
  37. package/docs/assets/bus.html-c71254aa.js +1 -1
  38. package/docs/assets/bus.html-dc7d3d19.js +6 -6
  39. package/docs/assets/column-title.html-c735cb5a.js +3 -3
  40. package/docs/assets/column-title.html-e9316762.js +1 -1
  41. package/docs/assets/confirm.html-ddfdc27f.js +10 -10
  42. package/docs/assets/confirm.html-ef3e2bef.js +1 -1
  43. package/docs/assets/copy.html-d20345b6.js +1 -1
  44. package/docs/assets/copy.html-ef8c8571.js +13 -13
  45. package/docs/assets/data.html-6432175d.js +30 -30
  46. package/docs/assets/data.html-a3b05d5b.js +1 -1
  47. package/docs/assets/dialog.html-1f698e5a.js +1 -1
  48. package/docs/assets/dialog.html-62902b83.js +68 -68
  49. package/docs/assets/dialog.html-baea77c9.js +1 -1
  50. package/docs/assets/dialog.html-bb082fc4.js +1 -1
  51. package/docs/assets/dict.html-1311da3d.js +23 -23
  52. package/docs/assets/dict.html-b96fbf0c.js +1 -1
  53. package/docs/assets/dictOptions.html-7c4f40a5.js +1 -1
  54. package/docs/assets/dictOptions.html-fb99d175.js +5 -5
  55. package/docs/assets/dragger.html-668d3efa.js +1 -1
  56. package/docs/assets/dragger.html-749d585a.js +1 -1
  57. package/docs/assets/editor-code.html-6ab26ea9.js +1 -1
  58. package/docs/assets/editor-code.html-d196205d.js +1 -1
  59. package/docs/assets/empty.html-1c139131.js +1 -1
  60. package/docs/assets/empty.html-1e9c441d.js +1 -1
  61. package/docs/assets/field-date.html-069fdb13.js +1 -1
  62. package/docs/assets/field-date.html-ad204aa9.js +1 -1
  63. package/docs/assets/field-table.html-ce480f03.js +1 -1
  64. package/docs/assets/field-table.html-d9236160.js +1 -1
  65. package/docs/assets/field-text.html-7277c62f.js +1 -1
  66. package/docs/assets/field-text.html-ccb4cecf.js +1 -1
  67. package/docs/assets/field-tree.html-519bfb45.js +1 -1
  68. package/docs/assets/field-tree.html-fdc748d6.js +1 -1
  69. package/docs/assets/form.html-2b562c37.js +2 -2
  70. package/docs/assets/form.html-75104cd5.js +1 -1
  71. package/docs/assets/framework-204010b2.js +5 -5
  72. package/docs/assets/getData.html-990e3787.js +1 -1
  73. package/docs/assets/getData.html-bb72025f.js +34 -34
  74. package/docs/assets/getFile.html-42368004.js +1 -1
  75. package/docs/assets/getFile.html-99abd054.js +3 -3
  76. package/docs/assets/getImage.html-3429c5a1.js +1 -1
  77. package/docs/assets/getImage.html-4d886d83.js +3 -3
  78. package/docs/assets/getTime.html-7435f922.js +1 -1
  79. package/docs/assets/getTime.html-b37f49eb.js +20 -20
  80. package/docs/assets/img.html-7d1da657.js +1 -1
  81. package/docs/assets/img.html-fbea1105.js +1 -1
  82. package/docs/assets/index.html-1695dd7c.js +1 -1
  83. package/docs/assets/index.html-65a4aa67.js +1 -1
  84. package/docs/assets/index.html-7b98d5bd.js +1 -1
  85. package/docs/assets/index.html-c01f2648.js +1 -1
  86. package/docs/assets/input-number.html-0b250d2a.js +1 -1
  87. package/docs/assets/input-number.html-a8eb0378.js +1 -1
  88. package/docs/assets/list-menu-item.html-7f1b4611.js +1 -1
  89. package/docs/assets/list-menu-item.html-84ed5ab8.js +1 -1
  90. package/docs/assets/list-menu.html-28b4163f.js +1 -1
  91. package/docs/assets/list-menu.html-cb6ba95b.js +1 -1
  92. package/docs/assets/loading.html-dae9e39d.js +6 -6
  93. package/docs/assets/loading.html-dc74c9e6.js +1 -1
  94. package/docs/assets/notify.html-e6c4c514.js +1 -1
  95. package/docs/assets/notify.html-f2c4d914.js +8 -8
  96. package/docs/assets/power-page.html-32e02f82.js +1 -1
  97. package/docs/assets/power-page.html-485e77da.js +1 -1
  98. package/docs/assets/power.html-d258cc19.js +93 -93
  99. package/docs/assets/power.html-e490bd32.js +1 -1
  100. package/docs/assets/previewImage.html-6a6b4245.js +1 -1
  101. package/docs/assets/previewImage.html-c5b7e945.js +2 -2
  102. package/docs/assets/price.html-1882c548.js +19 -19
  103. package/docs/assets/price.html-94d3f5be.js +1 -1
  104. package/docs/assets/price.html-d213df0f.js +1 -1
  105. package/docs/assets/price.html-deaf880f.js +1 -1
  106. package/docs/assets/render.html-8efcbdd4.js +1 -1
  107. package/docs/assets/render.html-df228e38.js +1 -1
  108. package/docs/assets/rule.html-2cd57fc2.js +13 -13
  109. package/docs/assets/rule.html-61662001.js +1 -1
  110. package/docs/assets/ruleValid.html-04fe2552.js +1 -1
  111. package/docs/assets/ruleValid.html-e0a776af.js +14 -14
  112. package/docs/assets/search-0782d0d1.svg +1 -1
  113. package/docs/assets/search-item.html-3f75394c.js +1 -1
  114. package/docs/assets/search-item.html-4e942ecd.js +1 -1
  115. package/docs/assets/search.html-2807043e.js +1 -1
  116. package/docs/assets/search.html-c24f8806.js +1 -1
  117. package/docs/assets/select.html-00d0607c.js +1 -1
  118. package/docs/assets/select.html-de7731f5.js +1 -1
  119. package/docs/assets/splitter.html-56f51a70.js +1 -1
  120. package/docs/assets/splitter.html-f5c836d7.js +1 -1
  121. package/docs/assets/style-161e43ab.css +1 -1
  122. package/docs/assets/symbols.html-a6aea4bf.js +1 -1
  123. package/docs/assets/symbols.html-b1f65bad.js +21 -21
  124. package/docs/assets/table-column-fixed.html-3a69e7b2.js +1 -1
  125. package/docs/assets/table-column-fixed.html-e763c38b.js +1 -1
  126. package/docs/assets/table-pagination.html-236934d3.js +1 -1
  127. package/docs/assets/table-pagination.html-c37ee2ac.js +1 -1
  128. package/docs/assets/table-splitter.html-07eab15c.js +1 -1
  129. package/docs/assets/table-splitter.html-7670ee65.js +1 -1
  130. package/docs/assets/table-summary.html-04db434f.js +1 -1
  131. package/docs/assets/table-summary.html-943c65a0.js +1 -1
  132. package/docs/assets/table.html-36253ad7.js +1 -1
  133. package/docs/assets/table.html-7f9c5d1b.js +38 -38
  134. package/docs/assets/table.html-93d53dc8.js +1 -1
  135. package/docs/assets/table.html-ac99b9cb.js +1 -1
  136. package/docs/assets/thumbnail.html-bab1976b.js +1 -1
  137. package/docs/assets/thumbnail.html-eb64e5e8.js +1 -1
  138. package/docs/assets/timestamp.html-4e54f79b.js +13 -13
  139. package/docs/assets/timestamp.html-d0e1b88a.js +1 -1
  140. package/docs/assets/toast.html-58ecbe21.js +1 -1
  141. package/docs/assets/toast.html-c9b9d36b.js +6 -6
  142. package/docs/assets/toolbar.html-83d9f97c.js +1 -1
  143. package/docs/assets/toolbar.html-ff7b8c92.js +1 -1
  144. package/docs/assets/tree.html-d07cbe79.js +23 -23
  145. package/docs/assets/tree.html-ea04193e.js +1 -1
  146. package/docs/assets/uploader-query.html-05590718.js +1 -1
  147. package/docs/assets/uploader-query.html-3175bac5.js +1 -1
  148. package/docs/assets/uploader.html-36da4394.js +2 -2
  149. package/docs/assets/uploader.html-6b5f3079.js +1 -1
  150. package/docs/assets/uploader.html-b9340b57.js +1 -1
  151. package/docs/assets/uploader.html-bc1c22e3.js +1 -1
  152. package/docs/assets/value-format.html-8ae3d47d.js +1 -1
  153. package/docs/assets/value-format.html-afa99b3d.js +1 -1
  154. package/docs/components/column-title.html +35 -35
  155. package/docs/components/data.html +62 -62
  156. package/docs/components/dialog.html +33 -33
  157. package/docs/components/dragger.html +33 -33
  158. package/docs/components/editor-code.html +33 -33
  159. package/docs/components/empty.html +33 -33
  160. package/docs/components/field-date.html +33 -33
  161. package/docs/components/field-table.html +33 -33
  162. package/docs/components/field-text.html +33 -33
  163. package/docs/components/field-tree.html +33 -33
  164. package/docs/components/img.html +33 -33
  165. package/docs/components/input-number.html +33 -33
  166. package/docs/components/list-menu-item.html +33 -33
  167. package/docs/components/list-menu.html +33 -33
  168. package/docs/components/power-page.html +33 -33
  169. package/docs/components/price.html +33 -33
  170. package/docs/components/render.html +33 -33
  171. package/docs/components/search-item.html +33 -33
  172. package/docs/components/search.html +33 -33
  173. package/docs/components/select.html +33 -33
  174. package/docs/components/splitter.html +33 -33
  175. package/docs/components/table-column-fixed.html +33 -33
  176. package/docs/components/table-pagination.html +33 -33
  177. package/docs/components/table-splitter.html +33 -33
  178. package/docs/components/table-summary.html +33 -33
  179. package/docs/components/table.html +33 -33
  180. package/docs/components/thumbnail.html +33 -33
  181. package/docs/components/toolbar.html +33 -33
  182. package/docs/components/uploader-query.html +33 -33
  183. package/docs/components/uploader.html +33 -33
  184. package/docs/components/value-format.html +33 -33
  185. package/docs/index.html +33 -33
  186. package/docs/utils/alert.html +37 -37
  187. package/docs/utils/area.html +74 -74
  188. package/docs/utils/arr.html +43 -43
  189. package/docs/utils/auth.html +40 -40
  190. package/docs/utils/bus.html +38 -38
  191. package/docs/utils/confirm.html +42 -42
  192. package/docs/utils/copy.html +45 -45
  193. package/docs/utils/dialog.html +100 -100
  194. package/docs/utils/dict.html +55 -55
  195. package/docs/utils/dictOptions.html +37 -37
  196. package/docs/utils/form.html +34 -34
  197. package/docs/utils/getData.html +66 -66
  198. package/docs/utils/getFile.html +35 -35
  199. package/docs/utils/getImage.html +35 -35
  200. package/docs/utils/getTime.html +52 -52
  201. package/docs/utils/index.html +33 -33
  202. package/docs/utils/loading.html +38 -38
  203. package/docs/utils/notify.html +40 -40
  204. package/docs/utils/power.html +125 -125
  205. package/docs/utils/previewImage.html +34 -34
  206. package/docs/utils/price.html +51 -51
  207. package/docs/utils/rule.html +45 -45
  208. package/docs/utils/ruleValid.html +46 -46
  209. package/docs/utils/symbols.html +53 -53
  210. package/docs/utils/table.html +70 -70
  211. package/docs/utils/timestamp.html +45 -45
  212. package/docs/utils/toast.html +38 -38
  213. package/docs/utils/tree.html +55 -55
  214. package/docs/utils/uploader.html +34 -34
  215. package/package.json +1 -1
  216. package/sass/common.scss +179 -179
  217. package/sass/quasar/field.scss +250 -250
  218. package/sass/quasar/table.scss +161 -161
  219. package/sass/variables.scss +138 -138
  220. package/utils/$form.js +72 -72
  221. package/utils/$power.js +1233 -1233
  222. package/utils/$render.js +75 -75
  223. package/utils/$table.js +1171 -1171
  224. package/utils/$tree.js +664 -664
  225. package/utils/dialog.js +36 -36
  226. package/utils/dict.js +21 -21
  227. package/utils/getImage.js +167 -153
  228. package/utils/index.js +62 -62
  229. package/utils/previewImage.js +14 -10
  230. package/utils/timestamp.js +18 -18
  231. package/utils/useSearch.js +496 -496
@@ -1,1227 +1,1227 @@
1
- <template>
2
-
3
- <!-- 如果有默认插槽 -->
4
- <template v-if="$slots.default">
5
- <slot
6
- :showValue="showValue"
7
- :selected="selected"
8
- :onRemove="onRemoveSelected"
9
- :onShowDialog="onShowDialog"
10
- :onClear="onFieldClear"
11
- />
12
- </template>
13
-
14
- <!--:class="fieldFocused ? 'q-field&#45;&#45;float q-field&#45;&#45;focused q-field&#45;&#45;highlighted' : ''"-->
15
- <!--:clearable="clearable && (! multiple || collapseTags)"-->
16
- <q-field
17
- class="n-field-table"
18
- :model-value="showValue"
19
- :disable="disable"
20
- :readonly="readonly"
21
- :clearable="clearable"
22
- @focus="onFieldFocus"
23
- @blur="onFieldBlur"
24
- @clear="onFieldClear"
25
- v-bind="$attrs"
26
- v-else
27
- >
28
- <template v-slot:control>
29
-
30
- <template v-if="multiple">
31
- <template v-if="selected.length">
32
-
33
- <!-- 多选插槽 -->
34
- <slot
35
- name="selected"
36
- :selected="selected"
37
- :remove="onRemoveSelected"
38
- v-if="$slots.selected"
39
- />
40
-
41
- <!-- 显示折叠的值数量 -->
42
- <q-chip
43
- dense
44
- :label="`+${selected.length}`"
45
- v-else-if="collapseTags"
46
- />
47
-
48
- <!-- 多选标签 -->
49
- <template v-else>
50
- <q-chip
51
- v-for="(item, index) in selected"
52
- :key="`options-${index}`"
53
- :label="currentFormatLabel(item)"
54
- dense
55
- :removable="! readonly && ! disable"
56
- @remove="onRemoveSelected(index)"
57
- />
58
- </template>
59
- </template>
60
-
61
- <!-- 占位符-->
62
- <span class="n-placeholder" v-else-if="placeholder">{{placeholder}}</span>
63
- </template>
64
-
65
- <!-- 显示文字 -->
66
- <span v-else-if="showValue">{{showValue}}</span>
67
-
68
- <!-- 占位符-->
69
- <span class="n-placeholder" v-else-if="placeholder">{{placeholder}}</span>
70
-
71
- <!-- 筛选输入框 -->
72
- <input
73
- ref="inputRef"
74
- class="q-field__input q-placeholder col q-field__input--padding"
75
- v-model="inputValue"
76
- v-if="filter && ! readonly && ! disable"
77
- />
78
-
79
- </template>
80
-
81
- <!-- 弹出对话框图标 -->
82
- <template v-slot:append v-if="! noDialog && ! readonly && ! disable">
83
- <q-icon
84
- class="cursor-pointer"
85
- name="search"
86
- @click.prevent.stop="onShowDialog"
87
- />
88
- </template>
89
-
90
- <!-- 弹出层代理 -->
91
- <q-popup-proxy
92
- ref="popupRef"
93
- no-refocus
94
- no-focus
95
- fit
96
- @focus="onFieldBlur"
97
- @show="onPopupShow"
98
- @before-hide="showPopup = false"
99
- v-if="! readonly && ! disable"
100
- >
101
- <!-- 快捷表格 -->
102
- <n-table
103
- class="n-table n-field-table__popup-table"
104
- v-model:pagination="tablePagination"
105
- :selected="selected"
106
- @update:selected="emitModelValue"
107
- :row-key="tableRowKey"
108
- :rows="tableRows"
109
- :columns="columns"
110
- :selection="multiple ? 'multiple' : 'none'"
111
- :loading="tableLoading"
112
- :rows-per-page-options="tableRowsPerPageOptions"
113
- @row-click="quickTableRowClick"
114
- @request="tableRequest"
115
- flat
116
- virtual-scroll
117
- dense
118
- v-bind="tableProps"
119
- >
120
- <!-- 图片 -->
121
- <template
122
- v-for="imgName in tableImgNames"
123
- v-slot:[`body-cell-${imgName}`]="props"
124
- >
125
- <q-td :props="props">
126
- <!-- 缩略图 -->
127
- <n-thumbnail
128
- :src="props.row[imgName]"
129
- preview
130
- />
131
- </q-td>
132
- </template>
133
-
134
- <!-- 插槽 -->
135
- <template
136
- v-for="slotName in slotNames"
137
- v-slot:[slotName]="props"
138
- >
139
- <q-td :props="props">
140
- <slot
141
- :name="slotName"
142
- v-bind="props"
143
- />
144
- </q-td>
145
- </template>
146
-
147
- <!-- 翻页 -->
148
- <template v-slot:pagination="props">
149
- <n-table-pagination
150
- :props="props"
151
- no-power
152
- dense
153
- />
154
- </template>
155
- </n-table>
156
- </q-popup-proxy>
157
- </q-field>
158
-
159
- <!-- 弹出对话框 -->
160
- <n-dialog
161
- v-model="showDialog"
162
- width="80%"
163
- :on-confirm="onDialogConfirm"
164
- @before-show="onDialogBeforeShow"
165
- @show="onDialogShow"
166
- @hide="onDialogHide"
167
- cancel
168
- v-bind="dialogProps"
169
- >
170
- <q-page>
171
- <n-mixed-table />
172
- </q-page>
173
- </n-dialog>
174
- </template>
175
-
176
- <script>
177
- import { ref, computed, watch, onUpdated } from 'vue'
178
-
179
- import $n_has from 'lodash/has'
180
- import $n_uniq from 'lodash/uniq'
181
- import $n_cloneDeep from 'lodash/cloneDeep'
182
- import $n_isFunction from 'lodash/isFunction'
183
- import $n_findIndex from 'lodash/findIndex'
184
- import $n_get from 'lodash/get'
185
-
186
- import $n_forEach from '@netang/utils/forEach'
187
- import $n_isValidArray from '@netang/utils/isValidArray'
188
- import $n_join from '@netang/utils/join'
189
- import $n_split from '@netang/utils/split'
190
- import $n_isValidObject from '@netang/utils/isValidObject'
191
- import $n_isValidValue from '@netang/utils/isValidValue'
192
- import $n_isValidString from '@netang/utils/isValidString'
193
- import $n_numberDeep from '@netang/utils/numberDeep'
194
- import $n_sleep from '@netang/utils/sleep'
195
- import $n_http from '@netang/utils/http'
196
- import $n_runAsync from '@netang/utils/runAsync'
197
-
198
- import $n_$power from '../../utils/$power'
199
- import $n_$table from '../../utils/$table'
200
-
201
- import { configs } from '../../utils/config'
202
-
203
- const {
204
- // 字典常量
205
- dicts,
206
- } = configs
207
-
208
- export default {
209
-
210
- /**
211
- * 标识
212
- */
213
- name: 'NFieldTable',
214
-
215
- /**
216
- * 关闭组件 attribute 透传行为
217
- */
218
- inheritAttrs: false,
219
-
220
- /**
221
- * 声明属性
222
- */
223
- props: {
224
- // 值 v-model
225
- modelValue: {
226
- required: true,
227
- },
228
- // 值字段(必填)
229
- valueKey: {
230
- type: String,
231
- required: true,
232
- },
233
- // 标签字段
234
- labelKey: String,
235
- // 值类型
236
- // string: 字符串或数字
237
- // stringArray: 普通数组(包含字符串或数字的一维数组)
238
- // objectArray: 对象数组(包含对象的一维数组)
239
- valueType: {
240
- type: String,
241
- default: 'objectArray'
242
- },
243
- // 值分隔符(值类型为 string 有效)
244
- valueSeparator: {
245
- type: String,
246
- default: ',',
247
- },
248
-
249
- // 请求路由路径
250
- path: String,
251
- // 请求地址(默认为 path)
252
- url: String,
253
- // 请求参数
254
- query: Object,
255
- // 附加请求数据
256
- data: Object,
257
- // 初始不加载已选数据
258
- noDefaultLoadSelected: Boolean,
259
- // 更新值时不加载已选数据
260
- noUpdateLoadSelected: Boolean,
261
- // 格式化显示标签
262
- formatLabel: Function,
263
- // 下拉表格显示的字段数组(空为:[值字段, 标签字段])
264
- showKeys: Array,
265
- // 隐藏搜索字段数组
266
- hideSearchKeys: Array,
267
- // 默认筛选字段(空为:标签字段)
268
- filterKey: String,
269
- // 是否开启筛选
270
- filter: Boolean,
271
- // 表格声明属性
272
- tableProps: Object,
273
- // 对话框声明属性
274
- dialogProps: Object,
275
-
276
- // 关闭对话框
277
- noDialog: Boolean,
278
-
279
- // 表格列数据
280
- columns: Array,
281
- // 行数据
282
- rows: Array,
283
- // 是否多选
284
- multiple: Boolean,
285
- // 多选模式下是否折叠标签
286
- collapseTags: Boolean,
287
- // 占位符
288
- placeholder: String,
289
- // 是否可清除
290
- clearable: Boolean,
291
- // 是否禁用
292
- disable: Boolean,
293
- // 是否只读
294
- readonly: Boolean,
295
- // 输入防抖(毫秒)
296
- inputDebounce: {
297
- type: [ Number, String ],
298
- default: 500
299
- },
300
- // 自定义请求方法
301
- request: Function,
302
- },
303
-
304
- /**
305
- * 声明事件
306
- */
307
- emits: [
308
- 'update:modelValue',
309
- 'update:selected',
310
- ],
311
-
312
- /**
313
- * 组合式
314
- */
315
- setup(props, { emit, slots }) {
316
-
317
- // ==========【计算属性】=========================================================================================
318
-
319
- /**
320
- * 插槽标识
321
- */
322
- const slotNames = computed(function() {
323
- return $n_isValidObject(slots) ? Object.keys(slots) : []
324
- })
325
-
326
- /**
327
- * 当前标签字段
328
- */
329
- const currentlabelKey = computed(function() {
330
- return props.labelKey || props.valueKey
331
- })
332
-
333
- /**
334
- * 当前显示字段
335
- */
336
- const currentShowKeys = computed(function() {
337
- return $n_uniq($n_isValidArray(props.showKeys)
338
- ? props.showKeys
339
- : [ props.valueKey, currentlabelKey.value ])
340
- })
341
-
342
- /**
343
- * 当前搜索字段
344
- */
345
- const currentFilterKey = computed(function() {
346
- return props.filterKey || currentlabelKey.value
347
- })
348
-
349
- /**
350
- * 显示值
351
- */
352
- const showValue = computed(function () {
353
-
354
- // 如果有已选数据
355
- return $n_isValidArray(selected.value)
356
- // 取已选数据第一条
357
- ? currentFormatLabel(selected.value[0])
358
- : ''
359
- })
360
-
361
- // ==========【数据】============================================================================================
362
-
363
- // 创建权限实例
364
- const $power = $n_$power.create({
365
- // 路由路径
366
- path: $n_isValidString(props.path) ? props.path : false,
367
- // 路由参数
368
- query: props.query,
369
- // 关闭权限页面
370
- power: false,
371
- // 禁止对话框注入
372
- $dialog: null,
373
- })
374
-
375
- const {
376
- // 当前路由路径
377
- routePath,
378
- } = $power
379
-
380
- // 创建表格实例
381
- const $table = $n_$table.create({
382
- // 权限实例
383
- $power,
384
- // 请求地址
385
- url: props.url,
386
- // 附加请求数据
387
- data: props.data,
388
- // 获取表格列数据
389
- columns: getTableColumns(),
390
- // 表格行唯一键值
391
- rowKey: props.valueKey,
392
- // 行数据
393
- rows: props.rows,
394
- // 选择类型, 可选值 single multiple none
395
- selection: props.multiple ? 'multiple' : 'single',
396
- // 已选数据
397
- selected: [],
398
- // http 设置
399
- httpSettings: {
400
- // 头部请求
401
- headers: {
402
- // 添加头部查看请求
403
- Pview: 1,
404
- },
405
- },
406
- // 刷新后清空已选数据
407
- refreshResetSelected: false,
408
- // 自定义请求方法
409
- async request({ httpOptions }) {
410
- return $n_isFunction(props.request) ?
411
- // 如果有自定义请求方法
412
- await $n_runAsync(props.request)({
413
- // http 请求参数
414
- httpOptions,
415
- // 对话框是否已显示
416
- showDialog: showDialog.value,
417
- }) :
418
- // 否则请求数据
419
- await $n_http(httpOptions)
420
- },
421
- })
422
-
423
- // 创建睡眠实例
424
- const sleep = $n_sleep()
425
-
426
- // 输入框节点
427
- const inputRef = ref(null)
428
-
429
- // 输入框值
430
- const inputValue = ref('')
431
-
432
- // 弹出层节点
433
- const popupRef = ref(null)
434
-
435
- // 是否显示对话框
436
- const showDialog = ref(false)
437
-
438
- // 是否显示弹出层
439
- const showPopup = ref(false)
440
-
441
- // 当前表格列数据
442
- const columns = getQuickTableColumns()
443
-
444
- // 停止观察值
445
- let stopValueWatcher = false
446
-
447
- // 临时已选数据
448
- let tempSelected = []
449
-
450
- // 初始化已选数据
451
- const selected = ref(valueToSelected(props.modelValue, true, true))
452
-
453
- // 加载已选数据
454
- loadSelected()
455
- .finally()
456
-
457
- // ==========【监听数据】=========================================================================================
458
-
459
- /**
460
- * 监听声明值
461
- */
462
- watch(() => props.modelValue, async function(val) {
463
-
464
- // 如果停止观察值
465
- if (stopValueWatcher === true) {
466
- // 取消停止观察值
467
- stopValueWatcher = false
468
- return
469
- }
470
-
471
- // 值转已选数据
472
- let newSelected = valueToSelected(val, false, false)
473
-
474
- // 如果值类型是数组对象
475
- if (props.valueType === 'objectArray') {
476
-
477
- // 设置已选数据
478
- setSelected(newSelected)
479
-
480
- // 否则值类型是字符串或数组
481
- } else {
482
-
483
- // 初始已选数据
484
- let _selected = []
485
-
486
- // 如果值转已选数据是有效数组
487
- if (newSelected.length) {
488
-
489
- // 当前已选数据
490
- const currentSelected = tempSelected.length ? tempSelected : selected.value
491
-
492
- // 如果有已选数据
493
- if (currentSelected.length) {
494
-
495
- // 新已选数据
496
- _selected = currentSelected.filter(e => newSelected.indexOf(e[props.valueKey]) > -1)
497
-
498
- // 需增加的值
499
- newSelected = newSelected.filter(e => _selected.map(e => e[props.valueKey]).indexOf(e) === -1)
500
- }
501
-
502
- // 需增加的值
503
- if (newSelected.length) {
504
-
505
- // 如果更新值时不加载已选数据
506
- if (props.noUpdateLoadSelected) {
507
- // 请求选择数据
508
- _selected.push(...newSelected.map(e => setSelectedItem(e)))
509
- } else {
510
- // 请求选择数据
511
- _selected.push(...await onRequestSelected(newSelected))
512
- }
513
- }
514
- }
515
-
516
- // 设置已选数据
517
- setSelected(_selected)
518
-
519
- // 清空临时已选数据
520
- tempSelected = []
521
- }
522
-
523
- // 将已选数据转为值
524
- const _value = selectedToValue(selected.value)
525
-
526
- // 如果声明值发生变化
527
- if (_value !== props.modelValue) {
528
- // 停止观察值
529
- stopValueWatcher = true
530
- // 触发更新已选数据
531
- emit('update:modelValue', _value)
532
- }
533
-
534
- // 设置输入框焦点
535
- setInputFocus()
536
-
537
- // 设置输入框文字选中
538
- setInputSelection()
539
-
540
- }, {
541
- // 深度监听
542
- deep: true,
543
- })
544
-
545
- /**
546
- * 监听输入框值
547
- */
548
- watch(inputValue, async function (val) {
549
-
550
- // 延迟执行
551
- await sleep(props.inputDebounce)
552
-
553
- // 是否有值
554
- const hasValue = $n_isValidValue(val)
555
-
556
- const n_search = {}
557
- n_search[currentFilterKey.value] = [
558
- {
559
- // 比较类型
560
- compare: dicts.SEARCH_COMPARE_TYPE__LIKE,
561
- // 值
562
- value: hasValue ? val : '',
563
- }
564
- ]
565
-
566
- // 设置表格传参
567
- $table.setQuery({
568
- n_search,
569
- })
570
-
571
- if (
572
- // 如果弹出层是隐藏的
573
- ! showPopup.value
574
- // 如果输入框有值
575
- && hasValue
576
- ) {
577
- // 显示弹出层节点
578
- showPopupRef()
579
- }
580
-
581
- // 表格重新加载
582
- await $table.tableReload()
583
- })
584
-
585
- // ==========【方法】=============================================================================================
586
-
587
- /**
588
- * 加载已选数据
589
- */
590
- async function loadSelected() {
591
- if (
592
- // 如果值类型不是数组对象
593
- props.valueType !== 'objectArray'
594
- // 如果初始加载已选数据
595
- && ! props.noDefaultLoadSelected
596
- // 如果有请求路由路径
597
- && routePath
598
- ) {
599
- // 获取值数组
600
- const values = valueToSelected(props.modelValue, false, false)
601
- if (values.length) {
602
- // 初始的已选数据
603
- const _selected = await onRequestSelected(values)
604
- const _value = selectedToValue(_selected)
605
-
606
- // 如果声明值未发生变化
607
- if (_value === props.modelValue) {
608
- // 设置已选数据
609
- setSelected(_selected)
610
-
611
- } else {
612
- // 设置临时已选数据
613
- tempSelected = _selected
614
- // 触发更新值
615
- emit('update:modelValue', _value)
616
- }
617
- return
618
- }
619
- }
620
-
621
- // 触发更新已选数据
622
- emit('update:selected', selected.value)
623
- }
624
-
625
- /**
626
- * 触发更新值
627
- */
628
- function emitModelValue(val) {
629
-
630
- // 设置临时已选数据
631
- tempSelected = val
632
-
633
- // 触发更新值
634
- emit('update:modelValue', selectedToValue(val))
635
- }
636
-
637
- /**
638
- * 设置已选数据
639
- */
640
- function setSelected(val) {
641
-
642
- // 设置已选数据
643
- selected.value = val
644
-
645
- // 触发更新已选数据
646
- emit('update:selected', val)
647
- }
648
-
649
- /**
650
- * 当前格式化显示标签
651
- */
652
- function currentFormatLabel(item) {
653
-
654
- // 如果有格式化显示标签方法
655
- if ($n_isFunction(props.formatLabel)) {
656
- // 执行格式化显示标签方法
657
- return props.formatLabel(item)
658
- }
659
-
660
- // 否则显示该值的标签字段
661
- const val = item[currentlabelKey.value]
662
- return $n_isValidValue(val) ? val : item[props.valueKey]
663
- }
664
-
665
- /**
666
- * 设置已选数据的单个元素
667
- */
668
- function setSelectedItem(val) {
669
- const obj = {}
670
- obj[props.valueKey] = val
671
- obj[currentlabelKey.value] = val
672
- return obj
673
- }
674
-
675
- /**
676
- * 值转已选数据
677
- */
678
- function valueToSelected(val, isFirst, toSelected) {
679
-
680
- // 如果值类型是数组对象
681
- if (props.valueType === 'objectArray') {
682
-
683
- // 如果是有效数组
684
- if ($n_isValidArray(val)) {
685
- for (const item of val) {
686
- if (
687
- // 如果元素不是有效对象
688
- ! $n_isValidObject(item)
689
- // 如果元素没有值字段
690
- || ! $n_has(item, props.valueKey)
691
- ) {
692
- return []
693
- }
694
- }
695
- }
696
-
697
- // 否则直接返回
698
- return val
699
- }
700
-
701
- if (
702
- // 非初始化
703
- ! isFirst
704
- // 或初始不加载已选数据
705
- || props.noDefaultLoadSelected
706
- // 或没有路由路径
707
- || ! routePath
708
- ) {
709
- // 将值转为数组
710
- val = props.valueType === 'string' ? $n_split(val, props.valueSeparator) : val
711
-
712
- // 如果是有效数组
713
- if ($n_isValidArray(val)) {
714
- val = val.filter(e => $n_isValidValue(e))
715
- return toSelected ? val.map(e => setSelectedItem(e)) : val
716
- }
717
- }
718
-
719
- return []
720
- }
721
-
722
- /**
723
- * 已选数据转值
724
- */
725
- function selectedToValue(val) {
726
-
727
- // 如果值类型是数组对象
728
- if (props.valueType === 'objectArray') {
729
-
730
- // 则直接返回
731
- return val
732
- }
733
-
734
- // 值数组
735
- const values = val.length
736
- // 如果有已选数据
737
- ? (
738
- props.multiple
739
- // 如果是多选
740
- ? val.map(e => e[props.valueKey])
741
- // 否则是单选
742
- : [ val[0][props.valueKey] ]
743
- )
744
- // 否则为空
745
- : []
746
-
747
- // 如果值类型是数组
748
- if (props.valueType === 'stringArray') {
749
-
750
- // 直接返回数组
751
- return values
752
- }
753
-
754
- // 返回转为分隔符隔开的字符串
755
- return $n_numberDeep($n_join(values, props.valueSeparator))
756
- }
757
-
758
- /**
759
- * 请求选择数据
760
- */
761
- async function onRequestSelected(value) {
762
-
763
- // 请求参数
764
- const httpOptions = {
765
- url: $table.routePath,
766
- data: Object.assign(
767
- // 获取表格请求数据
768
- $table.getTableRequestData({
769
- // filter,
770
- pagination: {
771
- // 页码
772
- page: 1,
773
- // 每页的数据条数
774
- rowsPerPage: value.length,
775
- // 排序字段
776
- sortBy: null,
777
- // 是否降序排列
778
- descending: true,
779
- }
780
- }, false),
781
- {
782
- // 查看字段
783
- n_view: {
784
- // 查看字段
785
- field: props.valueKey,
786
- // 查看值
787
- value,
788
- },
789
- }
790
- ),
791
- // 是否开启防抖(防止重复请求)
792
- debounce: false,
793
- }
794
-
795
- // 请求数据
796
- const { status, data } = $n_isFunction(props.request) ?
797
- // 如果有自定义请求方法
798
- await $n_runAsync(props.request)({
799
- // http 请求参数
800
- httpOptions,
801
- // 对话框是否已显示
802
- showDialog: showDialog.value,
803
- }) :
804
- // 否则请求数据
805
- await $n_http(httpOptions)
806
-
807
- return status && $n_isValidArray($n_get(data, 'rows')) ? data.rows : []
808
- }
809
-
810
- /**
811
- * 获取表格列数据
812
- */
813
- function getTableColumns() {
814
-
815
- let columns
816
-
817
- // 如果有声明路由表格列数据
818
- if ($n_isValidArray(props.columns)) {
819
- columns = $n_cloneDeep(props.columns)
820
-
821
- // 如果有路由路径
822
- } else if (routePath) {
823
- // 否则如果有路由表格列数据
824
- const rawTableColumns = $n_$table.config(routePath, 'columns')
825
- if ($n_isValidArray(rawTableColumns)) {
826
- columns = $n_cloneDeep(rawTableColumns)
827
- }
828
- }
829
-
830
- if ($n_isValidArray(columns)) {
831
- if ($n_isValidArray(props.hideSearchKeys)) {
832
- for (const item of columns) {
833
- if (
834
- props.hideSearchKeys.indexOf(item.name) > -1
835
- && $n_has(item, 'search')
836
- ) {
837
- item.search.hide = true
838
- }
839
- }
840
- }
841
- return columns
842
- }
843
-
844
- return []
845
- }
846
-
847
- /**
848
- * 获取快捷表格列数据
849
- */
850
- function getQuickTableColumns() {
851
-
852
- const columns = []
853
-
854
- // 如果有原始表格列数据
855
- if ($n_isValidArray($table.tableColumns.value)) {
856
-
857
- // 克隆原始表格列数据
858
- const rawTableColumns = $n_cloneDeep($table.tableColumns.value)
859
-
860
- // 快捷表格显示的属性名称数组
861
- $n_forEach(currentShowKeys.value, function (key) {
862
- for (const item of rawTableColumns) {
863
- if (item.name === key) {
864
- // 删除搜索字段
865
- if ($n_has(item, 'search')) {
866
- delete item.search
867
- }
868
- // 删除可见字段
869
- if ($n_has(item, 'visible')) {
870
- delete item.visible
871
- }
872
- columns.push(item)
873
- }
874
- }
875
- })
876
- }
877
-
878
- return columns
879
- }
880
-
881
- /**
882
- * 移除已选数据
883
- */
884
- function onRemoveSelected(index) {
885
- selected.value.splice(index, 1)
886
- }
887
-
888
- /**
889
- * 字段获取焦点触发
890
- */
891
- function onFieldFocus(e) {
892
-
893
- // 停止冒泡
894
- e.stopPropagation()
895
-
896
- // 设置输入框焦点
897
- setInputFocus()
898
-
899
- // window.scrollTo(window.pageXOffset || window.scrollX || document.body.scrollLeft || 0, 0)
900
- }
901
-
902
- /**
903
- * 字段失去焦点触发
904
- */
905
- function onFieldBlur(e) {
906
-
907
- // 停止冒泡
908
- e.stopPropagation()
909
-
910
- if (
911
- // 如果开启筛选
912
- props.filter
913
- // 如果没有显示弹出层
914
- && ! showPopup.value
915
- ) {
916
- // 清空输入框值
917
- inputValue.value = ''
918
- }
919
- }
920
-
921
- /**
922
- * 字段清空触发
923
- */
924
- function onFieldClear() {
925
-
926
- // 触发更新值
927
- // 清空快捷表格已选数据
928
- emitModelValue([])
929
-
930
- // 隐藏弹出层节点
931
- hidePopupRef()
932
- }
933
-
934
- /**
935
- * 显示弹出层节点
936
- */
937
- function showPopupRef() {
938
-
939
- // 如果有弹出层节点
940
- if (popupRef.value) {
941
- // 显示弹出层
942
- popupRef.value.show()
943
- }
944
- }
945
-
946
- /**
947
- * 隐藏弹出层节点
948
- */
949
- function hidePopupRef() {
950
-
951
- // 如果有弹出层节点
952
- if (popupRef.value) {
953
- // 隐藏弹出层
954
- popupRef.value.hide()
955
- }
956
- }
957
-
958
- /**
959
- * 弹出层显示回调
960
- */
961
- function onPopupShow() {
962
-
963
- // 显示弹出层
964
- showPopup.value = true
965
-
966
- // 设置输入框焦点
967
- setInputFocus()
968
-
969
- // 表格加载(只加载一次)
970
- $table.tableLoad()
971
- .finally()
972
- }
973
-
974
- /**
975
- * 显示对话框
976
- */
977
- function onShowDialog() {
978
- // 显示对话框
979
- showDialog.value = true
980
- }
981
-
982
- /**
983
- * 对话框显示前回调
984
- */
985
- function onDialogBeforeShow() {
986
-
987
- // 设置当前已选数据
988
- $table.tableSelected.value = [...selected.value]
989
-
990
- // 隐藏弹出层节点
991
- hidePopupRef()
992
- }
993
-
994
- /**
995
- * 对话框显示回调
996
- */
997
- let _dialogShowed = false
998
- function onDialogShow() {
999
-
1000
- if ($n_isFunction(props.request)) {
1001
-
1002
- if (_dialogShowed) {
1003
- return
1004
- }
1005
- _dialogShowed = true
1006
-
1007
- // 表格重新加载
1008
- $table.tableReload()
1009
- .finally()
1010
-
1011
- return
1012
- }
1013
-
1014
- // 表格加载(只加载一次)
1015
- $table.tableLoad()
1016
- .finally()
1017
- }
1018
-
1019
- /**
1020
- * 对话框隐藏后回调
1021
- */
1022
- function onDialogHide() {
1023
-
1024
- let isReload = true
1025
-
1026
- // 清空输入框值
1027
- if (
1028
- // 如果开启筛选
1029
- props.filter
1030
- // 如果有输入框值
1031
- && inputValue.value
1032
- ) {
1033
- // 此时清空输入框后, 会自动刷新表格
1034
- inputValue.value = ''
1035
-
1036
- // 所以只需要重置搜索值即可, 不需要再重置后刷新表格
1037
- isReload = false
1038
- }
1039
-
1040
- // 如果有表格搜索值
1041
- if ($table.hasTableSearchValue()) {
1042
- // 表格搜索重置
1043
- $table.tableSearchReset(isReload)
1044
- }
1045
- }
1046
-
1047
- /**
1048
- * 对话框点击确认回调
1049
- */
1050
- function onDialogConfirm(data) {
1051
-
1052
- // 触发更新值
1053
- emitModelValue([...data])
1054
- }
1055
-
1056
- /**
1057
- * 单击快捷表格行
1058
- */
1059
- function quickTableRowClick(e, row) {
1060
-
1061
- // 如果为多选
1062
- if (props.multiple) {
1063
-
1064
- // 克隆已选数据
1065
- const _selected = [...selected.value]
1066
-
1067
- const opt = {}
1068
- opt[props.valueKey] = row[props.valueKey]
1069
-
1070
- // 获取当前数据索引
1071
- const itemIndex = $n_findIndex(_selected, opt)
1072
-
1073
- // 如果不存在
1074
- if (itemIndex === -1) {
1075
- // 则添加
1076
- _selected.push(row)
1077
-
1078
- // 否则
1079
- } else {
1080
- // 删除
1081
- _selected.splice(itemIndex, 1)
1082
- }
1083
-
1084
- // 触发更新值
1085
- emitModelValue(_selected)
1086
-
1087
- // 否则为单选
1088
- } else {
1089
-
1090
- // 触发更新值
1091
- emitModelValue([ row ])
1092
-
1093
- // 隐藏弹出层节点
1094
- hidePopupRef()
1095
- }
1096
- }
1097
-
1098
- /**
1099
- * 设置输入框文字选中
1100
- */
1101
- function setInputSelection() {
1102
- if (
1103
- // 如果开启筛选
1104
- props.filter
1105
- // 如果有输入框节点
1106
- && inputRef.value
1107
- // 如果输入框有值
1108
- && inputValue.value.length
1109
- ) {
1110
- // 全选文字
1111
- inputRef.value.select()
1112
- // inputRef.value.setSelectionRange(0, inputValue.value.length)
1113
- }
1114
- }
1115
-
1116
- /**
1117
- * 设置输入框焦点
1118
- */
1119
- function setInputFocus() {
1120
- if (
1121
- // 如果开启筛选
1122
- props.filter
1123
- // 如果有输入框节点
1124
- && inputRef.value
1125
- ) {
1126
- inputRef.value.focus()
1127
- }
1128
- }
1129
-
1130
- // ==========【生命周期】=========================================================================================
1131
-
1132
- /**
1133
- * 在组件因为响应式状态变更而更新其 DOM 树之后调用
1134
- */
1135
- onUpdated(function () {
1136
- if (
1137
- popupRef.value
1138
- && $n_has(popupRef.value, 'currentComponent.ref.updatePosition')
1139
- ) {
1140
- popupRef.value.currentComponent.ref.updatePosition()
1141
- }
1142
- })
1143
-
1144
- // ==========【返回】=============================================================================================
1145
-
1146
- return {
1147
- // 解构表格实例
1148
- ...$table,
1149
-
1150
- // 插槽标识
1151
- slotNames,
1152
- // 当前标签字段
1153
- currentlabelKey,
1154
- // 显示值
1155
- showValue,
1156
-
1157
- // 输入框节点
1158
- inputRef,
1159
- // 输入框值
1160
- inputValue,
1161
- // 弹出层节点
1162
- popupRef,
1163
- // 是否显示对话框
1164
- showDialog,
1165
- // 是否显示弹出层
1166
- showPopup,
1167
- // 当前已选数据
1168
- selected,
1169
- // 当前表格列数据
1170
- columns,
1171
-
1172
- // 当前格式化显示标签
1173
- currentFormatLabel,
1174
- // 移除已选数据
1175
- onRemoveSelected,
1176
-
1177
- // 字段获取焦点触发
1178
- onFieldFocus,
1179
- // 字段失去焦点触发
1180
- onFieldBlur,
1181
- // 字段清空触发
1182
- onFieldClear,
1183
-
1184
- // 弹出层显示回调
1185
- onPopupShow,
1186
-
1187
- // 显示对话框
1188
- onShowDialog,
1189
- // 对话框显示前回调
1190
- onDialogBeforeShow,
1191
- // 对话框显示回调
1192
- onDialogShow,
1193
- // 对话框隐藏后回调
1194
- onDialogHide,
1195
- // 对话框点击确认回调
1196
- onDialogConfirm,
1197
-
1198
- // 单击快捷表格行
1199
- quickTableRowClick,
1200
-
1201
- // 触发更新值
1202
- emitModelValue,
1203
- }
1204
- },
1205
- }
1206
- </script>
1207
-
1208
- <style lang="scss">
1209
- .n-field-table {
1210
- .q-field__input--padding {
1211
- padding-left: 4px;
1212
- min-width: 50px !important;
1213
- cursor: text;
1214
- }
1215
- }
1216
-
1217
- /**
1218
- * 桌面
1219
- */
1220
- body.desktop {
1221
- .n-field-table {
1222
- &__popup-table {
1223
- height: 300px;
1224
- }
1225
- }
1226
- }
1227
- </style>
1
+ <template>
2
+
3
+ <!-- 如果有默认插槽 -->
4
+ <template v-if="$slots.default">
5
+ <slot
6
+ :showValue="showValue"
7
+ :selected="selected"
8
+ :onRemove="onRemoveSelected"
9
+ :onShowDialog="onShowDialog"
10
+ :onClear="onFieldClear"
11
+ />
12
+ </template>
13
+
14
+ <!--:class="fieldFocused ? 'q-field&#45;&#45;float q-field&#45;&#45;focused q-field&#45;&#45;highlighted' : ''"-->
15
+ <!--:clearable="clearable && (! multiple || collapseTags)"-->
16
+ <q-field
17
+ class="n-field-table"
18
+ :model-value="showValue"
19
+ :disable="disable"
20
+ :readonly="readonly"
21
+ :clearable="clearable"
22
+ @focus="onFieldFocus"
23
+ @blur="onFieldBlur"
24
+ @clear="onFieldClear"
25
+ v-bind="$attrs"
26
+ v-else
27
+ >
28
+ <template v-slot:control>
29
+
30
+ <template v-if="multiple">
31
+ <template v-if="selected.length">
32
+
33
+ <!-- 多选插槽 -->
34
+ <slot
35
+ name="selected"
36
+ :selected="selected"
37
+ :remove="onRemoveSelected"
38
+ v-if="$slots.selected"
39
+ />
40
+
41
+ <!-- 显示折叠的值数量 -->
42
+ <q-chip
43
+ dense
44
+ :label="`+${selected.length}`"
45
+ v-else-if="collapseTags"
46
+ />
47
+
48
+ <!-- 多选标签 -->
49
+ <template v-else>
50
+ <q-chip
51
+ v-for="(item, index) in selected"
52
+ :key="`options-${index}`"
53
+ :label="currentFormatLabel(item)"
54
+ dense
55
+ :removable="! readonly && ! disable"
56
+ @remove="onRemoveSelected(index)"
57
+ />
58
+ </template>
59
+ </template>
60
+
61
+ <!-- 占位符-->
62
+ <span class="n-placeholder" v-else-if="placeholder">{{placeholder}}</span>
63
+ </template>
64
+
65
+ <!-- 显示文字 -->
66
+ <span v-else-if="showValue">{{showValue}}</span>
67
+
68
+ <!-- 占位符-->
69
+ <span class="n-placeholder" v-else-if="placeholder">{{placeholder}}</span>
70
+
71
+ <!-- 筛选输入框 -->
72
+ <input
73
+ ref="inputRef"
74
+ class="q-field__input q-placeholder col q-field__input--padding"
75
+ v-model="inputValue"
76
+ v-if="filter && ! readonly && ! disable"
77
+ />
78
+
79
+ </template>
80
+
81
+ <!-- 弹出对话框图标 -->
82
+ <template v-slot:append v-if="! noDialog && ! readonly && ! disable">
83
+ <q-icon
84
+ class="cursor-pointer"
85
+ name="search"
86
+ @click.prevent.stop="onShowDialog"
87
+ />
88
+ </template>
89
+
90
+ <!-- 弹出层代理 -->
91
+ <q-popup-proxy
92
+ ref="popupRef"
93
+ no-refocus
94
+ no-focus
95
+ fit
96
+ @focus="onFieldBlur"
97
+ @show="onPopupShow"
98
+ @before-hide="showPopup = false"
99
+ v-if="! readonly && ! disable"
100
+ >
101
+ <!-- 快捷表格 -->
102
+ <n-table
103
+ class="n-table n-field-table__popup-table"
104
+ v-model:pagination="tablePagination"
105
+ :selected="selected"
106
+ @update:selected="emitModelValue"
107
+ :row-key="tableRowKey"
108
+ :rows="tableRows"
109
+ :columns="columns"
110
+ :selection="multiple ? 'multiple' : 'none'"
111
+ :loading="tableLoading"
112
+ :rows-per-page-options="tableRowsPerPageOptions"
113
+ @row-click="quickTableRowClick"
114
+ @request="tableRequest"
115
+ flat
116
+ virtual-scroll
117
+ dense
118
+ v-bind="tableProps"
119
+ >
120
+ <!-- 图片 -->
121
+ <template
122
+ v-for="imgName in tableImgNames"
123
+ v-slot:[`body-cell-${imgName}`]="props"
124
+ >
125
+ <q-td :props="props">
126
+ <!-- 缩略图 -->
127
+ <n-thumbnail
128
+ :src="props.row[imgName]"
129
+ preview
130
+ />
131
+ </q-td>
132
+ </template>
133
+
134
+ <!-- 插槽 -->
135
+ <template
136
+ v-for="slotName in slotNames"
137
+ v-slot:[slotName]="props"
138
+ >
139
+ <q-td :props="props">
140
+ <slot
141
+ :name="slotName"
142
+ v-bind="props"
143
+ />
144
+ </q-td>
145
+ </template>
146
+
147
+ <!-- 翻页 -->
148
+ <template v-slot:pagination="props">
149
+ <n-table-pagination
150
+ :props="props"
151
+ no-power
152
+ dense
153
+ />
154
+ </template>
155
+ </n-table>
156
+ </q-popup-proxy>
157
+ </q-field>
158
+
159
+ <!-- 弹出对话框 -->
160
+ <n-dialog
161
+ v-model="showDialog"
162
+ width="80%"
163
+ :on-confirm="onDialogConfirm"
164
+ @before-show="onDialogBeforeShow"
165
+ @show="onDialogShow"
166
+ @hide="onDialogHide"
167
+ cancel
168
+ v-bind="dialogProps"
169
+ >
170
+ <q-page>
171
+ <n-mixed-table />
172
+ </q-page>
173
+ </n-dialog>
174
+ </template>
175
+
176
+ <script>
177
+ import { ref, computed, watch, onUpdated } from 'vue'
178
+
179
+ import $n_has from 'lodash/has'
180
+ import $n_uniq from 'lodash/uniq'
181
+ import $n_cloneDeep from 'lodash/cloneDeep'
182
+ import $n_isFunction from 'lodash/isFunction'
183
+ import $n_findIndex from 'lodash/findIndex'
184
+ import $n_get from 'lodash/get'
185
+
186
+ import $n_forEach from '@netang/utils/forEach'
187
+ import $n_isValidArray from '@netang/utils/isValidArray'
188
+ import $n_join from '@netang/utils/join'
189
+ import $n_split from '@netang/utils/split'
190
+ import $n_isValidObject from '@netang/utils/isValidObject'
191
+ import $n_isValidValue from '@netang/utils/isValidValue'
192
+ import $n_isValidString from '@netang/utils/isValidString'
193
+ import $n_numberDeep from '@netang/utils/numberDeep'
194
+ import $n_sleep from '@netang/utils/sleep'
195
+ import $n_http from '@netang/utils/http'
196
+ import $n_runAsync from '@netang/utils/runAsync'
197
+
198
+ import $n_$power from '../../utils/$power'
199
+ import $n_$table from '../../utils/$table'
200
+
201
+ import { configs } from '../../utils/config'
202
+
203
+ const {
204
+ // 字典常量
205
+ dicts,
206
+ } = configs
207
+
208
+ export default {
209
+
210
+ /**
211
+ * 标识
212
+ */
213
+ name: 'NFieldTable',
214
+
215
+ /**
216
+ * 关闭组件 attribute 透传行为
217
+ */
218
+ inheritAttrs: false,
219
+
220
+ /**
221
+ * 声明属性
222
+ */
223
+ props: {
224
+ // 值 v-model
225
+ modelValue: {
226
+ required: true,
227
+ },
228
+ // 值字段(必填)
229
+ valueKey: {
230
+ type: String,
231
+ required: true,
232
+ },
233
+ // 标签字段
234
+ labelKey: String,
235
+ // 值类型
236
+ // string: 字符串或数字
237
+ // stringArray: 普通数组(包含字符串或数字的一维数组)
238
+ // objectArray: 对象数组(包含对象的一维数组)
239
+ valueType: {
240
+ type: String,
241
+ default: 'objectArray'
242
+ },
243
+ // 值分隔符(值类型为 string 有效)
244
+ valueSeparator: {
245
+ type: String,
246
+ default: ',',
247
+ },
248
+
249
+ // 请求路由路径
250
+ path: String,
251
+ // 请求地址(默认为 path)
252
+ url: String,
253
+ // 请求参数
254
+ query: Object,
255
+ // 附加请求数据
256
+ data: Object,
257
+ // 初始不加载已选数据
258
+ noDefaultLoadSelected: Boolean,
259
+ // 更新值时不加载已选数据
260
+ noUpdateLoadSelected: Boolean,
261
+ // 格式化显示标签
262
+ formatLabel: Function,
263
+ // 下拉表格显示的字段数组(空为:[值字段, 标签字段])
264
+ showKeys: Array,
265
+ // 隐藏搜索字段数组
266
+ hideSearchKeys: Array,
267
+ // 默认筛选字段(空为:标签字段)
268
+ filterKey: String,
269
+ // 是否开启筛选
270
+ filter: Boolean,
271
+ // 表格声明属性
272
+ tableProps: Object,
273
+ // 对话框声明属性
274
+ dialogProps: Object,
275
+
276
+ // 关闭对话框
277
+ noDialog: Boolean,
278
+
279
+ // 表格列数据
280
+ columns: Array,
281
+ // 行数据
282
+ rows: Array,
283
+ // 是否多选
284
+ multiple: Boolean,
285
+ // 多选模式下是否折叠标签
286
+ collapseTags: Boolean,
287
+ // 占位符
288
+ placeholder: String,
289
+ // 是否可清除
290
+ clearable: Boolean,
291
+ // 是否禁用
292
+ disable: Boolean,
293
+ // 是否只读
294
+ readonly: Boolean,
295
+ // 输入防抖(毫秒)
296
+ inputDebounce: {
297
+ type: [ Number, String ],
298
+ default: 500
299
+ },
300
+ // 自定义请求方法
301
+ request: Function,
302
+ },
303
+
304
+ /**
305
+ * 声明事件
306
+ */
307
+ emits: [
308
+ 'update:modelValue',
309
+ 'update:selected',
310
+ ],
311
+
312
+ /**
313
+ * 组合式
314
+ */
315
+ setup(props, { emit, slots }) {
316
+
317
+ // ==========【计算属性】=========================================================================================
318
+
319
+ /**
320
+ * 插槽标识
321
+ */
322
+ const slotNames = computed(function() {
323
+ return $n_isValidObject(slots) ? Object.keys(slots) : []
324
+ })
325
+
326
+ /**
327
+ * 当前标签字段
328
+ */
329
+ const currentlabelKey = computed(function() {
330
+ return props.labelKey || props.valueKey
331
+ })
332
+
333
+ /**
334
+ * 当前显示字段
335
+ */
336
+ const currentShowKeys = computed(function() {
337
+ return $n_uniq($n_isValidArray(props.showKeys)
338
+ ? props.showKeys
339
+ : [ props.valueKey, currentlabelKey.value ])
340
+ })
341
+
342
+ /**
343
+ * 当前搜索字段
344
+ */
345
+ const currentFilterKey = computed(function() {
346
+ return props.filterKey || currentlabelKey.value
347
+ })
348
+
349
+ /**
350
+ * 显示值
351
+ */
352
+ const showValue = computed(function () {
353
+
354
+ // 如果有已选数据
355
+ return $n_isValidArray(selected.value)
356
+ // 取已选数据第一条
357
+ ? currentFormatLabel(selected.value[0])
358
+ : ''
359
+ })
360
+
361
+ // ==========【数据】============================================================================================
362
+
363
+ // 创建权限实例
364
+ const $power = $n_$power.create({
365
+ // 路由路径
366
+ path: $n_isValidString(props.path) ? props.path : false,
367
+ // 路由参数
368
+ query: props.query,
369
+ // 关闭权限页面
370
+ power: false,
371
+ // 禁止对话框注入
372
+ $dialog: null,
373
+ })
374
+
375
+ const {
376
+ // 当前路由路径
377
+ routePath,
378
+ } = $power
379
+
380
+ // 创建表格实例
381
+ const $table = $n_$table.create({
382
+ // 权限实例
383
+ $power,
384
+ // 请求地址
385
+ url: props.url,
386
+ // 附加请求数据
387
+ data: props.data,
388
+ // 获取表格列数据
389
+ columns: getTableColumns(),
390
+ // 表格行唯一键值
391
+ rowKey: props.valueKey,
392
+ // 行数据
393
+ rows: props.rows,
394
+ // 选择类型, 可选值 single multiple none
395
+ selection: props.multiple ? 'multiple' : 'single',
396
+ // 已选数据
397
+ selected: [],
398
+ // http 设置
399
+ httpSettings: {
400
+ // 头部请求
401
+ headers: {
402
+ // 添加头部查看请求
403
+ Pview: 1,
404
+ },
405
+ },
406
+ // 刷新后清空已选数据
407
+ refreshResetSelected: false,
408
+ // 自定义请求方法
409
+ async request({ httpOptions }) {
410
+ return $n_isFunction(props.request) ?
411
+ // 如果有自定义请求方法
412
+ await $n_runAsync(props.request)({
413
+ // http 请求参数
414
+ httpOptions,
415
+ // 对话框是否已显示
416
+ showDialog: showDialog.value,
417
+ }) :
418
+ // 否则请求数据
419
+ await $n_http(httpOptions)
420
+ },
421
+ })
422
+
423
+ // 创建睡眠实例
424
+ const sleep = $n_sleep()
425
+
426
+ // 输入框节点
427
+ const inputRef = ref(null)
428
+
429
+ // 输入框值
430
+ const inputValue = ref('')
431
+
432
+ // 弹出层节点
433
+ const popupRef = ref(null)
434
+
435
+ // 是否显示对话框
436
+ const showDialog = ref(false)
437
+
438
+ // 是否显示弹出层
439
+ const showPopup = ref(false)
440
+
441
+ // 当前表格列数据
442
+ const columns = getQuickTableColumns()
443
+
444
+ // 停止观察值
445
+ let stopValueWatcher = false
446
+
447
+ // 临时已选数据
448
+ let tempSelected = []
449
+
450
+ // 初始化已选数据
451
+ const selected = ref(valueToSelected(props.modelValue, true, true))
452
+
453
+ // 加载已选数据
454
+ loadSelected()
455
+ .finally()
456
+
457
+ // ==========【监听数据】=========================================================================================
458
+
459
+ /**
460
+ * 监听声明值
461
+ */
462
+ watch(() => props.modelValue, async function(val) {
463
+
464
+ // 如果停止观察值
465
+ if (stopValueWatcher === true) {
466
+ // 取消停止观察值
467
+ stopValueWatcher = false
468
+ return
469
+ }
470
+
471
+ // 值转已选数据
472
+ let newSelected = valueToSelected(val, false, false)
473
+
474
+ // 如果值类型是数组对象
475
+ if (props.valueType === 'objectArray') {
476
+
477
+ // 设置已选数据
478
+ setSelected(newSelected)
479
+
480
+ // 否则值类型是字符串或数组
481
+ } else {
482
+
483
+ // 初始已选数据
484
+ let _selected = []
485
+
486
+ // 如果值转已选数据是有效数组
487
+ if (newSelected.length) {
488
+
489
+ // 当前已选数据
490
+ const currentSelected = tempSelected.length ? tempSelected : selected.value
491
+
492
+ // 如果有已选数据
493
+ if (currentSelected.length) {
494
+
495
+ // 新已选数据
496
+ _selected = currentSelected.filter(e => newSelected.indexOf(e[props.valueKey]) > -1)
497
+
498
+ // 需增加的值
499
+ newSelected = newSelected.filter(e => _selected.map(e => e[props.valueKey]).indexOf(e) === -1)
500
+ }
501
+
502
+ // 需增加的值
503
+ if (newSelected.length) {
504
+
505
+ // 如果更新值时不加载已选数据
506
+ if (props.noUpdateLoadSelected) {
507
+ // 请求选择数据
508
+ _selected.push(...newSelected.map(e => setSelectedItem(e)))
509
+ } else {
510
+ // 请求选择数据
511
+ _selected.push(...await onRequestSelected(newSelected))
512
+ }
513
+ }
514
+ }
515
+
516
+ // 设置已选数据
517
+ setSelected(_selected)
518
+
519
+ // 清空临时已选数据
520
+ tempSelected = []
521
+ }
522
+
523
+ // 将已选数据转为值
524
+ const _value = selectedToValue(selected.value)
525
+
526
+ // 如果声明值发生变化
527
+ if (_value !== props.modelValue) {
528
+ // 停止观察值
529
+ stopValueWatcher = true
530
+ // 触发更新已选数据
531
+ emit('update:modelValue', _value)
532
+ }
533
+
534
+ // 设置输入框焦点
535
+ setInputFocus()
536
+
537
+ // 设置输入框文字选中
538
+ setInputSelection()
539
+
540
+ }, {
541
+ // 深度监听
542
+ deep: true,
543
+ })
544
+
545
+ /**
546
+ * 监听输入框值
547
+ */
548
+ watch(inputValue, async function (val) {
549
+
550
+ // 延迟执行
551
+ await sleep(props.inputDebounce)
552
+
553
+ // 是否有值
554
+ const hasValue = $n_isValidValue(val)
555
+
556
+ const n_search = {}
557
+ n_search[currentFilterKey.value] = [
558
+ {
559
+ // 比较类型
560
+ compare: dicts.SEARCH_COMPARE_TYPE__LIKE,
561
+ // 值
562
+ value: hasValue ? val : '',
563
+ }
564
+ ]
565
+
566
+ // 设置表格传参
567
+ $table.setQuery({
568
+ n_search,
569
+ })
570
+
571
+ if (
572
+ // 如果弹出层是隐藏的
573
+ ! showPopup.value
574
+ // 如果输入框有值
575
+ && hasValue
576
+ ) {
577
+ // 显示弹出层节点
578
+ showPopupRef()
579
+ }
580
+
581
+ // 表格重新加载
582
+ await $table.tableReload()
583
+ })
584
+
585
+ // ==========【方法】=============================================================================================
586
+
587
+ /**
588
+ * 加载已选数据
589
+ */
590
+ async function loadSelected() {
591
+ if (
592
+ // 如果值类型不是数组对象
593
+ props.valueType !== 'objectArray'
594
+ // 如果初始加载已选数据
595
+ && ! props.noDefaultLoadSelected
596
+ // 如果有请求路由路径
597
+ && routePath
598
+ ) {
599
+ // 获取值数组
600
+ const values = valueToSelected(props.modelValue, false, false)
601
+ if (values.length) {
602
+ // 初始的已选数据
603
+ const _selected = await onRequestSelected(values)
604
+ const _value = selectedToValue(_selected)
605
+
606
+ // 如果声明值未发生变化
607
+ if (_value === props.modelValue) {
608
+ // 设置已选数据
609
+ setSelected(_selected)
610
+
611
+ } else {
612
+ // 设置临时已选数据
613
+ tempSelected = _selected
614
+ // 触发更新值
615
+ emit('update:modelValue', _value)
616
+ }
617
+ return
618
+ }
619
+ }
620
+
621
+ // 触发更新已选数据
622
+ emit('update:selected', selected.value)
623
+ }
624
+
625
+ /**
626
+ * 触发更新值
627
+ */
628
+ function emitModelValue(val) {
629
+
630
+ // 设置临时已选数据
631
+ tempSelected = val
632
+
633
+ // 触发更新值
634
+ emit('update:modelValue', selectedToValue(val))
635
+ }
636
+
637
+ /**
638
+ * 设置已选数据
639
+ */
640
+ function setSelected(val) {
641
+
642
+ // 设置已选数据
643
+ selected.value = val
644
+
645
+ // 触发更新已选数据
646
+ emit('update:selected', val)
647
+ }
648
+
649
+ /**
650
+ * 当前格式化显示标签
651
+ */
652
+ function currentFormatLabel(item) {
653
+
654
+ // 如果有格式化显示标签方法
655
+ if ($n_isFunction(props.formatLabel)) {
656
+ // 执行格式化显示标签方法
657
+ return props.formatLabel(item)
658
+ }
659
+
660
+ // 否则显示该值的标签字段
661
+ const val = item[currentlabelKey.value]
662
+ return $n_isValidValue(val) ? val : item[props.valueKey]
663
+ }
664
+
665
+ /**
666
+ * 设置已选数据的单个元素
667
+ */
668
+ function setSelectedItem(val) {
669
+ const obj = {}
670
+ obj[props.valueKey] = val
671
+ obj[currentlabelKey.value] = val
672
+ return obj
673
+ }
674
+
675
+ /**
676
+ * 值转已选数据
677
+ */
678
+ function valueToSelected(val, isFirst, toSelected) {
679
+
680
+ // 如果值类型是数组对象
681
+ if (props.valueType === 'objectArray') {
682
+
683
+ // 如果是有效数组
684
+ if ($n_isValidArray(val)) {
685
+ for (const item of val) {
686
+ if (
687
+ // 如果元素不是有效对象
688
+ ! $n_isValidObject(item)
689
+ // 如果元素没有值字段
690
+ || ! $n_has(item, props.valueKey)
691
+ ) {
692
+ return []
693
+ }
694
+ }
695
+ }
696
+
697
+ // 否则直接返回
698
+ return val
699
+ }
700
+
701
+ if (
702
+ // 非初始化
703
+ ! isFirst
704
+ // 或初始不加载已选数据
705
+ || props.noDefaultLoadSelected
706
+ // 或没有路由路径
707
+ || ! routePath
708
+ ) {
709
+ // 将值转为数组
710
+ val = props.valueType === 'string' ? $n_split(val, props.valueSeparator) : val
711
+
712
+ // 如果是有效数组
713
+ if ($n_isValidArray(val)) {
714
+ val = val.filter(e => $n_isValidValue(e))
715
+ return toSelected ? val.map(e => setSelectedItem(e)) : val
716
+ }
717
+ }
718
+
719
+ return []
720
+ }
721
+
722
+ /**
723
+ * 已选数据转值
724
+ */
725
+ function selectedToValue(val) {
726
+
727
+ // 如果值类型是数组对象
728
+ if (props.valueType === 'objectArray') {
729
+
730
+ // 则直接返回
731
+ return val
732
+ }
733
+
734
+ // 值数组
735
+ const values = val.length
736
+ // 如果有已选数据
737
+ ? (
738
+ props.multiple
739
+ // 如果是多选
740
+ ? val.map(e => e[props.valueKey])
741
+ // 否则是单选
742
+ : [ val[0][props.valueKey] ]
743
+ )
744
+ // 否则为空
745
+ : []
746
+
747
+ // 如果值类型是数组
748
+ if (props.valueType === 'stringArray') {
749
+
750
+ // 直接返回数组
751
+ return values
752
+ }
753
+
754
+ // 返回转为分隔符隔开的字符串
755
+ return $n_numberDeep($n_join(values, props.valueSeparator))
756
+ }
757
+
758
+ /**
759
+ * 请求选择数据
760
+ */
761
+ async function onRequestSelected(value) {
762
+
763
+ // 请求参数
764
+ const httpOptions = {
765
+ url: $table.routePath,
766
+ data: Object.assign(
767
+ // 获取表格请求数据
768
+ $table.getTableRequestData({
769
+ // filter,
770
+ pagination: {
771
+ // 页码
772
+ page: 1,
773
+ // 每页的数据条数
774
+ rowsPerPage: value.length,
775
+ // 排序字段
776
+ sortBy: null,
777
+ // 是否降序排列
778
+ descending: true,
779
+ }
780
+ }, false),
781
+ {
782
+ // 查看字段
783
+ n_view: {
784
+ // 查看字段
785
+ field: props.valueKey,
786
+ // 查看值
787
+ value,
788
+ },
789
+ }
790
+ ),
791
+ // 是否开启防抖(防止重复请求)
792
+ debounce: false,
793
+ }
794
+
795
+ // 请求数据
796
+ const { status, data } = $n_isFunction(props.request) ?
797
+ // 如果有自定义请求方法
798
+ await $n_runAsync(props.request)({
799
+ // http 请求参数
800
+ httpOptions,
801
+ // 对话框是否已显示
802
+ showDialog: showDialog.value,
803
+ }) :
804
+ // 否则请求数据
805
+ await $n_http(httpOptions)
806
+
807
+ return status && $n_isValidArray($n_get(data, 'rows')) ? data.rows : []
808
+ }
809
+
810
+ /**
811
+ * 获取表格列数据
812
+ */
813
+ function getTableColumns() {
814
+
815
+ let columns
816
+
817
+ // 如果有声明路由表格列数据
818
+ if ($n_isValidArray(props.columns)) {
819
+ columns = $n_cloneDeep(props.columns)
820
+
821
+ // 如果有路由路径
822
+ } else if (routePath) {
823
+ // 否则如果有路由表格列数据
824
+ const rawTableColumns = $n_$table.config(routePath, 'columns')
825
+ if ($n_isValidArray(rawTableColumns)) {
826
+ columns = $n_cloneDeep(rawTableColumns)
827
+ }
828
+ }
829
+
830
+ if ($n_isValidArray(columns)) {
831
+ if ($n_isValidArray(props.hideSearchKeys)) {
832
+ for (const item of columns) {
833
+ if (
834
+ props.hideSearchKeys.indexOf(item.name) > -1
835
+ && $n_has(item, 'search')
836
+ ) {
837
+ item.search.hide = true
838
+ }
839
+ }
840
+ }
841
+ return columns
842
+ }
843
+
844
+ return []
845
+ }
846
+
847
+ /**
848
+ * 获取快捷表格列数据
849
+ */
850
+ function getQuickTableColumns() {
851
+
852
+ const columns = []
853
+
854
+ // 如果有原始表格列数据
855
+ if ($n_isValidArray($table.tableColumns.value)) {
856
+
857
+ // 克隆原始表格列数据
858
+ const rawTableColumns = $n_cloneDeep($table.tableColumns.value)
859
+
860
+ // 快捷表格显示的属性名称数组
861
+ $n_forEach(currentShowKeys.value, function (key) {
862
+ for (const item of rawTableColumns) {
863
+ if (item.name === key) {
864
+ // 删除搜索字段
865
+ if ($n_has(item, 'search')) {
866
+ delete item.search
867
+ }
868
+ // 删除可见字段
869
+ if ($n_has(item, 'visible')) {
870
+ delete item.visible
871
+ }
872
+ columns.push(item)
873
+ }
874
+ }
875
+ })
876
+ }
877
+
878
+ return columns
879
+ }
880
+
881
+ /**
882
+ * 移除已选数据
883
+ */
884
+ function onRemoveSelected(index) {
885
+ selected.value.splice(index, 1)
886
+ }
887
+
888
+ /**
889
+ * 字段获取焦点触发
890
+ */
891
+ function onFieldFocus(e) {
892
+
893
+ // 停止冒泡
894
+ e.stopPropagation()
895
+
896
+ // 设置输入框焦点
897
+ setInputFocus()
898
+
899
+ // window.scrollTo(window.pageXOffset || window.scrollX || document.body.scrollLeft || 0, 0)
900
+ }
901
+
902
+ /**
903
+ * 字段失去焦点触发
904
+ */
905
+ function onFieldBlur(e) {
906
+
907
+ // 停止冒泡
908
+ e.stopPropagation()
909
+
910
+ if (
911
+ // 如果开启筛选
912
+ props.filter
913
+ // 如果没有显示弹出层
914
+ && ! showPopup.value
915
+ ) {
916
+ // 清空输入框值
917
+ inputValue.value = ''
918
+ }
919
+ }
920
+
921
+ /**
922
+ * 字段清空触发
923
+ */
924
+ function onFieldClear() {
925
+
926
+ // 触发更新值
927
+ // 清空快捷表格已选数据
928
+ emitModelValue([])
929
+
930
+ // 隐藏弹出层节点
931
+ hidePopupRef()
932
+ }
933
+
934
+ /**
935
+ * 显示弹出层节点
936
+ */
937
+ function showPopupRef() {
938
+
939
+ // 如果有弹出层节点
940
+ if (popupRef.value) {
941
+ // 显示弹出层
942
+ popupRef.value.show()
943
+ }
944
+ }
945
+
946
+ /**
947
+ * 隐藏弹出层节点
948
+ */
949
+ function hidePopupRef() {
950
+
951
+ // 如果有弹出层节点
952
+ if (popupRef.value) {
953
+ // 隐藏弹出层
954
+ popupRef.value.hide()
955
+ }
956
+ }
957
+
958
+ /**
959
+ * 弹出层显示回调
960
+ */
961
+ function onPopupShow() {
962
+
963
+ // 显示弹出层
964
+ showPopup.value = true
965
+
966
+ // 设置输入框焦点
967
+ setInputFocus()
968
+
969
+ // 表格加载(只加载一次)
970
+ $table.tableLoad()
971
+ .finally()
972
+ }
973
+
974
+ /**
975
+ * 显示对话框
976
+ */
977
+ function onShowDialog() {
978
+ // 显示对话框
979
+ showDialog.value = true
980
+ }
981
+
982
+ /**
983
+ * 对话框显示前回调
984
+ */
985
+ function onDialogBeforeShow() {
986
+
987
+ // 设置当前已选数据
988
+ $table.tableSelected.value = [...selected.value]
989
+
990
+ // 隐藏弹出层节点
991
+ hidePopupRef()
992
+ }
993
+
994
+ /**
995
+ * 对话框显示回调
996
+ */
997
+ let _dialogShowed = false
998
+ function onDialogShow() {
999
+
1000
+ if ($n_isFunction(props.request)) {
1001
+
1002
+ if (_dialogShowed) {
1003
+ return
1004
+ }
1005
+ _dialogShowed = true
1006
+
1007
+ // 表格重新加载
1008
+ $table.tableReload()
1009
+ .finally()
1010
+
1011
+ return
1012
+ }
1013
+
1014
+ // 表格加载(只加载一次)
1015
+ $table.tableLoad()
1016
+ .finally()
1017
+ }
1018
+
1019
+ /**
1020
+ * 对话框隐藏后回调
1021
+ */
1022
+ function onDialogHide() {
1023
+
1024
+ let isReload = true
1025
+
1026
+ // 清空输入框值
1027
+ if (
1028
+ // 如果开启筛选
1029
+ props.filter
1030
+ // 如果有输入框值
1031
+ && inputValue.value
1032
+ ) {
1033
+ // 此时清空输入框后, 会自动刷新表格
1034
+ inputValue.value = ''
1035
+
1036
+ // 所以只需要重置搜索值即可, 不需要再重置后刷新表格
1037
+ isReload = false
1038
+ }
1039
+
1040
+ // 如果有表格搜索值
1041
+ if ($table.hasTableSearchValue()) {
1042
+ // 表格搜索重置
1043
+ $table.tableSearchReset(isReload)
1044
+ }
1045
+ }
1046
+
1047
+ /**
1048
+ * 对话框点击确认回调
1049
+ */
1050
+ function onDialogConfirm(data) {
1051
+
1052
+ // 触发更新值
1053
+ emitModelValue([...data])
1054
+ }
1055
+
1056
+ /**
1057
+ * 单击快捷表格行
1058
+ */
1059
+ function quickTableRowClick(e, row) {
1060
+
1061
+ // 如果为多选
1062
+ if (props.multiple) {
1063
+
1064
+ // 克隆已选数据
1065
+ const _selected = [...selected.value]
1066
+
1067
+ const opt = {}
1068
+ opt[props.valueKey] = row[props.valueKey]
1069
+
1070
+ // 获取当前数据索引
1071
+ const itemIndex = $n_findIndex(_selected, opt)
1072
+
1073
+ // 如果不存在
1074
+ if (itemIndex === -1) {
1075
+ // 则添加
1076
+ _selected.push(row)
1077
+
1078
+ // 否则
1079
+ } else {
1080
+ // 删除
1081
+ _selected.splice(itemIndex, 1)
1082
+ }
1083
+
1084
+ // 触发更新值
1085
+ emitModelValue(_selected)
1086
+
1087
+ // 否则为单选
1088
+ } else {
1089
+
1090
+ // 触发更新值
1091
+ emitModelValue([ row ])
1092
+
1093
+ // 隐藏弹出层节点
1094
+ hidePopupRef()
1095
+ }
1096
+ }
1097
+
1098
+ /**
1099
+ * 设置输入框文字选中
1100
+ */
1101
+ function setInputSelection() {
1102
+ if (
1103
+ // 如果开启筛选
1104
+ props.filter
1105
+ // 如果有输入框节点
1106
+ && inputRef.value
1107
+ // 如果输入框有值
1108
+ && inputValue.value.length
1109
+ ) {
1110
+ // 全选文字
1111
+ inputRef.value.select()
1112
+ // inputRef.value.setSelectionRange(0, inputValue.value.length)
1113
+ }
1114
+ }
1115
+
1116
+ /**
1117
+ * 设置输入框焦点
1118
+ */
1119
+ function setInputFocus() {
1120
+ if (
1121
+ // 如果开启筛选
1122
+ props.filter
1123
+ // 如果有输入框节点
1124
+ && inputRef.value
1125
+ ) {
1126
+ inputRef.value.focus()
1127
+ }
1128
+ }
1129
+
1130
+ // ==========【生命周期】=========================================================================================
1131
+
1132
+ /**
1133
+ * 在组件因为响应式状态变更而更新其 DOM 树之后调用
1134
+ */
1135
+ onUpdated(function () {
1136
+ if (
1137
+ popupRef.value
1138
+ && $n_has(popupRef.value, 'currentComponent.ref.updatePosition')
1139
+ ) {
1140
+ popupRef.value.currentComponent.ref.updatePosition()
1141
+ }
1142
+ })
1143
+
1144
+ // ==========【返回】=============================================================================================
1145
+
1146
+ return {
1147
+ // 解构表格实例
1148
+ ...$table,
1149
+
1150
+ // 插槽标识
1151
+ slotNames,
1152
+ // 当前标签字段
1153
+ currentlabelKey,
1154
+ // 显示值
1155
+ showValue,
1156
+
1157
+ // 输入框节点
1158
+ inputRef,
1159
+ // 输入框值
1160
+ inputValue,
1161
+ // 弹出层节点
1162
+ popupRef,
1163
+ // 是否显示对话框
1164
+ showDialog,
1165
+ // 是否显示弹出层
1166
+ showPopup,
1167
+ // 当前已选数据
1168
+ selected,
1169
+ // 当前表格列数据
1170
+ columns,
1171
+
1172
+ // 当前格式化显示标签
1173
+ currentFormatLabel,
1174
+ // 移除已选数据
1175
+ onRemoveSelected,
1176
+
1177
+ // 字段获取焦点触发
1178
+ onFieldFocus,
1179
+ // 字段失去焦点触发
1180
+ onFieldBlur,
1181
+ // 字段清空触发
1182
+ onFieldClear,
1183
+
1184
+ // 弹出层显示回调
1185
+ onPopupShow,
1186
+
1187
+ // 显示对话框
1188
+ onShowDialog,
1189
+ // 对话框显示前回调
1190
+ onDialogBeforeShow,
1191
+ // 对话框显示回调
1192
+ onDialogShow,
1193
+ // 对话框隐藏后回调
1194
+ onDialogHide,
1195
+ // 对话框点击确认回调
1196
+ onDialogConfirm,
1197
+
1198
+ // 单击快捷表格行
1199
+ quickTableRowClick,
1200
+
1201
+ // 触发更新值
1202
+ emitModelValue,
1203
+ }
1204
+ },
1205
+ }
1206
+ </script>
1207
+
1208
+ <style lang="scss">
1209
+ .n-field-table {
1210
+ .q-field__input--padding {
1211
+ padding-left: 4px;
1212
+ min-width: 50px !important;
1213
+ cursor: text;
1214
+ }
1215
+ }
1216
+
1217
+ /**
1218
+ * 桌面
1219
+ */
1220
+ body.desktop {
1221
+ .n-field-table {
1222
+ &__popup-table {
1223
+ height: 300px;
1224
+ }
1225
+ }
1226
+ }
1227
+ </style>