@netang/quasar 0.2.4 → 0.2.6

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 (248) hide show
  1. package/.editorconfig +12 -12
  2. package/_docs/docs/components/field-table.md +58 -58
  3. package/_docs/docs/components/field-tree.md +21 -21
  4. package/_docs/docs/components/table.md +24 -24
  5. package/_docs/docs/utils/table.md +196 -196
  6. package/components/column-title/index.vue +38 -38
  7. package/components/dialog/img-viewer/index.vue +657 -657
  8. package/components/dialog/index.vue +373 -373
  9. package/components/drawer/index.vue +303 -303
  10. package/components/editor-code/index.vue +325 -325
  11. package/components/empty/index.vue +80 -80
  12. package/components/field-date/index.vue +850 -850
  13. package/components/field-table/index.vue +33 -11
  14. package/components/field-tree/index.vue +754 -754
  15. package/components/img/index.vue +239 -239
  16. package/components/input-number/index.vue +547 -547
  17. package/components/mixed-table/index.vue +532 -532
  18. package/components/mixed-table-splitter/index.vue +377 -377
  19. package/components/power-page/index.vue +94 -94
  20. package/components/private/components/move-to-tree/index.vue +154 -154
  21. package/components/private/edit-power-data/index.vue +846 -846
  22. package/components/private/table-visible-columns-button/index.vue +114 -114
  23. package/components/render/index.vue +123 -123
  24. package/components/search/index.vue +231 -231
  25. package/components/search-item/index.vue +210 -210
  26. package/components/select/index.vue +177 -177
  27. package/components/splitter/index.vue +422 -422
  28. package/components/table/index.vue +513 -513
  29. package/components/table-column-fixed/index.vue +110 -110
  30. package/components/table-summary/index.vue +107 -107
  31. package/components/toolbar/index.vue +146 -146
  32. package/components/tree/index.vue +1728 -1728
  33. package/components/uploader/index.vue +190 -190
  34. package/components/uploader-query/index.vue +928 -928
  35. package/docs/404.html +33 -33
  36. package/docs/assets/404.html-60b35caa.js +1 -1
  37. package/docs/assets/404.html-d1e63d77.js +1 -1
  38. package/docs/assets/alert.html-b2a2a72f.js +5 -5
  39. package/docs/assets/alert.html-ba46d137.js +1 -1
  40. package/docs/assets/app-9f30aa4b.js +6 -6
  41. package/docs/assets/area.html-01b9b58d.js +42 -42
  42. package/docs/assets/area.html-9a4fce6a.js +1 -1
  43. package/docs/assets/arr.html-145d27e7.js +1 -1
  44. package/docs/assets/arr.html-674e65ab.js +11 -11
  45. package/docs/assets/auth.html-579fa830.js +1 -1
  46. package/docs/assets/auth.html-8544ed95.js +8 -8
  47. package/docs/assets/bus.html-c71254aa.js +1 -1
  48. package/docs/assets/bus.html-dc7d3d19.js +6 -6
  49. package/docs/assets/column-title.html-c735cb5a.js +3 -3
  50. package/docs/assets/column-title.html-e9316762.js +1 -1
  51. package/docs/assets/confirm.html-ddfdc27f.js +10 -10
  52. package/docs/assets/confirm.html-ef3e2bef.js +1 -1
  53. package/docs/assets/copy.html-d20345b6.js +1 -1
  54. package/docs/assets/copy.html-ef8c8571.js +13 -13
  55. package/docs/assets/data.html-6432175d.js +30 -30
  56. package/docs/assets/data.html-a3b05d5b.js +1 -1
  57. package/docs/assets/dialog.html-1f698e5a.js +1 -1
  58. package/docs/assets/dialog.html-62902b83.js +68 -68
  59. package/docs/assets/dialog.html-baea77c9.js +1 -1
  60. package/docs/assets/dialog.html-bb082fc4.js +1 -1
  61. package/docs/assets/dict.html-1311da3d.js +23 -23
  62. package/docs/assets/dict.html-b96fbf0c.js +1 -1
  63. package/docs/assets/dictOptions.html-7c4f40a5.js +1 -1
  64. package/docs/assets/dictOptions.html-fb99d175.js +5 -5
  65. package/docs/assets/dragger.html-668d3efa.js +1 -1
  66. package/docs/assets/dragger.html-749d585a.js +1 -1
  67. package/docs/assets/editor-code.html-6ab26ea9.js +1 -1
  68. package/docs/assets/editor-code.html-d196205d.js +1 -1
  69. package/docs/assets/empty.html-1c139131.js +1 -1
  70. package/docs/assets/empty.html-1e9c441d.js +1 -1
  71. package/docs/assets/field-date.html-069fdb13.js +1 -1
  72. package/docs/assets/field-date.html-ad204aa9.js +1 -1
  73. package/docs/assets/field-table.html-ce480f03.js +1 -1
  74. package/docs/assets/field-table.html-d9236160.js +1 -1
  75. package/docs/assets/field-text.html-7277c62f.js +1 -1
  76. package/docs/assets/field-text.html-ccb4cecf.js +1 -1
  77. package/docs/assets/field-tree.html-519bfb45.js +1 -1
  78. package/docs/assets/field-tree.html-fdc748d6.js +1 -1
  79. package/docs/assets/form.html-2b562c37.js +2 -2
  80. package/docs/assets/form.html-75104cd5.js +1 -1
  81. package/docs/assets/framework-204010b2.js +5 -5
  82. package/docs/assets/getData.html-990e3787.js +1 -1
  83. package/docs/assets/getData.html-bb72025f.js +34 -34
  84. package/docs/assets/getFile.html-42368004.js +1 -1
  85. package/docs/assets/getFile.html-99abd054.js +3 -3
  86. package/docs/assets/getImage.html-3429c5a1.js +1 -1
  87. package/docs/assets/getImage.html-4d886d83.js +3 -3
  88. package/docs/assets/getTime.html-7435f922.js +1 -1
  89. package/docs/assets/getTime.html-b37f49eb.js +20 -20
  90. package/docs/assets/img.html-7d1da657.js +1 -1
  91. package/docs/assets/img.html-fbea1105.js +1 -1
  92. package/docs/assets/index.html-1695dd7c.js +1 -1
  93. package/docs/assets/index.html-65a4aa67.js +1 -1
  94. package/docs/assets/index.html-7b98d5bd.js +1 -1
  95. package/docs/assets/index.html-c01f2648.js +1 -1
  96. package/docs/assets/input-number.html-0b250d2a.js +1 -1
  97. package/docs/assets/input-number.html-a8eb0378.js +1 -1
  98. package/docs/assets/list-menu-item.html-7f1b4611.js +1 -1
  99. package/docs/assets/list-menu-item.html-84ed5ab8.js +1 -1
  100. package/docs/assets/list-menu.html-28b4163f.js +1 -1
  101. package/docs/assets/list-menu.html-cb6ba95b.js +1 -1
  102. package/docs/assets/loading.html-dae9e39d.js +6 -6
  103. package/docs/assets/loading.html-dc74c9e6.js +1 -1
  104. package/docs/assets/notify.html-e6c4c514.js +1 -1
  105. package/docs/assets/notify.html-f2c4d914.js +8 -8
  106. package/docs/assets/power-page.html-32e02f82.js +1 -1
  107. package/docs/assets/power-page.html-485e77da.js +1 -1
  108. package/docs/assets/power.html-d258cc19.js +93 -93
  109. package/docs/assets/power.html-e490bd32.js +1 -1
  110. package/docs/assets/previewImage.html-6a6b4245.js +1 -1
  111. package/docs/assets/previewImage.html-c5b7e945.js +2 -2
  112. package/docs/assets/price.html-1882c548.js +19 -19
  113. package/docs/assets/price.html-94d3f5be.js +1 -1
  114. package/docs/assets/price.html-d213df0f.js +1 -1
  115. package/docs/assets/price.html-deaf880f.js +1 -1
  116. package/docs/assets/render.html-8efcbdd4.js +1 -1
  117. package/docs/assets/render.html-df228e38.js +1 -1
  118. package/docs/assets/rule.html-2cd57fc2.js +13 -13
  119. package/docs/assets/rule.html-61662001.js +1 -1
  120. package/docs/assets/ruleValid.html-04fe2552.js +1 -1
  121. package/docs/assets/ruleValid.html-e0a776af.js +14 -14
  122. package/docs/assets/search-0782d0d1.svg +1 -1
  123. package/docs/assets/search-item.html-3f75394c.js +1 -1
  124. package/docs/assets/search-item.html-4e942ecd.js +1 -1
  125. package/docs/assets/search.html-2807043e.js +1 -1
  126. package/docs/assets/search.html-c24f8806.js +1 -1
  127. package/docs/assets/select.html-00d0607c.js +1 -1
  128. package/docs/assets/select.html-de7731f5.js +1 -1
  129. package/docs/assets/splitter.html-56f51a70.js +1 -1
  130. package/docs/assets/splitter.html-f5c836d7.js +1 -1
  131. package/docs/assets/style-161e43ab.css +1 -1
  132. package/docs/assets/symbols.html-a6aea4bf.js +1 -1
  133. package/docs/assets/symbols.html-b1f65bad.js +21 -21
  134. package/docs/assets/table-column-fixed.html-3a69e7b2.js +1 -1
  135. package/docs/assets/table-column-fixed.html-e763c38b.js +1 -1
  136. package/docs/assets/table-pagination.html-236934d3.js +1 -1
  137. package/docs/assets/table-pagination.html-c37ee2ac.js +1 -1
  138. package/docs/assets/table-splitter.html-07eab15c.js +1 -1
  139. package/docs/assets/table-splitter.html-7670ee65.js +1 -1
  140. package/docs/assets/table-summary.html-04db434f.js +1 -1
  141. package/docs/assets/table-summary.html-943c65a0.js +1 -1
  142. package/docs/assets/table.html-36253ad7.js +1 -1
  143. package/docs/assets/table.html-7f9c5d1b.js +38 -38
  144. package/docs/assets/table.html-93d53dc8.js +1 -1
  145. package/docs/assets/table.html-ac99b9cb.js +1 -1
  146. package/docs/assets/thumbnail.html-bab1976b.js +1 -1
  147. package/docs/assets/thumbnail.html-eb64e5e8.js +1 -1
  148. package/docs/assets/timestamp.html-4e54f79b.js +13 -13
  149. package/docs/assets/timestamp.html-d0e1b88a.js +1 -1
  150. package/docs/assets/toast.html-58ecbe21.js +1 -1
  151. package/docs/assets/toast.html-c9b9d36b.js +6 -6
  152. package/docs/assets/toolbar.html-83d9f97c.js +1 -1
  153. package/docs/assets/toolbar.html-ff7b8c92.js +1 -1
  154. package/docs/assets/tree.html-d07cbe79.js +23 -23
  155. package/docs/assets/tree.html-ea04193e.js +1 -1
  156. package/docs/assets/uploader-query.html-05590718.js +1 -1
  157. package/docs/assets/uploader-query.html-3175bac5.js +1 -1
  158. package/docs/assets/uploader.html-36da4394.js +2 -2
  159. package/docs/assets/uploader.html-6b5f3079.js +1 -1
  160. package/docs/assets/uploader.html-b9340b57.js +1 -1
  161. package/docs/assets/uploader.html-bc1c22e3.js +1 -1
  162. package/docs/assets/value-format.html-8ae3d47d.js +1 -1
  163. package/docs/assets/value-format.html-afa99b3d.js +1 -1
  164. package/docs/components/column-title.html +35 -35
  165. package/docs/components/data.html +62 -62
  166. package/docs/components/dialog.html +33 -33
  167. package/docs/components/dragger.html +33 -33
  168. package/docs/components/editor-code.html +33 -33
  169. package/docs/components/empty.html +33 -33
  170. package/docs/components/field-date.html +33 -33
  171. package/docs/components/field-table.html +33 -33
  172. package/docs/components/field-text.html +33 -33
  173. package/docs/components/field-tree.html +33 -33
  174. package/docs/components/img.html +33 -33
  175. package/docs/components/input-number.html +33 -33
  176. package/docs/components/list-menu-item.html +33 -33
  177. package/docs/components/list-menu.html +33 -33
  178. package/docs/components/power-page.html +33 -33
  179. package/docs/components/price.html +33 -33
  180. package/docs/components/render.html +33 -33
  181. package/docs/components/search-item.html +33 -33
  182. package/docs/components/search.html +33 -33
  183. package/docs/components/select.html +33 -33
  184. package/docs/components/splitter.html +33 -33
  185. package/docs/components/table-column-fixed.html +33 -33
  186. package/docs/components/table-pagination.html +33 -33
  187. package/docs/components/table-splitter.html +33 -33
  188. package/docs/components/table-summary.html +33 -33
  189. package/docs/components/table.html +33 -33
  190. package/docs/components/thumbnail.html +33 -33
  191. package/docs/components/toolbar.html +33 -33
  192. package/docs/components/uploader-query.html +33 -33
  193. package/docs/components/uploader.html +33 -33
  194. package/docs/components/value-format.html +33 -33
  195. package/docs/index.html +33 -33
  196. package/docs/utils/alert.html +37 -37
  197. package/docs/utils/area.html +74 -74
  198. package/docs/utils/arr.html +43 -43
  199. package/docs/utils/auth.html +40 -40
  200. package/docs/utils/bus.html +38 -38
  201. package/docs/utils/confirm.html +42 -42
  202. package/docs/utils/copy.html +45 -45
  203. package/docs/utils/dialog.html +100 -100
  204. package/docs/utils/dict.html +55 -55
  205. package/docs/utils/dictOptions.html +37 -37
  206. package/docs/utils/form.html +34 -34
  207. package/docs/utils/getData.html +66 -66
  208. package/docs/utils/getFile.html +35 -35
  209. package/docs/utils/getImage.html +35 -35
  210. package/docs/utils/getTime.html +52 -52
  211. package/docs/utils/index.html +33 -33
  212. package/docs/utils/loading.html +38 -38
  213. package/docs/utils/notify.html +40 -40
  214. package/docs/utils/power.html +125 -125
  215. package/docs/utils/previewImage.html +34 -34
  216. package/docs/utils/price.html +51 -51
  217. package/docs/utils/rule.html +45 -45
  218. package/docs/utils/ruleValid.html +46 -46
  219. package/docs/utils/symbols.html +53 -53
  220. package/docs/utils/table.html +70 -70
  221. package/docs/utils/timestamp.html +45 -45
  222. package/docs/utils/toast.html +38 -38
  223. package/docs/utils/tree.html +55 -55
  224. package/docs/utils/uploader.html +34 -34
  225. package/package.json +2 -1
  226. package/sass/common.scss +184 -184
  227. package/sass/index.scss +12 -12
  228. package/sass/quasar/field.scss +250 -250
  229. package/sass/quasar/table.scss +168 -168
  230. package/sass/variables.scss +140 -140
  231. package/utils/$form.js +72 -72
  232. package/utils/$power.js +1464 -1460
  233. package/utils/$render.js +75 -75
  234. package/utils/$search.js +416 -416
  235. package/utils/$table.js +1275 -1275
  236. package/utils/config.js +52 -52
  237. package/utils/dialog.js +36 -36
  238. package/utils/dict.js +21 -21
  239. package/utils/getData.js +88 -88
  240. package/utils/getFile.js +62 -62
  241. package/utils/getImage.js +227 -227
  242. package/utils/getTime.js +113 -113
  243. package/utils/index.js +65 -65
  244. package/utils/previewImage.js +14 -14
  245. package/utils/timestamp.js +18 -18
  246. package/utils/useFileUrl.js +26 -26
  247. package/utils/useSearch.js +499 -499
  248. package/utils/useUploader.js +303 -303
