@netang/quasar 0.1.67 → 0.1.68

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