@netang/quasar 0.1.89 → 0.1.90

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 (250) 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 +1394 -1394
  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 +212 -212
  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 +893 -893
  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 +1 -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 +1435 -1435
  233. package/utils/$render.js +75 -75
  234. package/utils/$search.js +416 -416
  235. package/utils/$table.js +1275 -1275
  236. package/utils/$tree.js +34 -16
  237. package/utils/config.js +57 -57
  238. package/utils/dialog.js +36 -36
  239. package/utils/dict.js +21 -21
  240. package/utils/getData.js +88 -88
  241. package/utils/getFile.js +41 -41
  242. package/utils/getImage.js +176 -176
  243. package/utils/getTime.js +113 -113
  244. package/utils/index.js +65 -65
  245. package/utils/previewImage.js +14 -14
  246. package/utils/timestamp.js +18 -18
  247. package/utils/uploader/qiniu.js +320 -320
  248. package/utils/uploader.js +1625 -1625
  249. package/utils/useFileUrl.js +25 -25
  250. package/utils/useSearch.js +499 -499
@@ -1,893 +1,893 @@
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
- ghost: fileItemIndex === fromIndex,
105
- }"
106
- :draggable="currentDrag"
107
- @mousedown.self="mousedown($event, fileItemIndex)"
108
- @mouseup="dragEnd"
109
- @dragstart="dragStart($event, fileItemIndex)"
110
- @dragenter="dragEnter($event, fileItemIndex)"
111
- @dragend="dragEnd"
112
- >
113
- <n-img
114
- :src="currentQuery[fileItemIndex].src"
115
- :spinner-size="toPx(currentSize / 2)"
116
- :width="toPx(currentSize)"
117
- :height="toPx(currentSize)"
118
- @click="onFileItemClick(fileItem)"
119
- fit="fill"
120
- >
121
- <!-- 如果是外链 -->
122
- <span class="n-uploader-query__item__net" v-if="fileItem.isNet && ! fileItem.isNetUploaded">链接</span>
123
-
124
- <!-- 内容 -->
125
- <div
126
- class="n-uploader-query__item__inner absolute-full flex flex-center no-padding transparent"
127
- :class="{
128
- 'transparent': fileItem.status < UPLOAD_STATUS.success,
129
- }"
130
- v-if="fileItem.status !== UPLOAD_STATUS.success"
131
- >
132
- <!-- 如果上传失败 -->
133
- <div
134
- class="n-uploader-query__item__inner__msg n-uploader-query__item__inner__msg--error"
135
- v-if="fileItem.status === UPLOAD_STATUS.fail"
136
- >{{fileItem.msg}}</div>
137
-
138
- <!-- 上传中前 -->
139
- <q-circular-progress
140
- indeterminate
141
- rounded
142
- :size="toPx(currentSize / 1.5)"
143
- :thickness="0.14"
144
- color="white"
145
- v-if="fileItem.status < UPLOAD_STATUS.uploading"
146
- />
147
-
148
- <!-- 上传中 -->
149
- <q-circular-progress
150
- :value="fileItem.progress"
151
- :size="toPx(currentSize / 1.5)"
152
- :thickness="0.14"
153
- color="white"
154
- track-color="grey-5"
155
- show-value
156
- v-else-if="fileItem.status === UPLOAD_STATUS.uploading"
157
- >
158
- <q-icon
159
- class="cursor-pointer"
160
- name="pause"
161
- :size="toPx(currentSize / 3)"
162
- @click.prevent.stop="uploader.deleteFileItem(fileItem)"
163
- />
164
- </q-circular-progress>
165
- </div>
166
-
167
- <!-- 操作 -->
168
- <div
169
- class="n-uploader-query__item__settings transparent no-padding"
170
- v-if="fileItem.status !== UPLOAD_STATUS.uploading"
171
- >
172
- <!-- 操作插槽-->
173
- <slot
174
- name="settings"
175
- :file="fileItem"
176
- />
177
-
178
- <!-- 预览 -->
179
- <q-icon
180
- class="n-uploader-query__item__settings__icon cursor-pointer"
181
- name="search"
182
- :size="settingsIconSize"
183
- title="预览"
184
- @click.prevent.stop="previewImage(fileItemIndex)"
185
- v-bind="settingsIconProps"
186
- v-if="! noPreview && currentQuery[fileItemIndex].preview_src"
187
- />
188
-
189
- <!-- 删除 -->
190
- <q-icon
191
- class="n-uploader-query__item__settings__icon cursor-pointer"
192
- name="close"
193
- :size="settingsIconSize"
194
- title="删除"
195
- @click.prevent.stop="uploader.deleteFileItem(fileItem)"
196
- v-bind="settingsIconProps"
197
- v-if="! noDelete && ! disable && ! readonly"
198
- />
199
-
200
- </div>
201
- </n-img>
202
- </div>
203
-
204
- <!-- 右边方块按钮 -->
205
- <template v-if="! disable && ! readonly && rightSquareButton">
206
- <slot
207
- name="square-button"
208
- :size="currentSize"
209
- :show="showSquareButton"
210
- v-if="$slots['square-button']"
211
- />
212
- <div
213
- class="n-uploader-query__button--square cursor-pointer"
214
- :style="{
215
- width: toPx(currentSize),
216
- height: toPx(currentSize),
217
- }"
218
- @click="uploader.chooseUpload"
219
- v-show="showSquareButton"
220
- v-else-if="! noButton && currentButtonType === 'square'"
221
- >
222
- <q-icon
223
- name="add"
224
- :size="toPx(currentSize / 2)"
225
- />
226
- <div class="n-uploader-query__button--square__text" v-if="buttonText">{{buttonText}}</div>
227
- </div>
228
- </template>
229
- </template>
230
-
231
- <!-- 上传文件队列 -->
232
- <template v-else>
233
-
234
- <!-- 单个文件 -->
235
- <div
236
- v-for="(fileItem, fileItemIndex) in query"
237
- :key="`query-item-${fileItem.id}`"
238
- class="n-uploader-query__item n-uploader-query__item--file"
239
- :class="{
240
- ghost: fileItemIndex === fromIndex,
241
- }"
242
- :style="{
243
- height: toPx(currentSize),
244
- }"
245
- :draggable="currentDrag"
246
- @mousedown.self="mousedown($event, fileItemIndex)"
247
- @mouseup="dragEnd"
248
- @dragstart="dragStart($event, fileItemIndex)"
249
- @dragenter="dragEnter($event, fileItemIndex)"
250
- @dragend="dragEnd"
251
- >
252
- <!-- 如果是外链 -->
253
- <span class="n-uploader-query__item__net" v-if="fileItem.isNet && ! fileItem.isNetUploaded">链接</span>
254
-
255
- <!-- 图标 -->
256
- <div
257
- class="n-uploader-query__item__icon"
258
- :style="{
259
- width: toPx(currentSize),
260
- height: toPx(currentSize),
261
- }"
262
- >
263
- <!-- 上传中前 -->
264
- <q-circular-progress
265
- class="n-uploader-query__item__icon__icon"
266
- indeterminate
267
- rounded
268
- :size="toPx(currentSize / 1.8)"
269
- :thickness="0.18"
270
- v-if="fileItem.status < UPLOAD_STATUS.uploading"
271
- />
272
-
273
- <!-- 上传中 -->
274
- <q-circular-progress
275
- class="n-uploader-query__item__icon__icon"
276
- :value="fileItem.progress"
277
- :size="toPx(currentSize / 1.8)"
278
- :thickness="0.18"
279
- show-value
280
- v-else-if="fileItem.status === UPLOAD_STATUS.uploading"
281
- >
282
- <q-icon
283
- class="cursor-pointer"
284
- name="pause"
285
- :size="toPx(currentSize / 3)"
286
- @click.prevent.stop="uploader.deleteFileItem(fileItem)"
287
- />
288
- </q-circular-progress>
289
-
290
- <!-- 文件图标 -->
291
- <q-icon
292
- class="n-uploader-query__item__icon__icon"
293
- name="description"
294
- :size="toPx(currentSize / 1.5)"
295
- v-else-if="type === 'file'"
296
- />
297
-
298
- <!-- 播放图标 -->
299
- <q-icon
300
- class="n-uploader-query__item__icon__icon cursor-pointer"
301
- name="play_circle"
302
- title="播放"
303
- :size="toPx(currentSize / 1.5)"
304
- @click.prevent.stop="uploader.play(fileItem)"
305
- v-else
306
- />
307
- </div>
308
-
309
- <!-- 信息 -->
310
- <div class="n-uploader-query__item__info" @click="onFileItemClick(fileItem)">
311
- <!-- 标题 -->
312
- <div class="n-uploader-query__item__info__title ellipsis">{{getFileName(fileItem)}}</div>
313
- <!-- 错误提示 -->
314
- <div class="n-uploader-query__item__info__msg--error" v-if="fileItem.status === UPLOAD_STATUS.fail">{{fileItem.msg}}</div>
315
- </div>
316
-
317
- <!-- 操作 -->
318
- <div class="n-uploader-query__item__settings">
319
-
320
- <!-- 操作插槽-->
321
- <slot
322
- name="settings"
323
- :file="fileItem"
324
- />
325
-
326
- <template v-if="fileItem.status === UPLOAD_STATUS.success">
327
-
328
- <!-- 复制地址 -->
329
- <q-icon
330
- class="n-uploader-query__item__settings__icon cursor-pointer"
331
- name="content_copy"
332
- color="white"
333
- :size="settingsIconSize"
334
- title="复制地址"
335
- @click.prevent.stop="uploader.copyUrl(fileItem)"
336
- v-bind="settingsIconProps"
337
- />
338
-
339
- <!-- 修改 -->
340
- <q-icon
341
- class="n-uploader-query__item__settings__icon cursor-pointer"
342
- name="edit"
343
- color="white"
344
- :size="settingsIconSize"
345
- title="修改"
346
- v-bind="settingsIconProps"
347
- v-if="! noEdit && ! disable && ! readonly && ! fileItem.isNet"
348
- >
349
- <q-popup-edit
350
- :model-value="fileItem.title"
351
- buttons
352
- label-set="保存"
353
- @save="uploader.editFileTitle($event, fileItem)"
354
- v-slot="scope"
355
- >
356
- <q-input
357
- v-model="scope.value"
358
- dense
359
- autofocus
360
- counter
361
- :maxlength="50"
362
- @keyup.enter="scope.set"
363
- >
364
- <template v-slot:append>
365
- <span class="text-subtitle2 text-weight-bold">.{{fileItem.ext}}</span>
366
- </template>
367
- </q-input>
368
- </q-popup-edit>
369
- </q-icon>
370
- </template>
371
-
372
- <!-- 删除 -->
373
- <q-icon
374
- class="n-uploader-query__item__settings__icon cursor-pointer"
375
- name="close"
376
- color="white"
377
- :size="settingsIconSize"
378
- title="删除"
379
- @click.prevent.stop="uploader.deleteFileItem(fileItem)"
380
- v-bind="settingsIconProps"
381
- v-if="! noDelete && ! disable && ! readonly"
382
- />
383
- </div>
384
- </div>
385
- </template>
386
- </n-dragger>
387
- </div>
388
- </template>
389
-
390
- <script>
391
- import { computed, inject } from 'vue'
392
- import { useQuasar } from 'quasar'
393
-
394
- import $n_has from 'lodash/has'
395
- import $n_get from 'lodash/get'
396
-
397
- import $n_px from '@netang/utils/px'
398
- import $n_forEach from '@netang/utils/forEach'
399
- import $n_isValidArray from '@netang/utils/isValidArray'
400
- import $n_isValidString from '@netang/utils/isValidString'
401
-
402
- import $n_getImage from '../../utils/getImage'
403
- import $n_previewImage from '../../utils/previewImage'
404
-
405
- import NDragger from '../dragger'
406
-
407
- import { NUploaderKey } from '../../utils/symbols'
408
-
409
- import {
410
- // 上传状态
411
- UPLOAD_STATUS,
412
- } from '../../utils/useUploader'
413
-
414
- export default {
415
-
416
- /**
417
- * 标识
418
- */
419
- name: 'NUploaderQuery',
420
-
421
- /**
422
- * 组件
423
- */
424
- components: {
425
- NDragger,
426
- },
427
-
428
- /**
429
- * 声明属性
430
- */
431
- props: {
432
- // 按钮类型, 可选值 square button
433
- buttonType: {
434
- type: String,
435
- validator: v => [ 'square', 'button' ].includes(v),
436
- },
437
- // 按钮文字
438
- buttonText: String,
439
- // 按钮声明属性
440
- buttonProps: Object,
441
- // 图片/按钮/文件 尺寸
442
- size: Number,
443
- // 是否开启拖拽
444
- drag: {
445
- type: Boolean,
446
- default: true,
447
- },
448
- // 是否禁用
449
- disable: Boolean,
450
- // 是否只读
451
- readonly: Boolean,
452
- // 是否隐藏按钮
453
- noButton: Boolean,
454
- // 是否隐藏预览按钮
455
- noPreview: Boolean,
456
- // 是否隐藏修改按钮
457
- noEdit: Boolean,
458
- // 是否隐藏删除按钮
459
- noDelete: Boolean,
460
- // 自动显示方块按钮
461
- autoShowSquareButton: Boolean,
462
- // 方块按钮在右边显示
463
- rightSquareButton: Boolean,
464
- // 设置图标尺寸
465
- settingsIconSize: {
466
- type: String,
467
- default: 'xs',
468
- },
469
- // 设置图标传参
470
- settingsIconProps: Object,
471
- // 是否显示上传网络外链按钮
472
- showUploadNetButton: Boolean,
473
- },
474
-
475
- /**
476
- * 声明事件
477
- */
478
- emits: [
479
- 'update:modelValue',
480
- 'itemClick',
481
- ],
482
-
483
- /**
484
- * 组合式
485
- */
486
- setup(props, { emit }) {
487
-
488
- // ==========【数据】============================================================================================
489
-
490
- // quasar 对象
491
- const $q = useQuasar()
492
-
493
- // 获取上传器注入数据
494
- const {
495
- // 声明属性
496
- props: uploaderProps,
497
- // 上传器
498
- uploader,
499
- // 文件队列
500
- query,
501
- } = inject(NUploaderKey)
502
-
503
- // ==========【计算属性】=========================================================================================
504
-
505
- /**
506
- * 当前上传文件队列
507
- */
508
- const currentQuery = computed(function () {
509
-
510
- // 如果不是图片
511
- if (uploaderProps.type !== 'image') {
512
- if ($n_isValidArray(query.value)) {
513
- return query.value
514
- }
515
- return []
516
- }
517
-
518
- const lists = []
519
-
520
- $n_forEach(query.value, function (fileItem) {
521
- const newItem = Object.assign({}, fileItem)
522
-
523
- let src = ''
524
- let preview_src = ''
525
-
526
- if ($n_has(fileItem, '__img')) {
527
- src = fileItem.__img
528
- preview_src = src
529
- } else if ($n_isValidString(fileItem.hash)) {
530
- src = $n_getImage(fileItem.hash, { w: $q.platform.is.mobile ? currentSize.value * 2 : currentSize.value })
531
- if (src) {
532
- // 预览地址
533
- preview_src = fileItem.hash
534
- }
535
- }
536
-
537
- lists.push(Object.assign(newItem, {
538
- // 图片地址
539
- src,
540
- // 预览地址
541
- preview_src,
542
- }))
543
- })
544
-
545
- return lists
546
- })
547
-
548
-
549
- /**
550
- * 当前是否开启拖拽
551
- */
552
- const currentDrag = computed(function() {
553
- return props.drag
554
- && query.value.length > 1
555
- && ! props.readonly
556
- && ! props.disable
557
- })
558
-
559
- /**
560
- * 当前按钮类型
561
- */
562
- const currentButtonType = computed(function () {
563
- if (props.buttonType) {
564
- return props.buttonType
565
- }
566
- return uploaderProps.type === 'image' ? 'square' : 'button'
567
- })
568
-
569
- /**
570
- * 当前尺寸
571
- */
572
- const currentSize = computed(function () {
573
- if (props.size) {
574
- return props.size
575
- }
576
- return uploaderProps.type === 'image' ? 70 : 50
577
- })
578
-
579
- /**
580
- * 是否显示方块按钮
581
- */
582
- const showSquareButton = computed(function () {
583
- // 自动显示方块按钮 && 有上传文件限制数量
584
- return props.autoShowSquareButton && uploaderProps.count > 0 ?
585
- // 如果 当前上传文件队列数量 < 上传文件限制数量
586
- currentQuery.value.length < uploaderProps.count
587
- // 始终显示
588
- : true
589
- })
590
-
591
- // ==========【方法】=============================================================================================
592
-
593
- /**
594
- * 预览图片
595
- */
596
- function previewImage(startPosition) {
597
- $n_previewImage({
598
- // 需要预览的图片 URL 数组
599
- images: currentQuery.value.map(e => e.preview_src),
600
- // 图片预览起始位置索引
601
- startPosition,
602
- })
603
- }
604
-
605
- /**
606
- * 获取文件名称
607
- */
608
- function getFileName(fileItem) {
609
- return fileItem.title + ($n_get(fileItem, 'ext') ? '.' + fileItem.ext : '')
610
- }
611
-
612
- /**
613
- * 文件点击
614
- */
615
- function onFileItemClick(fileItem) {
616
- emit('itemClick', fileItem)
617
- }
618
-
619
- // ==========【返回】=============================================================================================
620
-
621
- return {
622
- // 上传状态
623
- UPLOAD_STATUS,
624
- // 上传文件类型, 可选值 file image video audio
625
- type: uploaderProps.type,
626
- // 上传文件数量(0:不限)
627
- count: uploaderProps.count,
628
- // 文件队列
629
- query,
630
- // 当前上传文件队列
631
- currentQuery,
632
-
633
- // 当前是否开启拖拽
634
- currentDrag,
635
- // 当前按钮类型
636
- currentButtonType,
637
- // 当前尺寸
638
- currentSize,
639
- // 是否显示方块按钮
640
- showSquareButton,
641
-
642
- // 上传器
643
- uploader,
644
-
645
- // 预览图片
646
- previewImage,
647
- // 获取文件名称
648
- getFileName,
649
- // 文件点击
650
- onFileItemClick,
651
-
652
- toPx: $n_px,
653
- }
654
- },
655
- }
656
- </script>
657
-
658
- <style lang="scss">
659
- @import "@/assets/sass/variables.scss";
660
-
661
- // 上传器队列
662
- .n-uploader-query {
663
-
664
- // 上传按钮
665
- &__button {
666
-
667
- // 方块
668
- &--square {
669
- display: inline-flex;
670
- overflow: hidden;
671
- flex-direction: column;
672
-
673
- &-button {
674
- flex: 1;
675
- display: flex;
676
- vertical-align: middle;
677
- border: 1px dashed rgba(var(--n-reverse-color-rgb), 0.2);
678
- flex-direction: column;
679
- justify-content: center;
680
- align-items: center;
681
- color: rgba(var(--n-reverse-color-rgb), 0.4);
682
- border-radius: 4px;
683
- overflow: hidden;
684
-
685
- &:hover {
686
- border-color: $primary;
687
- }
688
-
689
- // 文字
690
- &__text {
691
- font-size: 12px;
692
- }
693
- }
694
- }
695
-
696
- // 按钮
697
- &--button {
698
- + .n-uploader-query__query {
699
- margin-top: 0;
700
- }
701
- }
702
- }
703
-
704
- // 上传单个文件
705
- &__item {
706
- position: relative;
707
-
708
- // 开启拖拽
709
- &[draggable="true"] {
710
- cursor: move;
711
- }
712
-
713
- // 当前拖拽占位元素
714
- &.ghost {
715
- &:after {
716
- content: "";
717
- position: absolute;
718
- top: 0;
719
- left: 0;
720
- right: 0;
721
- bottom: 0;
722
- border: 2px dashed mix(#ffffff, $primary, 40%);
723
- border-radius: 4px;
724
- background-color: rgba(255, 255, 255, 0.75);
725
- }
726
-
727
- .n-uploader-query__item__inner,
728
- .n-uploader-query__item__settings {
729
- display: none;
730
- }
731
- }
732
-
733
- &:hover {
734
- .n-uploader-query__item__settings {
735
- visibility: visible;
736
- }
737
- }
738
-
739
- // 单个图片
740
- &--image {
741
- border-radius: 4px;
742
- background-color: rgba(0,0,0,0.1);
743
- overflow: hidden;
744
- }
745
-
746
- // 单个文件
747
- &--file {
748
- position: relative;
749
- width: 300px;
750
- display: flex;
751
- flex-direction: row;
752
- align-items: center;
753
- border-radius: 4px;
754
- color: rgba(var(--n-reverse-color-rgb), 0.8);
755
- background-color: rgba(var(--n-reverse-color-rgb), 0.05);
756
-
757
- // 图标
758
- .n-uploader-query__item__icon {
759
- position: relative;
760
- display: flex;
761
- align-items: center;
762
- justify-content: center;
763
- z-index: 1;
764
-
765
- &__icon {
766
- color: rgba(var(--n-reverse-color-rgb), 0.2);
767
- }
768
- }
769
-
770
- // 信息
771
- .n-uploader-query__item__info {
772
- display: flex;
773
- flex-direction: column;
774
- line-height: 18px;
775
-
776
- &__title {
777
- max-width: 150px;
778
- }
779
-
780
- &__msg--error {
781
- color: $negative;
782
- }
783
- }
784
- }
785
-
786
- // 外链
787
- &__net {
788
- position: absolute;
789
- bottom: -1px;
790
- right: -1px;
791
- color: #ffffff;
792
- padding: 1px 3px;
793
- border-radius: 3px;
794
- background-color: var(--q-primary);
795
- transform: scale(0.7);
796
- pointer-events: none;
797
- }
798
-
799
- //操作
800
- &__settings {
801
- position: absolute;
802
- top: 5px;
803
- right: 5px;
804
- visibility: hidden;
805
-
806
- &__icon {
807
- background-color: rgba(0,0,0,0.5) !important;
808
- color: #ffffff !important;
809
- border-radius: 50%;
810
- padding: 5px;
811
-
812
- + .n-uploader-query__item__settings__icon {
813
- margin-left: 4px;
814
- }
815
-
816
- &:hover {
817
- background-color: rgba(0,0,0,0.8);
818
- }
819
- }
820
- }
821
-
822
- // 内容
823
- &__inner {
824
-
825
- &__msg {
826
- margin: 3px;
827
- padding: 2px 3px;
828
- line-height: 18px;
829
- font-size: 12px;
830
- background-color: rgba(0,0,0,0.6);
831
- border-radius: 6px;
832
-
833
- &--error {
834
- background-color: $warning;
835
- }
836
- }
837
- }
838
- }
839
- }
840
-
841
- @media (max-width: $breakpoint-xs-max){
842
- // 上传器队列
843
- .n-uploader-query {
844
- // 上传单个文件
845
- &__item {
846
- // 单个文件
847
- &--file {
848
- width: 100%;
849
- }
850
- }
851
-
852
- // 信息
853
- .n-uploader-query__item__info {
854
- &__title {
855
- max-width: 200px;
856
- }
857
- }
858
- }
859
- }
860
-
861
- /**
862
- * 手机版
863
- */
864
- body.mobile {
865
- // 上传器队列
866
- .n-uploader-query {
867
- // 上传单个文件
868
- &__item {
869
- &__settings {
870
- visibility: visible;
871
- }
872
- }
873
- }
874
- }
875
-
876
- /**
877
- * 暗黑
878
- */
879
- .body--dark {
880
- .n-uploader-query__item--file {
881
- .n-uploader-query__item__settings {
882
- // 图标
883
- &__icon {
884
- background-color: rgba(255,255,255, 0.1);
885
-
886
- &:hover {
887
- background-color: rgba(255,255,255, 0.2);
888
- }
889
- }
890
- }
891
- }
892
- }
893
- </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
+ ghost: fileItemIndex === fromIndex,
105
+ }"
106
+ :draggable="currentDrag"
107
+ @mousedown.self="mousedown($event, fileItemIndex)"
108
+ @mouseup="dragEnd"
109
+ @dragstart="dragStart($event, fileItemIndex)"
110
+ @dragenter="dragEnter($event, fileItemIndex)"
111
+ @dragend="dragEnd"
112
+ >
113
+ <n-img
114
+ :src="currentQuery[fileItemIndex].src"
115
+ :spinner-size="toPx(currentSize / 2)"
116
+ :width="toPx(currentSize)"
117
+ :height="toPx(currentSize)"
118
+ @click="onFileItemClick(fileItem)"
119
+ fit="fill"
120
+ >
121
+ <!-- 如果是外链 -->
122
+ <span class="n-uploader-query__item__net" v-if="fileItem.isNet && ! fileItem.isNetUploaded">链接</span>
123
+
124
+ <!-- 内容 -->
125
+ <div
126
+ class="n-uploader-query__item__inner absolute-full flex flex-center no-padding transparent"
127
+ :class="{
128
+ 'transparent': fileItem.status < UPLOAD_STATUS.success,
129
+ }"
130
+ v-if="fileItem.status !== UPLOAD_STATUS.success"
131
+ >
132
+ <!-- 如果上传失败 -->
133
+ <div
134
+ class="n-uploader-query__item__inner__msg n-uploader-query__item__inner__msg--error"
135
+ v-if="fileItem.status === UPLOAD_STATUS.fail"
136
+ >{{fileItem.msg}}</div>
137
+
138
+ <!-- 上传中前 -->
139
+ <q-circular-progress
140
+ indeterminate
141
+ rounded
142
+ :size="toPx(currentSize / 1.5)"
143
+ :thickness="0.14"
144
+ color="white"
145
+ v-if="fileItem.status < UPLOAD_STATUS.uploading"
146
+ />
147
+
148
+ <!-- 上传中 -->
149
+ <q-circular-progress
150
+ :value="fileItem.progress"
151
+ :size="toPx(currentSize / 1.5)"
152
+ :thickness="0.14"
153
+ color="white"
154
+ track-color="grey-5"
155
+ show-value
156
+ v-else-if="fileItem.status === UPLOAD_STATUS.uploading"
157
+ >
158
+ <q-icon
159
+ class="cursor-pointer"
160
+ name="pause"
161
+ :size="toPx(currentSize / 3)"
162
+ @click.prevent.stop="uploader.deleteFileItem(fileItem)"
163
+ />
164
+ </q-circular-progress>
165
+ </div>
166
+
167
+ <!-- 操作 -->
168
+ <div
169
+ class="n-uploader-query__item__settings transparent no-padding"
170
+ v-if="fileItem.status !== UPLOAD_STATUS.uploading"
171
+ >
172
+ <!-- 操作插槽-->
173
+ <slot
174
+ name="settings"
175
+ :file="fileItem"
176
+ />
177
+
178
+ <!-- 预览 -->
179
+ <q-icon
180
+ class="n-uploader-query__item__settings__icon cursor-pointer"
181
+ name="search"
182
+ :size="settingsIconSize"
183
+ title="预览"
184
+ @click.prevent.stop="previewImage(fileItemIndex)"
185
+ v-bind="settingsIconProps"
186
+ v-if="! noPreview && currentQuery[fileItemIndex].preview_src"
187
+ />
188
+
189
+ <!-- 删除 -->
190
+ <q-icon
191
+ class="n-uploader-query__item__settings__icon cursor-pointer"
192
+ name="close"
193
+ :size="settingsIconSize"
194
+ title="删除"
195
+ @click.prevent.stop="uploader.deleteFileItem(fileItem)"
196
+ v-bind="settingsIconProps"
197
+ v-if="! noDelete && ! disable && ! readonly"
198
+ />
199
+
200
+ </div>
201
+ </n-img>
202
+ </div>
203
+
204
+ <!-- 右边方块按钮 -->
205
+ <template v-if="! disable && ! readonly && rightSquareButton">
206
+ <slot
207
+ name="square-button"
208
+ :size="currentSize"
209
+ :show="showSquareButton"
210
+ v-if="$slots['square-button']"
211
+ />
212
+ <div
213
+ class="n-uploader-query__button--square cursor-pointer"
214
+ :style="{
215
+ width: toPx(currentSize),
216
+ height: toPx(currentSize),
217
+ }"
218
+ @click="uploader.chooseUpload"
219
+ v-show="showSquareButton"
220
+ v-else-if="! noButton && currentButtonType === 'square'"
221
+ >
222
+ <q-icon
223
+ name="add"
224
+ :size="toPx(currentSize / 2)"
225
+ />
226
+ <div class="n-uploader-query__button--square__text" v-if="buttonText">{{buttonText}}</div>
227
+ </div>
228
+ </template>
229
+ </template>
230
+
231
+ <!-- 上传文件队列 -->
232
+ <template v-else>
233
+
234
+ <!-- 单个文件 -->
235
+ <div
236
+ v-for="(fileItem, fileItemIndex) in query"
237
+ :key="`query-item-${fileItem.id}`"
238
+ class="n-uploader-query__item n-uploader-query__item--file"
239
+ :class="{
240
+ ghost: fileItemIndex === fromIndex,
241
+ }"
242
+ :style="{
243
+ height: toPx(currentSize),
244
+ }"
245
+ :draggable="currentDrag"
246
+ @mousedown.self="mousedown($event, fileItemIndex)"
247
+ @mouseup="dragEnd"
248
+ @dragstart="dragStart($event, fileItemIndex)"
249
+ @dragenter="dragEnter($event, fileItemIndex)"
250
+ @dragend="dragEnd"
251
+ >
252
+ <!-- 如果是外链 -->
253
+ <span class="n-uploader-query__item__net" v-if="fileItem.isNet && ! fileItem.isNetUploaded">链接</span>
254
+
255
+ <!-- 图标 -->
256
+ <div
257
+ class="n-uploader-query__item__icon"
258
+ :style="{
259
+ width: toPx(currentSize),
260
+ height: toPx(currentSize),
261
+ }"
262
+ >
263
+ <!-- 上传中前 -->
264
+ <q-circular-progress
265
+ class="n-uploader-query__item__icon__icon"
266
+ indeterminate
267
+ rounded
268
+ :size="toPx(currentSize / 1.8)"
269
+ :thickness="0.18"
270
+ v-if="fileItem.status < UPLOAD_STATUS.uploading"
271
+ />
272
+
273
+ <!-- 上传中 -->
274
+ <q-circular-progress
275
+ class="n-uploader-query__item__icon__icon"
276
+ :value="fileItem.progress"
277
+ :size="toPx(currentSize / 1.8)"
278
+ :thickness="0.18"
279
+ show-value
280
+ v-else-if="fileItem.status === UPLOAD_STATUS.uploading"
281
+ >
282
+ <q-icon
283
+ class="cursor-pointer"
284
+ name="pause"
285
+ :size="toPx(currentSize / 3)"
286
+ @click.prevent.stop="uploader.deleteFileItem(fileItem)"
287
+ />
288
+ </q-circular-progress>
289
+
290
+ <!-- 文件图标 -->
291
+ <q-icon
292
+ class="n-uploader-query__item__icon__icon"
293
+ name="description"
294
+ :size="toPx(currentSize / 1.5)"
295
+ v-else-if="type === 'file'"
296
+ />
297
+
298
+ <!-- 播放图标 -->
299
+ <q-icon
300
+ class="n-uploader-query__item__icon__icon cursor-pointer"
301
+ name="play_circle"
302
+ title="播放"
303
+ :size="toPx(currentSize / 1.5)"
304
+ @click.prevent.stop="uploader.play(fileItem)"
305
+ v-else
306
+ />
307
+ </div>
308
+
309
+ <!-- 信息 -->
310
+ <div class="n-uploader-query__item__info" @click="onFileItemClick(fileItem)">
311
+ <!-- 标题 -->
312
+ <div class="n-uploader-query__item__info__title ellipsis">{{getFileName(fileItem)}}</div>
313
+ <!-- 错误提示 -->
314
+ <div class="n-uploader-query__item__info__msg--error" v-if="fileItem.status === UPLOAD_STATUS.fail">{{fileItem.msg}}</div>
315
+ </div>
316
+
317
+ <!-- 操作 -->
318
+ <div class="n-uploader-query__item__settings">
319
+
320
+ <!-- 操作插槽-->
321
+ <slot
322
+ name="settings"
323
+ :file="fileItem"
324
+ />
325
+
326
+ <template v-if="fileItem.status === UPLOAD_STATUS.success">
327
+
328
+ <!-- 复制地址 -->
329
+ <q-icon
330
+ class="n-uploader-query__item__settings__icon cursor-pointer"
331
+ name="content_copy"
332
+ color="white"
333
+ :size="settingsIconSize"
334
+ title="复制地址"
335
+ @click.prevent.stop="uploader.copyUrl(fileItem)"
336
+ v-bind="settingsIconProps"
337
+ />
338
+
339
+ <!-- 修改 -->
340
+ <q-icon
341
+ class="n-uploader-query__item__settings__icon cursor-pointer"
342
+ name="edit"
343
+ color="white"
344
+ :size="settingsIconSize"
345
+ title="修改"
346
+ v-bind="settingsIconProps"
347
+ v-if="! noEdit && ! disable && ! readonly && ! fileItem.isNet"
348
+ >
349
+ <q-popup-edit
350
+ :model-value="fileItem.title"
351
+ buttons
352
+ label-set="保存"
353
+ @save="uploader.editFileTitle($event, fileItem)"
354
+ v-slot="scope"
355
+ >
356
+ <q-input
357
+ v-model="scope.value"
358
+ dense
359
+ autofocus
360
+ counter
361
+ :maxlength="50"
362
+ @keyup.enter="scope.set"
363
+ >
364
+ <template v-slot:append>
365
+ <span class="text-subtitle2 text-weight-bold">.{{fileItem.ext}}</span>
366
+ </template>
367
+ </q-input>
368
+ </q-popup-edit>
369
+ </q-icon>
370
+ </template>
371
+
372
+ <!-- 删除 -->
373
+ <q-icon
374
+ class="n-uploader-query__item__settings__icon cursor-pointer"
375
+ name="close"
376
+ color="white"
377
+ :size="settingsIconSize"
378
+ title="删除"
379
+ @click.prevent.stop="uploader.deleteFileItem(fileItem)"
380
+ v-bind="settingsIconProps"
381
+ v-if="! noDelete && ! disable && ! readonly"
382
+ />
383
+ </div>
384
+ </div>
385
+ </template>
386
+ </n-dragger>
387
+ </div>
388
+ </template>
389
+
390
+ <script>
391
+ import { computed, inject } from 'vue'
392
+ import { useQuasar } from 'quasar'
393
+
394
+ import $n_has from 'lodash/has'
395
+ import $n_get from 'lodash/get'
396
+
397
+ import $n_px from '@netang/utils/px'
398
+ import $n_forEach from '@netang/utils/forEach'
399
+ import $n_isValidArray from '@netang/utils/isValidArray'
400
+ import $n_isValidString from '@netang/utils/isValidString'
401
+
402
+ import $n_getImage from '../../utils/getImage'
403
+ import $n_previewImage from '../../utils/previewImage'
404
+
405
+ import NDragger from '../dragger'
406
+
407
+ import { NUploaderKey } from '../../utils/symbols'
408
+
409
+ import {
410
+ // 上传状态
411
+ UPLOAD_STATUS,
412
+ } from '../../utils/useUploader'
413
+
414
+ export default {
415
+
416
+ /**
417
+ * 标识
418
+ */
419
+ name: 'NUploaderQuery',
420
+
421
+ /**
422
+ * 组件
423
+ */
424
+ components: {
425
+ NDragger,
426
+ },
427
+
428
+ /**
429
+ * 声明属性
430
+ */
431
+ props: {
432
+ // 按钮类型, 可选值 square button
433
+ buttonType: {
434
+ type: String,
435
+ validator: v => [ 'square', 'button' ].includes(v),
436
+ },
437
+ // 按钮文字
438
+ buttonText: String,
439
+ // 按钮声明属性
440
+ buttonProps: Object,
441
+ // 图片/按钮/文件 尺寸
442
+ size: Number,
443
+ // 是否开启拖拽
444
+ drag: {
445
+ type: Boolean,
446
+ default: true,
447
+ },
448
+ // 是否禁用
449
+ disable: Boolean,
450
+ // 是否只读
451
+ readonly: Boolean,
452
+ // 是否隐藏按钮
453
+ noButton: Boolean,
454
+ // 是否隐藏预览按钮
455
+ noPreview: Boolean,
456
+ // 是否隐藏修改按钮
457
+ noEdit: Boolean,
458
+ // 是否隐藏删除按钮
459
+ noDelete: Boolean,
460
+ // 自动显示方块按钮
461
+ autoShowSquareButton: Boolean,
462
+ // 方块按钮在右边显示
463
+ rightSquareButton: Boolean,
464
+ // 设置图标尺寸
465
+ settingsIconSize: {
466
+ type: String,
467
+ default: 'xs',
468
+ },
469
+ // 设置图标传参
470
+ settingsIconProps: Object,
471
+ // 是否显示上传网络外链按钮
472
+ showUploadNetButton: Boolean,
473
+ },
474
+
475
+ /**
476
+ * 声明事件
477
+ */
478
+ emits: [
479
+ 'update:modelValue',
480
+ 'itemClick',
481
+ ],
482
+
483
+ /**
484
+ * 组合式
485
+ */
486
+ setup(props, { emit }) {
487
+
488
+ // ==========【数据】============================================================================================
489
+
490
+ // quasar 对象
491
+ const $q = useQuasar()
492
+
493
+ // 获取上传器注入数据
494
+ const {
495
+ // 声明属性
496
+ props: uploaderProps,
497
+ // 上传器
498
+ uploader,
499
+ // 文件队列
500
+ query,
501
+ } = inject(NUploaderKey)
502
+
503
+ // ==========【计算属性】=========================================================================================
504
+
505
+ /**
506
+ * 当前上传文件队列
507
+ */
508
+ const currentQuery = computed(function () {
509
+
510
+ // 如果不是图片
511
+ if (uploaderProps.type !== 'image') {
512
+ if ($n_isValidArray(query.value)) {
513
+ return query.value
514
+ }
515
+ return []
516
+ }
517
+
518
+ const lists = []
519
+
520
+ $n_forEach(query.value, function (fileItem) {
521
+ const newItem = Object.assign({}, fileItem)
522
+
523
+ let src = ''
524
+ let preview_src = ''
525
+
526
+ if ($n_has(fileItem, '__img')) {
527
+ src = fileItem.__img
528
+ preview_src = src
529
+ } else if ($n_isValidString(fileItem.hash)) {
530
+ src = $n_getImage(fileItem.hash, { w: $q.platform.is.mobile ? currentSize.value * 2 : currentSize.value })
531
+ if (src) {
532
+ // 预览地址
533
+ preview_src = fileItem.hash
534
+ }
535
+ }
536
+
537
+ lists.push(Object.assign(newItem, {
538
+ // 图片地址
539
+ src,
540
+ // 预览地址
541
+ preview_src,
542
+ }))
543
+ })
544
+
545
+ return lists
546
+ })
547
+
548
+
549
+ /**
550
+ * 当前是否开启拖拽
551
+ */
552
+ const currentDrag = computed(function() {
553
+ return props.drag
554
+ && query.value.length > 1
555
+ && ! props.readonly
556
+ && ! props.disable
557
+ })
558
+
559
+ /**
560
+ * 当前按钮类型
561
+ */
562
+ const currentButtonType = computed(function () {
563
+ if (props.buttonType) {
564
+ return props.buttonType
565
+ }
566
+ return uploaderProps.type === 'image' ? 'square' : 'button'
567
+ })
568
+
569
+ /**
570
+ * 当前尺寸
571
+ */
572
+ const currentSize = computed(function () {
573
+ if (props.size) {
574
+ return props.size
575
+ }
576
+ return uploaderProps.type === 'image' ? 70 : 50
577
+ })
578
+
579
+ /**
580
+ * 是否显示方块按钮
581
+ */
582
+ const showSquareButton = computed(function () {
583
+ // 自动显示方块按钮 && 有上传文件限制数量
584
+ return props.autoShowSquareButton && uploaderProps.count > 0 ?
585
+ // 如果 当前上传文件队列数量 < 上传文件限制数量
586
+ currentQuery.value.length < uploaderProps.count
587
+ // 始终显示
588
+ : true
589
+ })
590
+
591
+ // ==========【方法】=============================================================================================
592
+
593
+ /**
594
+ * 预览图片
595
+ */
596
+ function previewImage(startPosition) {
597
+ $n_previewImage({
598
+ // 需要预览的图片 URL 数组
599
+ images: currentQuery.value.map(e => e.preview_src),
600
+ // 图片预览起始位置索引
601
+ startPosition,
602
+ })
603
+ }
604
+
605
+ /**
606
+ * 获取文件名称
607
+ */
608
+ function getFileName(fileItem) {
609
+ return fileItem.title + ($n_get(fileItem, 'ext') ? '.' + fileItem.ext : '')
610
+ }
611
+
612
+ /**
613
+ * 文件点击
614
+ */
615
+ function onFileItemClick(fileItem) {
616
+ emit('itemClick', fileItem)
617
+ }
618
+
619
+ // ==========【返回】=============================================================================================
620
+
621
+ return {
622
+ // 上传状态
623
+ UPLOAD_STATUS,
624
+ // 上传文件类型, 可选值 file image video audio
625
+ type: uploaderProps.type,
626
+ // 上传文件数量(0:不限)
627
+ count: uploaderProps.count,
628
+ // 文件队列
629
+ query,
630
+ // 当前上传文件队列
631
+ currentQuery,
632
+
633
+ // 当前是否开启拖拽
634
+ currentDrag,
635
+ // 当前按钮类型
636
+ currentButtonType,
637
+ // 当前尺寸
638
+ currentSize,
639
+ // 是否显示方块按钮
640
+ showSquareButton,
641
+
642
+ // 上传器
643
+ uploader,
644
+
645
+ // 预览图片
646
+ previewImage,
647
+ // 获取文件名称
648
+ getFileName,
649
+ // 文件点击
650
+ onFileItemClick,
651
+
652
+ toPx: $n_px,
653
+ }
654
+ },
655
+ }
656
+ </script>
657
+
658
+ <style lang="scss">
659
+ @import "@/assets/sass/variables.scss";
660
+
661
+ // 上传器队列
662
+ .n-uploader-query {
663
+
664
+ // 上传按钮
665
+ &__button {
666
+
667
+ // 方块
668
+ &--square {
669
+ display: inline-flex;
670
+ overflow: hidden;
671
+ flex-direction: column;
672
+
673
+ &-button {
674
+ flex: 1;
675
+ display: flex;
676
+ vertical-align: middle;
677
+ border: 1px dashed rgba(var(--n-reverse-color-rgb), 0.2);
678
+ flex-direction: column;
679
+ justify-content: center;
680
+ align-items: center;
681
+ color: rgba(var(--n-reverse-color-rgb), 0.4);
682
+ border-radius: 4px;
683
+ overflow: hidden;
684
+
685
+ &:hover {
686
+ border-color: $primary;
687
+ }
688
+
689
+ // 文字
690
+ &__text {
691
+ font-size: 12px;
692
+ }
693
+ }
694
+ }
695
+
696
+ // 按钮
697
+ &--button {
698
+ + .n-uploader-query__query {
699
+ margin-top: 0;
700
+ }
701
+ }
702
+ }
703
+
704
+ // 上传单个文件
705
+ &__item {
706
+ position: relative;
707
+
708
+ // 开启拖拽
709
+ &[draggable="true"] {
710
+ cursor: move;
711
+ }
712
+
713
+ // 当前拖拽占位元素
714
+ &.ghost {
715
+ &:after {
716
+ content: "";
717
+ position: absolute;
718
+ top: 0;
719
+ left: 0;
720
+ right: 0;
721
+ bottom: 0;
722
+ border: 2px dashed mix(#ffffff, $primary, 40%);
723
+ border-radius: 4px;
724
+ background-color: rgba(255, 255, 255, 0.75);
725
+ }
726
+
727
+ .n-uploader-query__item__inner,
728
+ .n-uploader-query__item__settings {
729
+ display: none;
730
+ }
731
+ }
732
+
733
+ &:hover {
734
+ .n-uploader-query__item__settings {
735
+ visibility: visible;
736
+ }
737
+ }
738
+
739
+ // 单个图片
740
+ &--image {
741
+ border-radius: 4px;
742
+ background-color: rgba(0,0,0,0.1);
743
+ overflow: hidden;
744
+ }
745
+
746
+ // 单个文件
747
+ &--file {
748
+ position: relative;
749
+ width: 300px;
750
+ display: flex;
751
+ flex-direction: row;
752
+ align-items: center;
753
+ border-radius: 4px;
754
+ color: rgba(var(--n-reverse-color-rgb), 0.8);
755
+ background-color: rgba(var(--n-reverse-color-rgb), 0.05);
756
+
757
+ // 图标
758
+ .n-uploader-query__item__icon {
759
+ position: relative;
760
+ display: flex;
761
+ align-items: center;
762
+ justify-content: center;
763
+ z-index: 1;
764
+
765
+ &__icon {
766
+ color: rgba(var(--n-reverse-color-rgb), 0.2);
767
+ }
768
+ }
769
+
770
+ // 信息
771
+ .n-uploader-query__item__info {
772
+ display: flex;
773
+ flex-direction: column;
774
+ line-height: 18px;
775
+
776
+ &__title {
777
+ max-width: 150px;
778
+ }
779
+
780
+ &__msg--error {
781
+ color: $negative;
782
+ }
783
+ }
784
+ }
785
+
786
+ // 外链
787
+ &__net {
788
+ position: absolute;
789
+ bottom: -1px;
790
+ right: -1px;
791
+ color: #ffffff;
792
+ padding: 1px 3px;
793
+ border-radius: 3px;
794
+ background-color: var(--q-primary);
795
+ transform: scale(0.7);
796
+ pointer-events: none;
797
+ }
798
+
799
+ //操作
800
+ &__settings {
801
+ position: absolute;
802
+ top: 5px;
803
+ right: 5px;
804
+ visibility: hidden;
805
+
806
+ &__icon {
807
+ background-color: rgba(0,0,0,0.5) !important;
808
+ color: #ffffff !important;
809
+ border-radius: 50%;
810
+ padding: 5px;
811
+
812
+ + .n-uploader-query__item__settings__icon {
813
+ margin-left: 4px;
814
+ }
815
+
816
+ &:hover {
817
+ background-color: rgba(0,0,0,0.8);
818
+ }
819
+ }
820
+ }
821
+
822
+ // 内容
823
+ &__inner {
824
+
825
+ &__msg {
826
+ margin: 3px;
827
+ padding: 2px 3px;
828
+ line-height: 18px;
829
+ font-size: 12px;
830
+ background-color: rgba(0,0,0,0.6);
831
+ border-radius: 6px;
832
+
833
+ &--error {
834
+ background-color: $warning;
835
+ }
836
+ }
837
+ }
838
+ }
839
+ }
840
+
841
+ @media (max-width: $breakpoint-xs-max){
842
+ // 上传器队列
843
+ .n-uploader-query {
844
+ // 上传单个文件
845
+ &__item {
846
+ // 单个文件
847
+ &--file {
848
+ width: 100%;
849
+ }
850
+ }
851
+
852
+ // 信息
853
+ .n-uploader-query__item__info {
854
+ &__title {
855
+ max-width: 200px;
856
+ }
857
+ }
858
+ }
859
+ }
860
+
861
+ /**
862
+ * 手机版
863
+ */
864
+ body.mobile {
865
+ // 上传器队列
866
+ .n-uploader-query {
867
+ // 上传单个文件
868
+ &__item {
869
+ &__settings {
870
+ visibility: visible;
871
+ }
872
+ }
873
+ }
874
+ }
875
+
876
+ /**
877
+ * 暗黑
878
+ */
879
+ .body--dark {
880
+ .n-uploader-query__item--file {
881
+ .n-uploader-query__item__settings {
882
+ // 图标
883
+ &__icon {
884
+ background-color: rgba(255,255,255, 0.1);
885
+
886
+ &:hover {
887
+ background-color: rgba(255,255,255, 0.2);
888
+ }
889
+ }
890
+ }
891
+ }
892
+ }
893
+ </style>