@@ -1,928 +1,928 @@
1
- <template>
2
- <div class="n-uploader-query">
3
-
4
- <!-- 上传按钮 -->
5
- <slot
6
- name="button"
7
- :disable="disable || readonly"
8
- :size="currentSize"
9
- v-if="$slots.button"
10
- />
11
- <div
12
- class="n-uploader-query__button--button"
13
- v-else-if="! noButton && currentButtonType === 'button'"
14
- >
15
- <!-- 按钮组-->
16
- <q-btn-group outline>
17
- <!-- 上传本地图片 -->
18
- <q-btn
19
- class="n-button-icon"
20
- :label="buttonText || '上传'"
21
- @click="uploader.chooseUpload"
22
- color="primary"
23
- outline
24
- :disable="disable || readonly"
25
- unelevated
26
- v-bind="buttonProps"
27
- />
28
- <!-- 上传网络图片 -->
29
- <q-btn
30
- class="n-button-icon q-px-sm"
31
- icon="cloud_upload"
32
- @click="uploader.chooseUploadNet"
33
- color="primary"
34
- outline
35
- :disable="disable || readonly"
36
- unelevated
37
- v-bind="buttonProps"
38
- v-if="showUploadNetButton"
39
- />
40
- </q-btn-group>
41
- </div>
42
-
43
- <!-- 拖拽器 -->
44
- <n-dragger
45
- class="n-uploader-query__query row q-gutter-sm"
46
- v-model="query"
47
- :drag="currentDrag"
48
- @update:model-value="uploader.updateValue"
49
- v-slot="{ mousedown, fromIndex, dragStart, dragEnter, dragEnd }"
50
- >
51
- <!-- 上传图片队列 -->
52
- <template v-if="type === 'image'">
53
-
54
- <!-- 左边方块按钮 -->
55
- <template v-if="! disable && ! readonly && ! rightSquareButton">
56
- <slot
57
- name="square-button"
58
- :size="currentSize"
59
- :show="showSquareButton"
60
- v-if="$slots['square-button']"
61
- />
62
- <div
63
- class="n-uploader-query__button--square"
64
- :style="{
65
- width: toPx(currentSize),
66
- height: toPx(currentSize),
67
- }"
68
- v-show="showSquareButton"
69
- v-else-if="! noButton && currentButtonType === 'square'"
70
- >
71
- <!-- 上传本地图片 -->
72
- <div
73
- class="n-uploader-query__button--square-button cursor-pointer"
74
- @click="uploader.chooseUpload"
75
- >
76
- <q-icon
77
- name="add"
78
- :size="toPx(currentSize / 2)"
79
- />
80
- <div class="n-uploader-query__button--square-button__text" v-if="buttonText">{{buttonText}}</div>
81
- </div>
82
-
83
- <!-- 上传网络图片 -->
84
- <div
85
- class="n-uploader-query__button--square-button q-mt-xs cursor-pointer"
86
- @click="uploader.chooseUploadNet"
87
- v-if="showUploadNetButton"
88
- >
89
- <q-icon
90
- name="cloud_upload"
91
- :size="toPx(currentSize / 3)"
92
- />
93
- <div class="n-uploader-query__button--square-button__text" v-if="buttonText">{{buttonText}}</div>
94
- </div>
95
- </div>
96
- </template>
97
-
98
- <!-- 单个图片 -->
99
- <div
100
- v-for="(fileItem, fileItemIndex) in query"
101
- :key="`query-item-${fileItem.id}`"
102
- class="n-uploader-query__item n-uploader-query__item--image"
103
- :class="[
104
- {
105
- ghost: fileItemIndex === fromIndex,
106
- },
107
- itemClass,
108
- ]"
109
- :style="itemStyle"
110
- :draggable="currentDrag"
111
- @mousedown.self="mousedown($event, fileItemIndex)"
112
- @mouseup="dragEnd"
113
- @dragstart="dragStart($event, fileItemIndex)"
114
- @dragenter="dragEnter($event, fileItemIndex)"
115
- @dragend="dragEnd"
116
- >
117
- <n-img
118
- :src="currentQuery[fileItemIndex].src"
119
- :spinner-size="toPx(currentSize / 2)"
120
- :width="toPx(currentSize)"
121
- :height="toPx(currentSize)"
122
- @click="onFileItemClick(fileItem, fileItemIndex)"
123
- fit="fill"
124
- >
125
- <!-- 如果是外链 -->
126
- <span class="n-uploader-query__item__net" v-if="fileItem.isNet && ! fileItem.isNetUploaded">链接</span>
127
-
128
- <!-- 内容 -->
129
- <div
130
- class="n-uploader-query__item__inner absolute-full flex flex-center no-padding transparent"
131
- :class="{
132
- 'transparent': fileItem.status < UPLOAD_STATUS.success,
133
- }"
134
- v-if="fileItem.status !== UPLOAD_STATUS.success"
135
- >
136
- <!-- 如果上传失败 -->
137
- <div
138
- class="n-uploader-query__item__inner__msg n-uploader-query__item__inner__msg--error"
139
- v-if="fileItem.status === UPLOAD_STATUS.fail"
140
- >{{fileItem.msg}}</div>
141
-
142
- <!-- 上传中前 -->
143
- <q-circular-progress
144
- indeterminate
145
- rounded
146
- :size="toPx(currentSize / 1.5)"
147
- :thickness="0.14"
148
- color="white"
149
- v-if="fileItem.status < UPLOAD_STATUS.uploading"
150
- />
151
-
152
- <!-- 上传中 -->
153
- <q-circular-progress
154
- :value="fileItem.progress"
155
- :size="toPx(currentSize / 1.5)"
156
- :thickness="0.14"
157
- color="white"
158
- track-color="grey-5"
159
- show-value
160
- v-else-if="fileItem.status === UPLOAD_STATUS.uploading"
161
- >
162
- <q-icon
163
- class="cursor-pointer"
164
- name="pause"
165
- :size="toPx(currentSize / 3)"
166
- @click.prevent.stop="uploader.deleteFileItem(fileItem)"
167
- />
168
- </q-circular-progress>
169
- </div>
170
-
171
- <!-- 操作 -->
172
- <div
173
- class="n-uploader-query__item__settings transparent no-padding"
174
- v-if="fileItem.status !== UPLOAD_STATUS.uploading"
175
- >
176
- <!-- 操作插槽-->
177
- <slot
178
- name="settings"
179
- :file="fileItem"
180
- :index="fileItemIndex"
181
- />
182
-
183
- <!-- 预览 -->
184
- <q-icon
185
- class="n-uploader-query__item__settings__icon cursor-pointer"
186
- name="search"
187
- :size="settingsIconSize"
188
- title="预览"
189
- @click.prevent.stop="previewImage(fileItemIndex)"
190
- v-bind="settingsIconProps"
191
- v-if="! noPreview && currentQuery[fileItemIndex].preview_src"
192
- />
193
-
194
- <!-- 删除 -->
195
- <q-icon
196
- class="n-uploader-query__item__settings__icon cursor-pointer"
197
- name="close"
198
- :size="settingsIconSize"
199
- title="删除"
200
- @click.prevent.stop="uploader.deleteFileItem(fileItem)"
201
- v-bind="settingsIconProps"
202
- v-if="! noDelete && ! disable && ! readonly"
203
- />
204
-
205
- </div>
206
-
207
- <!-- 插槽-->
208
- <slot
209
- name="item-append"
210
- :file="fileItem"
211
- :index="fileItemIndex"
212
- />
213
- </n-img>
214
-
215
- </div>
216
-
217
- <!-- 右边方块按钮 -->
218
- <template v-if="! disable && ! readonly && rightSquareButton">
219
- <slot
220
- name="square-button"
221
- :size="currentSize"
222
- :show="showSquareButton"
223
- v-if="$slots['square-button']"
224
- />
225
- <div
226
- class="n-uploader-query__button--square cursor-pointer"
227
- :style="{
228
- width: toPx(currentSize),
229
- height: toPx(currentSize),
230
- }"
231
- @click="uploader.chooseUpload"
232
- v-show="showSquareButton"
233
- v-else-if="! noButton && currentButtonType === 'square'"
234
- >
235
- <q-icon
236
- name="add"
237
- :size="toPx(currentSize / 2)"
238
- />
239
- <div class="n-uploader-query__button--square__text" v-if="buttonText">{{buttonText}}</div>
240
- </div>
241
- </template>
242
- </template>
243
-
244
- <!-- 上传文件队列 -->
245
- <template v-else>
246
-
247
- <!-- 单个文件 -->
248
- <div
249
- v-for="(fileItem, fileItemIndex) in query"
250
- :key="`query-item-${fileItem.id}`"
251
- class="n-uploader-query__item n-uploader-query__item--file"
252
- :class="[
253
- {
254
- ghost: fileItemIndex === fromIndex,
255
- },
256
- itemClass,
257
- ]"
258
- :style="[
259
- {
260
- height: toPx(currentSize),
261
- },
262
- itemStyle,
263
- ]"
264
- :draggable="currentDrag"
265
- @mousedown.self="mousedown($event, fileItemIndex)"
266
- @mouseup="dragEnd"
267
- @dragstart="dragStart($event, fileItemIndex)"
268
- @dragenter="dragEnter($event, fileItemIndex)"
269
- @dragend="dragEnd"
270
- >
271
- <!-- 如果是外链 -->
272
- <span class="n-uploader-query__item__net" v-if="fileItem.isNet && ! fileItem.isNetUploaded">链接</span>
273
-
274
- <!-- 图标 -->
275
- <div
276
- class="n-uploader-query__item__icon"
277
- :style="{
278
- width: toPx(currentSize),
279
- height: toPx(currentSize),
280
- }"
281
- >
282
- <!-- 上传中前 -->
283
- <q-circular-progress
284
- class="n-uploader-query__item__icon__icon"
285
- indeterminate
286
- rounded
287
- :size="toPx(currentSize / 1.8)"
288
- :thickness="0.18"
289
- v-if="fileItem.status < UPLOAD_STATUS.uploading"
290
- />
291
-
292
- <!-- 上传中 -->
293
- <q-circular-progress
294
- class="n-uploader-query__item__icon__icon"
295
- :value="fileItem.progress"
296
- :size="toPx(currentSize / 1.8)"
297
- :thickness="0.18"
298
- show-value
299
- v-else-if="fileItem.status === UPLOAD_STATUS.uploading"
300
- >
301
- <q-icon
302
- class="cursor-pointer"
303
- name="pause"
304
- :size="toPx(currentSize / 3)"
305
- @click.prevent.stop="uploader.deleteFileItem(fileItem)"
306
- />
307
- </q-circular-progress>
308
-
309
- <!-- 文件图标 -->
310
- <q-icon
311
- class="n-uploader-query__item__icon__icon"
312
- name="description"
313
- :size="toPx(currentSize / 1.5)"
314
- v-else-if="type === 'file'"
315
- />
316
-
317
- <!-- 播放图标 -->
318
- <q-icon
319
- class="n-uploader-query__item__icon__icon cursor-pointer"
320
- name="play_circle"
321
- title="播放"
322
- :size="toPx(currentSize / 1.5)"
323
- @click.prevent.stop="uploader.play(fileItem)"
324
- v-else
325
- />
326
- </div>
327
-
328
- <!-- 信息 -->
329
- <div class="n-uploader-query__item__info" @click="onFileItemClick(fileItem, fileItemIndex)">
330
- <!-- 标题 -->
331
- <div class="n-uploader-query__item__info__title ellipsis">{{getFileName(fileItem)}}</div>
332
- <!-- 错误提示 -->
333
- <div class="n-uploader-query__item__info__msg--error" v-if="fileItem.status === UPLOAD_STATUS.fail">{{fileItem.msg}}</div>
334
- </div>
335
-
336
- <!-- 操作 -->
337
- <div class="n-uploader-query__item__settings">
338
-
339
- <!-- 操作插槽-->
340
- <slot
341
- name="settings"
342
- :file="fileItem"
343
- :index="fileItemIndex"
344
- />
345
-
346
- <template v-if="fileItem.status === UPLOAD_STATUS.success">
347
-
348
- <!-- 复制地址 -->
349
- <q-icon
350
- class="n-uploader-query__item__settings__icon cursor-pointer"
351
- name="content_copy"
352
- color="white"
353
- :size="settingsIconSize"
354
- title="复制地址"
355
- @click.prevent.stop="uploader.copyUrl(fileItem)"
356
- v-bind="settingsIconProps"
357
- />
358
-
359
- <!-- 修改 -->
360
- <q-icon
361
- class="n-uploader-query__item__settings__icon cursor-pointer"
362
- name="edit"
363
- color="white"
364
- :size="settingsIconSize"
365
- title="修改"
366
- v-bind="settingsIconProps"
367
- v-if="! noEdit && ! disable && ! readonly && ! fileItem.isNet"
368
- >
369
- <q-popup-edit
370
- :model-value="fileItem.title"
371
- buttons
372
- label-set="保存"
373
- @save="uploader.editFileTitle($event, fileItem)"
374
- v-slot="scope"
375
- >
376
- <q-input
377
- v-model="scope.value"
378
- dense
379
- autofocus
380
- counter
381
- :maxlength="50"
382
- @keyup.enter="scope.set"
383
- >
384
- <template v-slot:append>
385
- <span class="text-subtitle2 text-weight-bold">.{{fileItem.ext}}</span>
386
- </template>
387
- </q-input>
388
- </q-popup-edit>
389
- </q-icon>
390
- </template>
391
-
392
- <!-- 删除 -->
393
- <q-icon
394
- class="n-uploader-query__item__settings__icon cursor-pointer"
395
- name="close"
396
- color="white"
397
- :size="settingsIconSize"
398
- title="删除"
399
- @click.prevent.stop="uploader.deleteFileItem(fileItem)"
400
- v-bind="settingsIconProps"
401
- v-if="! noDelete && ! disable && ! readonly"
402
- />
403
- </div>
404
-
405
- <!-- 插槽-->
406
- <slot
407
- name="item-append"
408
- :file="fileItem"
409
- :index="fileItemIndex"
410
- />
411
-
412
- </div>
413
- </template>
414
- </n-dragger>
415
- </div>
416
- </template>
417
-
418
- <script>
419
- import { computed, inject } from 'vue'
420
- import { useQuasar } from 'quasar'
421
-
422
- import $n_has from 'lodash/has'
423
- import $n_get from 'lodash/get'
424
-
425
- import $n_px from '@netang/utils/px'
426
- import $n_forEach from '@netang/utils/forEach'
427
- import $n_isValidArray from '@netang/utils/isValidArray'
428
- import $n_isValidString from '@netang/utils/isValidString'
429
-
430
- import $n_getImage from '../../utils/getImage'
431
- import $n_previewImage from '../../utils/previewImage'
432
-
433
- import NDragger from '../dragger'
434
-
435
- import { NUploaderKey } from '../../utils/symbols'
436
-
437
- import {
438
- // 上传状态
439
- UPLOAD_STATUS,
440
- } from '../../utils/useUploader'
441
-
442
- export default {
443
-
444
- /**
445
- * 标识
446
- */
447
- name: 'NUploaderQuery',
448
-
449
- /**
450
- * 组件
451
- */
452
- components: {
453
- NDragger,
454
- },
455
-
456
- /**
457
- * 声明属性
458
- */
459
- props: {
460
- // 单个文件类名
461
- itemClass: [String, Array, Object],
462
- // 单个文件样式
463
- itemStyle: [String, Array, Object],
464
- // 按钮类型, 可选值 square button
465
- buttonType: {
466
- type: String,
467
- validator: v => [ 'square', 'button' ].includes(v),
468
- },
469
- // 按钮文字
470
- buttonText: String,
471
- // 按钮声明属性
472
- buttonProps: Object,
473
- // 图片/按钮/文件 尺寸
474
- size: Number,
475
- // 是否开启拖拽
476
- drag: {
477
- type: Boolean,
478
- default: true,
479
- },
480
- // 是否禁用
481
- disable: Boolean,
482
- // 是否只读
483
- readonly: Boolean,
484
- // 是否隐藏按钮
485
- noButton: Boolean,
486
- // 是否隐藏预览按钮
487
- noPreview: Boolean,
488
- // 是否隐藏修改按钮
489
- noEdit: Boolean,
490
- // 是否隐藏删除按钮
491
- noDelete: Boolean,
492
- // 自动显示方块按钮
493
- autoShowSquareButton: Boolean,
494
- // 方块按钮在右边显示
495
- rightSquareButton: Boolean,
496
- // 设置图标尺寸
497
- settingsIconSize: {
498
- type: String,
499
- default: 'xs',
500
- },
501
- // 设置图标传参
502
- settingsIconProps: Object,
503
- // 是否显示上传网络外链按钮
504
- showUploadNetButton: Boolean,
505
- },
506
-
507
- /**
508
- * 声明事件
509
- */
510
- emits: [
511
- 'update:modelValue',
512
- 'itemClick',
513
- ],
514
-
515
- /**
516
- * 组合式
517
- */
518
- setup(props, { emit }) {
519
-
520
- // ==========【数据】============================================================================================
521
-
522
- // quasar 对象
523
- const $q = useQuasar()
524
-
525
- // 获取上传器注入数据
526
- const {
527
- // 声明属性
528
- props: uploaderProps,
529
- // 上传器
530
- uploader,
531
- // 文件队列
532
- query,
533
- } = inject(NUploaderKey)
534
-
535
- // ==========【计算属性】=========================================================================================
536
-
537
- /**
538
- * 当前上传文件队列
539
- */
540
- const currentQuery = computed(function () {
541
-
542
- // 如果不是图片
543
- if (uploaderProps.type !== 'image') {
544
- if ($n_isValidArray(query.value)) {
545
- return query.value
546
- }
547
- return []
548
- }
549
-
550
- const lists = []
551
-
552
- $n_forEach(query.value, function (fileItem) {
553
- const newItem = Object.assign({}, fileItem)
554
-
555
- let src = ''
556
- let preview_src = ''
557
-
558
- if ($n_has(fileItem, '__img')) {
559
- src = fileItem.__img
560
- preview_src = src
561
- } else if ($n_isValidString(fileItem.hash)) {
562
- src = $n_getImage(fileItem.hash, { w: $q.platform.is.mobile ? currentSize.value * 2 : currentSize.value })
563
- if (src) {
564
- // 预览地址
565
- preview_src = fileItem.hash
566
- }
567
- }
568
-
569
- lists.push(Object.assign(newItem, {
570
- // 图片地址
571
- src,
572
- // 预览地址
573
- preview_src,
574
- }))
575
- })
576
-
577
- return lists
578
- })
579
-
580
-
581
- /**
582
- * 当前是否开启拖拽
583
- */
584
- const currentDrag = computed(function() {
585
- return props.drag
586
- && query.value.length > 1
587
- && ! props.readonly
588
- && ! props.disable
589
- })
590
-
591
- /**
592
- * 当前按钮类型
593
- */
594
- const currentButtonType = computed(function () {
595
- if (props.buttonType) {
596
- return props.buttonType
597
- }
598
- return uploaderProps.type === 'image' ? 'square' : 'button'
599
- })
600
-
601
- /**
602
- * 当前尺寸
603
- */
604
- const currentSize = computed(function () {
605
- if (props.size) {
606
- return props.size
607
- }
608
- return uploaderProps.type === 'image' ? 70 : 50
609
- })
610
-
611
- /**
612
- * 是否显示方块按钮
613
- */
614
- const showSquareButton = computed(function () {
615
- // 自动显示方块按钮 && 有上传文件限制数量
616
- return props.autoShowSquareButton && uploaderProps.count > 0 ?
617
- // 如果 当前上传文件队列数量 < 上传文件限制数量
618
- currentQuery.value.length < uploaderProps.count
619
- // 始终显示
620
- : true
621
- })
622
-
623
- // ==========【方法】=============================================================================================
624
-
625
- /**
626
- * 预览图片
627
- */
628
- function previewImage(startPosition) {
629
- $n_previewImage({
630
- // 需要预览的图片 URL 数组
631
- images: currentQuery.value.map(e => e.preview_src),
632
- // 图片预览起始位置索引
633
- startPosition,
634
- })
635
- }
636
-
637
- /**
638
- * 获取文件名称
639
- */
640
- function getFileName(fileItem) {
641
- return fileItem.title + ($n_get(fileItem, 'ext') ? '.' + fileItem.ext : '')
642
- }
643
-
644
- /**
645
- * 文件点击
646
- */
647
- function onFileItemClick(file, index) {
648
- emit('itemClick', {
649
- file,
650
- index,
651
- })
652
- }
653
-
654
- // ==========【返回】=============================================================================================
655
-
656
- return {
657
- // 上传状态
658
- UPLOAD_STATUS,
659
- // 上传文件类型, 可选值 file image video audio
660
- type: uploaderProps.type,
661
- // 上传文件数量(0:不限)
662
- count: uploaderProps.count,
663
- // 文件队列
664
- query,
665
- // 当前上传文件队列
666
- currentQuery,
667
-
668
- // 当前是否开启拖拽
669
- currentDrag,
670
- // 当前按钮类型
671
- currentButtonType,
672
- // 当前尺寸
673
- currentSize,
674
- // 是否显示方块按钮
675
- showSquareButton,
676
-
677
- // 上传器
678
- uploader,
679
-
680
- // 预览图片
681
- previewImage,
682
- // 获取文件名称
683
- getFileName,
684
- // 文件点击
685
- onFileItemClick,
686
-
687
- toPx: $n_px,
688
- }
689
- },
690
- }
691
- </script>
692
-
693
- <style lang="scss">
694
- @import "@/assets/sass/variables.scss";
695
-
696
- // 上传器队列
697
- .n-uploader-query {
698
-
699
- // 上传按钮
700
- &__button {
701
-
702
- // 方块
703
- &--square {
704
- display: inline-flex;
705
- overflow: hidden;
706
- flex-direction: column;
707
-
708
- &-button {
709
- flex: 1;
710
- display: flex;
711
- vertical-align: middle;
712
- border: 1px dashed rgba(var(--n-reverse-color-rgb), 0.2);
713
- flex-direction: column;
714
- justify-content: center;
715
- align-items: center;
716
- color: rgba(var(--n-reverse-color-rgb), 0.4);
717
- border-radius: 4px;
718
- overflow: hidden;
719
-
720
- &:hover {
721
- border-color: $primary;
722
- }
723
-
724
- // 文字
725
- &__text {
726
- font-size: 12px;
727
- }
728
- }
729
- }
730
-
731
- // 按钮
732
- &--button {
733
- + .n-uploader-query__query {
734
- margin-top: 0;
735
- }
736
- }
737
- }
738
-
739
- // 上传单个文件
740
- &__item {
741
- position: relative;
742
-
743
- // 开启拖拽
744
- &[draggable="true"] {
745
- cursor: move;
746
- }
747
-
748
- // 当前拖拽占位元素
749
- &.ghost {
750
- &:after {
751
- content: "";
752
- position: absolute;
753
- top: 0;
754
- left: 0;
755
- right: 0;
756
- bottom: 0;
757
- border: 2px dashed mix(#ffffff, $primary, 40%);
758
- border-radius: 4px;
759
- background-color: rgba(255, 255, 255, 0.75);
760
- }
761
-
762
- .n-uploader-query__item__inner,
763
- .n-uploader-query__item__settings {
764
- display: none;
765
- }
766
- }
767
-
768
- &:hover {
769
- .n-uploader-query__item__settings {
770
- visibility: visible;
771
- }
772
- }
773
-
774
- // 单个图片
775
- &--image {
776
- border-radius: 4px;
777
- background-color: rgba(0,0,0,0.1);
778
- overflow: hidden;
779
- }
780
-
781
- // 单个文件
782
- &--file {
783
- position: relative;
784
- width: 300px;
785
- display: flex;
786
- flex-direction: row;
787
- align-items: center;
788
- border-radius: 4px;
789
- color: rgba(var(--n-reverse-color-rgb), 0.8);
790
- background-color: rgba(var(--n-reverse-color-rgb), 0.05);
791
-
792
- // 图标
793
- .n-uploader-query__item__icon {
794
- position: relative;
795
- display: flex;
796
- align-items: center;
797
- justify-content: center;
798
- z-index: 1;
799
-
800
- &__icon {
801
- color: rgba(var(--n-reverse-color-rgb), 0.2);
802
- }
803
- }
804
-
805
- // 信息
806
- .n-uploader-query__item__info {
807
- display: flex;
808
- flex-direction: column;
809
- line-height: 18px;
810
-
811
- &__title {
812
- max-width: 150px;
813
- }
814
-
815
- &__msg--error {
816
- color: $negative;
817
- }
818
- }
819
- }
820
-
821
- // 外链
822
- &__net {
823
- position: absolute;
824
- bottom: -1px;
825
- right: -1px;
826
- color: #ffffff;
827
- padding: 1px 3px;
828
- border-radius: 3px;
829
- background-color: var(--q-primary);
830
- transform: scale(0.7);
831
- pointer-events: none;
832
- }
833
-
834
- //操作
835
- &__settings {
836
- position: absolute;
837
- top: 5px;
838
- right: 5px;
839
- visibility: hidden;
840
-
841
- &__icon {
842
- background-color: rgba(0,0,0,0.5) !important;
843
- color: #ffffff !important;
844
- border-radius: 50%;
845
- padding: 5px;
846
-
847
- + .n-uploader-query__item__settings__icon {
848
- margin-left: 4px;
849
- }
850
-
851
- &:hover {
852
- background-color: rgba(0,0,0,0.8);
853
- }
854
- }
855
- }
856
-
857
- // 内容
858
- &__inner {
859
-
860
- &__msg {
861
- margin: 3px;
862
- padding: 2px 3px;
863
- line-height: 18px;
864
- font-size: 12px;
865
- background-color: rgba(0,0,0,0.6);
866
- border-radius: 6px;
867
-
868
- &--error {
869
- background-color: $warning;
870
- }
871
- }
872
- }
873
- }
874
- }
875
-
876
- @media (max-width: $breakpoint-xs-max){
877
- // 上传器队列
878
- .n-uploader-query {
879
- // 上传单个文件
880
- &__item {
881
- // 单个文件
882
- &--file {
883
- width: 100%;
884
- }
885
- }
886
-
887
- // 信息
888
- .n-uploader-query__item__info {
889
- &__title {
890
- max-width: 200px;
891
- }
892
- }
893
- }
894
- }
895
-
896
- /**
897
- * 手机版
898
- */
899
- body.mobile {
900
- // 上传器队列
901
- .n-uploader-query {
902
- // 上传单个文件
903
- &__item {
904
- &__settings {
905
- visibility: visible;
906
- }
907
- }
908
- }
909
- }
910
-
911
- /**
912
- * 暗黑
913
- */
914
- .body--dark {
915
- .n-uploader-query__item--file {
916
- .n-uploader-query__item__settings {
917
- // 图标
918
- &__icon {
919
- background-color: rgba(255,255,255, 0.1);
920
-
921
- &:hover {
922
- background-color: rgba(255,255,255, 0.2);
923
- }
924
- }
925
- }
926
- }
927
- }
928
- </style>
1
+ <template>
2
+ <div class="n-uploader-query">
3
+
4
+ <!-- 上传按钮 -->
5
+ <slot
6
+ name="button"
7
+ :disable="disable || readonly"
8
+ :size="currentSize"
9
+ v-if="$slots.button"
10
+ />
11
+ <div
12
+ class="n-uploader-query__button--button"
13
+ v-else-if="! noButton && currentButtonType === 'button'"
14
+ >
15
+ <!-- 按钮组-->
16
+ <q-btn-group outline>
17
+ <!-- 上传本地图片 -->
18
+ <q-btn
19
+ class="n-button-icon"
20
+ :label="buttonText || '上传'"
21
+ @click="uploader.chooseUpload"
22
+ color="primary"
23
+ outline
24
+ :disable="disable || readonly"
25
+ unelevated
26
+ v-bind="buttonProps"
27
+ />
28
+ <!-- 上传网络图片 -->
29
+ <q-btn
30
+ class="n-button-icon q-px-sm"
31
+ icon="cloud_upload"
32
+ @click="uploader.chooseUploadNet"
33
+ color="primary"
34
+ outline
35
+ :disable="disable || readonly"
36
+ unelevated
37
+ v-bind="buttonProps"
38
+ v-if="showUploadNetButton"
39
+ />
40
+ </q-btn-group>
41
+ </div>
42
+
43
+ <!-- 拖拽器 -->
44
+ <n-dragger
45
+ class="n-uploader-query__query row q-gutter-sm"
46
+ v-model="query"
47
+ :drag="currentDrag"
48
+ @update:model-value="uploader.updateValue"
49
+ v-slot="{ mousedown, fromIndex, dragStart, dragEnter, dragEnd }"
50
+ >
51
+ <!-- 上传图片队列 -->
52
+ <template v-if="type === 'image'">
53
+
54
+ <!-- 左边方块按钮 -->
55
+ <template v-if="! disable && ! readonly && ! rightSquareButton">
56
+ <slot
57
+ name="square-button"
58
+ :size="currentSize"
59
+ :show="showSquareButton"
60
+ v-if="$slots['square-button']"
61
+ />
62
+ <div
63
+ class="n-uploader-query__button--square"
64
+ :style="{
65
+ width: toPx(currentSize),
66
+ height: toPx(currentSize),
67
+ }"
68
+ v-show="showSquareButton"
69
+ v-else-if="! noButton && currentButtonType === 'square'"
70
+ >
71
+ <!-- 上传本地图片 -->
72
+ <div
73
+ class="n-uploader-query__button--square-button cursor-pointer"
74
+ @click="uploader.chooseUpload"
75
+ >
76
+ <q-icon
77
+ name="add"
78
+ :size="toPx(currentSize / 2)"
79
+ />
80
+ <div class="n-uploader-query__button--square-button__text" v-if="buttonText">{{buttonText}}</div>
81
+ </div>
82
+
83
+ <!-- 上传网络图片 -->
84
+ <div
85
+ class="n-uploader-query__button--square-button q-mt-xs cursor-pointer"
86
+ @click="uploader.chooseUploadNet"
87
+ v-if="showUploadNetButton"
88
+ >
89
+ <q-icon
90
+ name="cloud_upload"
91
+ :size="toPx(currentSize / 3)"
92
+ />
93
+ <div class="n-uploader-query__button--square-button__text" v-if="buttonText">{{buttonText}}</div>
94
+ </div>
95
+ </div>
96
+ </template>
97
+
98
+ <!-- 单个图片 -->
99
+ <div
100
+ v-for="(fileItem, fileItemIndex) in query"
101
+ :key="`query-item-${fileItem.id}`"
102
+ class="n-uploader-query__item n-uploader-query__item--image"
103
+ :class="[
104
+ {
105
+ ghost: fileItemIndex === fromIndex,
106
+ },
107
+ itemClass,
108
+ ]"
109
+ :style="itemStyle"
110
+ :draggable="currentDrag"
111
+ @mousedown.self="mousedown($event, fileItemIndex)"
112
+ @mouseup="dragEnd"
113
+ @dragstart="dragStart($event, fileItemIndex)"
114
+ @dragenter="dragEnter($event, fileItemIndex)"
115
+ @dragend="dragEnd"
116
+ >
117
+ <n-img
118
+ :src="currentQuery[fileItemIndex].src"
119
+ :spinner-size="toPx(currentSize / 2)"
120
+ :width="toPx(currentSize)"
121
+ :height="toPx(currentSize)"
122
+ @click="onFileItemClick(fileItem, fileItemIndex)"
123
+ fit="fill"
124
+ >
125
+ <!-- 如果是外链 -->
126
+ <span class="n-uploader-query__item__net" v-if="fileItem.isNet && ! fileItem.isNetUploaded">链接</span>
127
+
128
+ <!-- 内容 -->
129
+ <div
130
+ class="n-uploader-query__item__inner absolute-full flex flex-center no-padding transparent"
131
+ :class="{
132
+ 'transparent': fileItem.status < UPLOAD_STATUS.success,
133
+ }"
134
+ v-if="fileItem.status !== UPLOAD_STATUS.success"
135
+ >
136
+ <!-- 如果上传失败 -->
137
+ <div
138
+ class="n-uploader-query__item__inner__msg n-uploader-query__item__inner__msg--error"
139
+ v-if="fileItem.status === UPLOAD_STATUS.fail"
140
+ >{{fileItem.msg}}</div>
141
+
142
+ <!-- 上传中前 -->
143
+ <q-circular-progress
144
+ indeterminate
145
+ rounded
146
+ :size="toPx(currentSize / 1.5)"
147
+ :thickness="0.14"
148
+ color="white"
149
+ v-if="fileItem.status < UPLOAD_STATUS.uploading"
150
+ />
151
+
152
+ <!-- 上传中 -->
153
+ <q-circular-progress
154
+ :value="fileItem.progress"
155
+ :size="toPx(currentSize / 1.5)"
156
+ :thickness="0.14"
157
+ color="white"
158
+ track-color="grey-5"
159
+ show-value
160
+ v-else-if="fileItem.status === UPLOAD_STATUS.uploading"
161
+ >
162
+ <q-icon
163
+ class="cursor-pointer"
164
+ name="pause"
165
+ :size="toPx(currentSize / 3)"
166
+ @click.prevent.stop="uploader.deleteFileItem(fileItem)"
167
+ />
168
+ </q-circular-progress>
169
+ </div>
170
+
171
+ <!-- 操作 -->
172
+ <div
173
+ class="n-uploader-query__item__settings transparent no-padding"
174
+ v-if="fileItem.status !== UPLOAD_STATUS.uploading"
175
+ >
176
+ <!-- 操作插槽-->
177
+ <slot
178
+ name="settings"
179
+ :file="fileItem"
180
+ :index="fileItemIndex"
181
+ />
182
+
183
+ <!-- 预览 -->
184
+ <q-icon
185
+ class="n-uploader-query__item__settings__icon cursor-pointer"
186
+ name="search"
187
+ :size="settingsIconSize"
188
+ title="预览"
189
+ @click.prevent.stop="previewImage(fileItemIndex)"
190
+ v-bind="settingsIconProps"
191
+ v-if="! noPreview && currentQuery[fileItemIndex].preview_src"
192
+ />
193
+
194
+ <!-- 删除 -->
195
+ <q-icon
196
+ class="n-uploader-query__item__settings__icon cursor-pointer"
197
+ name="close"
198
+ :size="settingsIconSize"
199
+ title="删除"
200
+ @click.prevent.stop="uploader.deleteFileItem(fileItem)"
201
+ v-bind="settingsIconProps"
202
+ v-if="! noDelete && ! disable && ! readonly"
203
+ />
204
+
205
+ </div>
206
+
207
+ <!-- 插槽-->
208
+ <slot
209
+ name="item-append"
210
+ :file="fileItem"
211
+ :index="fileItemIndex"
212
+ />
213
+ </n-img>
214
+
215
+ </div>
216
+
217
+ <!-- 右边方块按钮 -->
218
+ <template v-if="! disable && ! readonly && rightSquareButton">
219
+ <slot
220
+ name="square-button"
221
+ :size="currentSize"
222
+ :show="showSquareButton"
223
+ v-if="$slots['square-button']"
224
+ />
225
+ <div
226
+ class="n-uploader-query__button--square cursor-pointer"
227
+ :style="{
228
+ width: toPx(currentSize),
229
+ height: toPx(currentSize),
230
+ }"
231
+ @click="uploader.chooseUpload"
232
+ v-show="showSquareButton"
233
+ v-else-if="! noButton && currentButtonType === 'square'"
234
+ >
235
+ <q-icon
236
+ name="add"
237
+ :size="toPx(currentSize / 2)"
238
+ />
239
+ <div class="n-uploader-query__button--square__text" v-if="buttonText">{{buttonText}}</div>
240
+ </div>
241
+ </template>
242
+ </template>
243
+
244
+ <!-- 上传文件队列 -->
245
+ <template v-else>
246
+
247
+ <!-- 单个文件 -->
248
+ <div
249
+ v-for="(fileItem, fileItemIndex) in query"
250
+ :key="`query-item-${fileItem.id}`"
251
+ class="n-uploader-query__item n-uploader-query__item--file"
252
+ :class="[
253
+ {
254
+ ghost: fileItemIndex === fromIndex,
255
+ },
256
+ itemClass,
257
+ ]"
258
+ :style="[
259
+ {
260
+ height: toPx(currentSize),
261
+ },
262
+ itemStyle,
263
+ ]"
264
+ :draggable="currentDrag"
265
+ @mousedown.self="mousedown($event, fileItemIndex)"
266
+ @mouseup="dragEnd"
267
+ @dragstart="dragStart($event, fileItemIndex)"
268
+ @dragenter="dragEnter($event, fileItemIndex)"
269
+ @dragend="dragEnd"
270
+ >
271
+ <!-- 如果是外链 -->
272
+ <span class="n-uploader-query__item__net" v-if="fileItem.isNet && ! fileItem.isNetUploaded">链接</span>
273
+
274
+ <!-- 图标 -->
275
+ <div
276
+ class="n-uploader-query__item__icon"
277
+ :style="{
278
+ width: toPx(currentSize),
279
+ height: toPx(currentSize),
280
+ }"
281
+ >
282
+ <!-- 上传中前 -->
283
+ <q-circular-progress
284
+ class="n-uploader-query__item__icon__icon"
285
+ indeterminate
286
+ rounded
287
+ :size="toPx(currentSize / 1.8)"
288
+ :thickness="0.18"
289
+ v-if="fileItem.status < UPLOAD_STATUS.uploading"
290
+ />
291
+
292
+ <!-- 上传中 -->
293
+ <q-circular-progress
294
+ class="n-uploader-query__item__icon__icon"
295
+ :value="fileItem.progress"
296
+ :size="toPx(currentSize / 1.8)"
297
+ :thickness="0.18"
298
+ show-value
299
+ v-else-if="fileItem.status === UPLOAD_STATUS.uploading"
300
+ >
301
+ <q-icon
302
+ class="cursor-pointer"
303
+ name="pause"
304
+ :size="toPx(currentSize / 3)"
305
+ @click.prevent.stop="uploader.deleteFileItem(fileItem)"
306
+ />
307
+ </q-circular-progress>
308
+
309
+ <!-- 文件图标 -->
310
+ <q-icon
311
+ class="n-uploader-query__item__icon__icon"
312
+ name="description"
313
+ :size="toPx(currentSize / 1.5)"
314
+ v-else-if="type === 'file'"
315
+ />
316
+
317
+ <!-- 播放图标 -->
318
+ <q-icon
319
+ class="n-uploader-query__item__icon__icon cursor-pointer"
320
+ name="play_circle"
321
+ title="播放"
322
+ :size="toPx(currentSize / 1.5)"
323
+ @click.prevent.stop="uploader.play(fileItem)"
324
+ v-else
325
+ />
326
+ </div>
327
+
328
+ <!-- 信息 -->
329
+ <div class="n-uploader-query__item__info" @click="onFileItemClick(fileItem, fileItemIndex)">
330
+ <!-- 标题 -->
331
+ <div class="n-uploader-query__item__info__title ellipsis">{{getFileName(fileItem)}}</div>
332
+ <!-- 错误提示 -->
333
+ <div class="n-uploader-query__item__info__msg--error" v-if="fileItem.status === UPLOAD_STATUS.fail">{{fileItem.msg}}</div>
334
+ </div>
335
+
336
+ <!-- 操作 -->
337
+ <div class="n-uploader-query__item__settings">
338
+
339
+ <!-- 操作插槽-->
340
+ <slot
341
+ name="settings"
342
+ :file="fileItem"
343
+ :index="fileItemIndex"
344
+ />
345
+
346
+ <template v-if="fileItem.status === UPLOAD_STATUS.success">
347
+
348
+ <!-- 复制地址 -->
349
+ <q-icon
350
+ class="n-uploader-query__item__settings__icon cursor-pointer"
351
+ name="content_copy"
352
+ color="white"
353
+ :size="settingsIconSize"
354
+ title="复制地址"
355
+ @click.prevent.stop="uploader.copyUrl(fileItem)"
356
+ v-bind="settingsIconProps"
357
+ />
358
+
359
+ <!-- 修改 -->
360
+ <q-icon
361
+ class="n-uploader-query__item__settings__icon cursor-pointer"
362
+ name="edit"
363
+ color="white"
364
+ :size="settingsIconSize"
365
+ title="修改"
366
+ v-bind="settingsIconProps"
367
+ v-if="! noEdit && ! disable && ! readonly && ! fileItem.isNet"
368
+ >
369
+ <q-popup-edit
370
+ :model-value="fileItem.title"
371
+ buttons
372
+ label-set="保存"
373
+ @save="uploader.editFileTitle($event, fileItem)"
374
+ v-slot="scope"
375
+ >
376
+ <q-input
377
+ v-model="scope.value"
378
+ dense
379
+ autofocus
380
+ counter
381
+ :maxlength="50"
382
+ @keyup.enter="scope.set"
383
+ >
384
+ <template v-slot:append>
385
+ <span class="text-subtitle2 text-weight-bold">.{{fileItem.ext}}</span>
386
+ </template>
387
+ </q-input>
388
+ </q-popup-edit>
389
+ </q-icon>
390
+ </template>
391
+
392
+ <!-- 删除 -->
393
+ <q-icon
394
+ class="n-uploader-query__item__settings__icon cursor-pointer"
395
+ name="close"
396
+ color="white"
397
+ :size="settingsIconSize"
398
+ title="删除"
399
+ @click.prevent.stop="uploader.deleteFileItem(fileItem)"
400
+ v-bind="settingsIconProps"
401
+ v-if="! noDelete && ! disable && ! readonly"
402
+ />
403
+ </div>
404
+
405
+ <!-- 插槽-->
406
+ <slot
407
+ name="item-append"
408
+ :file="fileItem"
409
+ :index="fileItemIndex"
410
+ />
411
+
412
+ </div>
413
+ </template>
414
+ </n-dragger>
415
+ </div>
416
+ </template>
417
+
418
+ <script>
419
+ import { computed, inject } from 'vue'
420
+ import { useQuasar } from 'quasar'
421
+
422
+ import $n_has from 'lodash/has'
423
+ import $n_get from 'lodash/get'
424
+
425
+ import $n_px from '@netang/utils/px'
426
+ import $n_forEach from '@netang/utils/forEach'
427
+ import $n_isValidArray from '@netang/utils/isValidArray'
428
+ import $n_isValidString from '@netang/utils/isValidString'
429
+
430
+ import $n_getImage from '../../utils/getImage'
431
+ import $n_previewImage from '../../utils/previewImage'
432
+
433
+ import NDragger from '../dragger'
434
+
435
+ import { NUploaderKey } from '../../utils/symbols'
436
+
437
+ import {
438
+ // 上传状态
439
+ UPLOAD_STATUS,
440
+ } from '../../utils/useUploader'
441
+
442
+ export default {
443
+
444
+ /**
445
+ * 标识
446
+ */
447
+ name: 'NUploaderQuery',
448
+
449
+ /**
450
+ * 组件
451
+ */
452
+ components: {
453
+ NDragger,
454
+ },
455
+
456
+ /**
457
+ * 声明属性
458
+ */
459
+ props: {
460
+ // 单个文件类名
461
+ itemClass: [String, Array, Object],
462
+ // 单个文件样式
463
+ itemStyle: [String, Array, Object],
464
+ // 按钮类型, 可选值 square button
465
+ buttonType: {
466
+ type: String,
467
+ validator: v => [ 'square', 'button' ].includes(v),
468
+ },
469
+ // 按钮文字
470
+ buttonText: String,
471
+ // 按钮声明属性
472
+ buttonProps: Object,
473
+ // 图片/按钮/文件 尺寸
474
+ size: Number,
475
+ // 是否开启拖拽
476
+ drag: {
477
+ type: Boolean,
478
+ default: true,
479
+ },
480
+ // 是否禁用
481
+ disable: Boolean,
482
+ // 是否只读
483
+ readonly: Boolean,
484
+ // 是否隐藏按钮
485
+ noButton: Boolean,
486
+ // 是否隐藏预览按钮
487
+ noPreview: Boolean,
488
+ // 是否隐藏修改按钮
489
+ noEdit: Boolean,
490
+ // 是否隐藏删除按钮
491
+ noDelete: Boolean,
492
+ // 自动显示方块按钮
493
+ autoShowSquareButton: Boolean,
494
+ // 方块按钮在右边显示
495
+ rightSquareButton: Boolean,
496
+ // 设置图标尺寸
497
+ settingsIconSize: {
498
+ type: String,
499
+ default: 'xs',
500
+ },
501
+ // 设置图标传参
502
+ settingsIconProps: Object,
503
+ // 是否显示上传网络外链按钮
504
+ showUploadNetButton: Boolean,
505
+ },
506
+
507
+ /**
508
+ * 声明事件
509
+ */
510
+ emits: [
511
+ 'update:modelValue',
512
+ 'itemClick',
513
+ ],
514
+
515
+ /**
516
+ * 组合式
517
+ */
518
+ setup(props, { emit }) {
519
+
520
+ // ==========【数据】============================================================================================
521
+
522
+ // quasar 对象
523
+ const $q = useQuasar()
524
+
525
+ // 获取上传器注入数据
526
+ const {
527
+ // 声明属性
528
+ props: uploaderProps,
529
+ // 上传器
530
+ uploader,
531
+ // 文件队列
532
+ query,
533
+ } = inject(NUploaderKey)
534
+
535
+ // ==========【计算属性】=========================================================================================
536
+
537
+ /**
538
+ * 当前上传文件队列
539
+ */
540
+ const currentQuery = computed(function () {
541
+
542
+ // 如果不是图片
543
+ if (uploaderProps.type !== 'image') {
544
+ if ($n_isValidArray(query.value)) {
545
+ return query.value
546
+ }
547
+ return []
548
+ }
549
+
550
+ const lists = []
551
+
552
+ $n_forEach(query.value, function (fileItem) {
553
+ const newItem = Object.assign({}, fileItem)
554
+
555
+ let src = ''
556
+ let preview_src = ''
557
+
558
+ if ($n_has(fileItem, '__img')) {
559
+ src = fileItem.__img
560
+ preview_src = src
561
+ } else if ($n_isValidString(fileItem.hash)) {
562
+ src = $n_getImage(fileItem.hash, { w: $q.platform.is.mobile ? currentSize.value * 2 : currentSize.value })
563
+ if (src) {
564
+ // 预览地址
565
+ preview_src = fileItem.hash
566
+ }
567
+ }
568
+
569
+ lists.push(Object.assign(newItem, {
570
+ // 图片地址
571
+ src,
572
+ // 预览地址
573
+ preview_src,
574
+ }))
575
+ })
576
+
577
+ return lists
578
+ })
579
+
580
+
581
+ /**
582
+ * 当前是否开启拖拽
583
+ */
584
+ const currentDrag = computed(function() {
585
+ return props.drag
586
+ && query.value.length > 1
587
+ && ! props.readonly
588
+ && ! props.disable
589
+ })
590
+
591
+ /**
592
+ * 当前按钮类型
593
+ */
594
+ const currentButtonType = computed(function () {
595
+ if (props.buttonType) {
596
+ return props.buttonType
597
+ }
598
+ return uploaderProps.type === 'image' ? 'square' : 'button'
599
+ })
600
+
601
+ /**
602
+ * 当前尺寸
603
+ */
604
+ const currentSize = computed(function () {
605
+ if (props.size) {
606
+ return props.size
607
+ }
608
+ return uploaderProps.type === 'image' ? 70 : 50
609
+ })
610
+
611
+ /**
612
+ * 是否显示方块按钮
613
+ */
614
+ const showSquareButton = computed(function () {
615
+ // 自动显示方块按钮 && 有上传文件限制数量
616
+ return props.autoShowSquareButton && uploaderProps.count > 0 ?
617
+ // 如果 当前上传文件队列数量 < 上传文件限制数量
618
+ currentQuery.value.length < uploaderProps.count
619
+ // 始终显示
620
+ : true
621
+ })
622
+
623
+ // ==========【方法】=============================================================================================
624
+
625
+ /**
626
+ * 预览图片
627
+ */
628
+ function previewImage(startPosition) {
629
+ $n_previewImage({
630
+ // 需要预览的图片 URL 数组
631
+ images: currentQuery.value.map(e => e.preview_src),
632
+ // 图片预览起始位置索引
633
+ startPosition,
634
+ })
635
+ }
636
+
637
+ /**
638
+ * 获取文件名称
639
+ */
640
+ function getFileName(fileItem) {
641
+ return fileItem.title + ($n_get(fileItem, 'ext') ? '.' + fileItem.ext : '')
642
+ }
643
+
644
+ /**
645
+ * 文件点击
646
+ */
647
+ function onFileItemClick(file, index) {
648
+ emit('itemClick', {
649
+ file,
650
+ index,
651
+ })
652
+ }
653
+
654
+ // ==========【返回】=============================================================================================
655
+
656
+ return {
657
+ // 上传状态
658
+ UPLOAD_STATUS,
659
+ // 上传文件类型, 可选值 file image video audio
660
+ type: uploaderProps.type,
661
+ // 上传文件数量(0:不限)
662
+ count: uploaderProps.count,
663
+ // 文件队列
664
+ query,
665
+ // 当前上传文件队列
666
+ currentQuery,
667
+
668
+ // 当前是否开启拖拽
669
+ currentDrag,
670
+ // 当前按钮类型
671
+ currentButtonType,
672
+ // 当前尺寸
673
+ currentSize,
674
+ // 是否显示方块按钮
675
+ showSquareButton,
676
+
677
+ // 上传器
678
+ uploader,
679
+
680
+ // 预览图片
681
+ previewImage,
682
+ // 获取文件名称
683
+ getFileName,
684
+ // 文件点击
685
+ onFileItemClick,
686
+
687
+ toPx: $n_px,
688
+ }
689
+ },
690
+ }
691
+ </script>
692
+
693
+ <style lang="scss">
694
+ @import "@/assets/sass/variables.scss";
695
+
696
+ // 上传器队列
697
+ .n-uploader-query {
698
+
699
+ // 上传按钮
700
+ &__button {
701
+
702
+ // 方块
703
+ &--square {
704
+ display: inline-flex;
705
+ overflow: hidden;
706
+ flex-direction: column;
707
+
708
+ &-button {
709
+ flex: 1;
710
+ display: flex;
711
+ vertical-align: middle;
712
+ border: 1px dashed rgba(var(--n-reverse-color-rgb), 0.2);
713
+ flex-direction: column;
714
+ justify-content: center;
715
+ align-items: center;
716
+ color: rgba(var(--n-reverse-color-rgb), 0.4);
717
+ border-radius: 4px;
718
+ overflow: hidden;
719
+
720
+ &:hover {
721
+ border-color: $primary;
722
+ }
723
+
724
+ // 文字
725
+ &__text {
726
+ font-size: 12px;
727
+ }
728
+ }
729
+ }
730
+
731
+ // 按钮
732
+ &--button {
733
+ + .n-uploader-query__query {
734
+ margin-top: 0;
735
+ }
736
+ }
737
+ }
738
+
739
+ // 上传单个文件
740
+ &__item {
741
+ position: relative;
742
+
743
+ // 开启拖拽
744
+ &[draggable="true"] {
745
+ cursor: move;
746
+ }
747
+
748
+ // 当前拖拽占位元素
749
+ &.ghost {
750
+ &:after {
751
+ content: "";
752
+ position: absolute;
753
+ top: 0;
754
+ left: 0;
755
+ right: 0;
756
+ bottom: 0;
757
+ border: 2px dashed mix(#ffffff, $primary, 40%);
758
+ border-radius: 4px;
759
+ background-color: rgba(255, 255, 255, 0.75);
760
+ }
761
+
762
+ .n-uploader-query__item__inner,
763
+ .n-uploader-query__item__settings {
764
+ display: none;
765
+ }
766
+ }
767
+
768
+ &:hover {
769
+ .n-uploader-query__item__settings {
770
+ visibility: visible;
771
+ }
772
+ }
773
+
774
+ // 单个图片
775
+ &--image {
776
+ border-radius: 4px;
777
+ background-color: rgba(0,0,0,0.1);
778
+ overflow: hidden;
779
+ }
780
+
781
+ // 单个文件
782
+ &--file {
783
+ position: relative;
784
+ width: 300px;
785
+ display: flex;
786
+ flex-direction: row;
787
+ align-items: center;
788
+ border-radius: 4px;
789
+ color: rgba(var(--n-reverse-color-rgb), 0.8);
790
+ background-color: rgba(var(--n-reverse-color-rgb), 0.05);
791
+
792
+ // 图标
793
+ .n-uploader-query__item__icon {
794
+ position: relative;
795
+ display: flex;
796
+ align-items: center;
797
+ justify-content: center;
798
+ z-index: 1;
799
+
800
+ &__icon {
801
+ color: rgba(var(--n-reverse-color-rgb), 0.2);
802
+ }
803
+ }
804
+
805
+ // 信息
806
+ .n-uploader-query__item__info {
807
+ display: flex;
808
+ flex-direction: column;
809
+ line-height: 18px;
810
+
811
+ &__title {
812
+ max-width: 150px;
813
+ }
814
+
815
+ &__msg--error {
816
+ color: $negative;
817
+ }
818
+ }
819
+ }
820
+
821
+ // 外链
822
+ &__net {
823
+ position: absolute;
824
+ bottom: -1px;
825
+ right: -1px;
826
+ color: #ffffff;
827
+ padding: 1px 3px;
828
+ border-radius: 3px;
829
+ background-color: var(--q-primary);
830
+ transform: scale(0.7);
831
+ pointer-events: none;
832
+ }
833
+
834
+ //操作
835
+ &__settings {
836
+ position: absolute;
837
+ top: 5px;
838
+ right: 5px;
839
+ visibility: hidden;
840
+
841
+ &__icon {
842
+ background-color: rgba(0,0,0,0.5) !important;
843
+ color: #ffffff !important;
844
+ border-radius: 50%;
845
+ padding: 5px;
846
+
847
+ + .n-uploader-query__item__settings__icon {
848
+ margin-left: 4px;
849
+ }
850
+
851
+ &:hover {
852
+ background-color: rgba(0,0,0,0.8);
853
+ }
854
+ }
855
+ }
856
+
857
+ // 内容
858
+ &__inner {
859
+
860
+ &__msg {
861
+ margin: 3px;
862
+ padding: 2px 3px;
863
+ line-height: 18px;
864
+ font-size: 12px;
865
+ background-color: rgba(0,0,0,0.6);
866
+ border-radius: 6px;
867
+
868
+ &--error {
869
+ background-color: $warning;
870
+ }
871
+ }
872
+ }
873
+ }
874
+ }
875
+
876
+ @media (max-width: $breakpoint-xs-max){
877
+ // 上传器队列
878
+ .n-uploader-query {
879
+ // 上传单个文件
880
+ &__item {
881
+ // 单个文件
882
+ &--file {
883
+ width: 100%;
884
+ }
885
+ }
886
+
887
+ // 信息
888
+ .n-uploader-query__item__info {
889
+ &__title {
890
+ max-width: 200px;
891
+ }
892
+ }
893
+ }
894
+ }
895
+
896
+ /**
897
+ * 手机版
898
+ */
899
+ body.mobile {
900
+ // 上传器队列
901
+ .n-uploader-query {
902
+ // 上传单个文件
903
+ &__item {
904
+ &__settings {
905
+ visibility: visible;
906
+ }
907
+ }
908
+ }
909
+ }
910
+
911
+ /**
912
+ * 暗黑
913
+ */
914
+ .body--dark {
915
+ .n-uploader-query__item--file {
916
+ .n-uploader-query__item__settings {
917
+ // 图标
918
+ &__icon {
919
+ background-color: rgba(255,255,255, 0.1);
920
+
921
+ &:hover {
922
+ background-color: rgba(255,255,255, 0.2);
923
+ }
924
+ }
925
+ }
926
+ }
927
+ }
928
+ </style>