index.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. <template>
  2. <view>
  3. <sjs src="../wxs/utils.sjs" module="utils" />
  4. <view class="van-uploader">
  5. <view class="van-uploader__wrapper">
  6. <!-- 预览样式 -->
  7. <view :wx:if="previewImage" :wx:for="lists" wx:key="index" class="van-uploader__preview">
  8. <image
  9. :wx:if="item.isImage"
  10. :mode="imageFit"
  11. :src="item.url || item.path"
  12. :alt="item.name || '图片' + index"
  13. class="van-uploader__preview-image"
  14. :style="'width: ' + computedPreviewSize + '; height: ' + computedPreviewSize + ';'"
  15. :data-url="item.url || item.path"
  16. @tap="doPreviewImage"
  17. />
  18. <view wx:else class="van-uploader__file" :style="'width: ' + computedPreviewSize + '; height: ' + computedPreviewSize + ';'">
  19. <van-icon name="description" class="van-uploader__file-icon" />
  20. <view class="van-uploader__file-name van-ellipsis">{{ item.name || item.url || item.path }}</view>
  21. </view>
  22. <van-icon :wx:if="deletable" name="clear" class="van-uploader__preview-delete" :data-index="index" @tap.native="deleteItem($event, { index })" />
  23. </view>
  24. <!-- 上传样式 -->
  25. <block :wx:if="isInCount">
  26. <view :wx:if="useSlot" class="van-uploader__slot" @tap="startUpload">
  27. <slot />
  28. </view>
  29. <!-- 默认上传样式 -->
  30. <view wx:else class="van-uploader__upload" :style="'width: ' + computedPreviewSize + '; height: ' + computedPreviewSize + ';'" @tap="startUpload">
  31. <van-icon name="plus" class="van-uploader__upload-icon" />
  32. <text :wx:if="uploadText" class="van-uploader__upload-text">{{ uploadText }}</text>
  33. </view>
  34. </block>
  35. </view>
  36. </view>
  37. </view>
  38. </template>
  39. <script>
  40. import { VantComponent } from '../common/component';
  41. import { isImageFile } from './utils';
  42. import { addUnit } from '../common/utils';
  43. export default {
  44. data() {
  45. return {
  46. lists: [],
  47. computedPreviewSize: '',
  48. isInCount: true
  49. };
  50. },
  51. props: {
  52. disabled: Boolean,
  53. multiple: Boolean,
  54. uploadText: String,
  55. useSlot: Boolean,
  56. useBeforeRead: Boolean,
  57. previewSize: {
  58. type: null,
  59. default: 90
  60. },
  61. name: {
  62. type: [Number, String],
  63. default: ''
  64. },
  65. accept: {
  66. type: String,
  67. default: 'image'
  68. },
  69. fileList: {
  70. type: Array,
  71. default: () => []
  72. },
  73. maxSize: {
  74. type: Number,
  75. default: Number.MAX_VALUE
  76. },
  77. maxCount: {
  78. type: Number,
  79. default: 100
  80. },
  81. deletable: {
  82. type: Boolean,
  83. default: true
  84. },
  85. previewImage: {
  86. type: Boolean,
  87. default: true
  88. },
  89. previewFullImage: {
  90. type: Boolean,
  91. default: true
  92. },
  93. imageFit: {
  94. type: String,
  95. default: 'scaleToFill'
  96. }
  97. },
  98. methods: {
  99. formatFileList() {
  100. const { fileList = [], maxCount } = this;
  101. const lists = fileList.map((item) =>
  102. Object.assign(Object.assign({}, item), {
  103. isImage: typeof item.isImage === 'undefined' ? isImageFile(item) : item.isImage
  104. })
  105. );
  106. this.setData({
  107. lists,
  108. isInCount: lists.length < maxCount
  109. });
  110. },
  111. setComputedPreviewSize(val) {
  112. this.setData({
  113. computedPreviewSize: addUnit(val)
  114. });
  115. },
  116. startUpload() {
  117. if (this.disabled) {
  118. return;
  119. }
  120. const { name = '', capture = ['album', 'camera'], maxCount = 100, multiple = false, maxSize, accept, lists, useBeforeRead = false } = this;
  121. let chooseFile = null;
  122. const newMaxCount = maxCount - lists.length;
  123. if (accept === 'image') {
  124. chooseFile = new Promise((resolve, reject) => {
  125. wx.chooseImage({
  126. count: multiple ? (newMaxCount > 9 ? 9 : newMaxCount) : 1,
  127. sourceType: capture,
  128. success: resolve,
  129. fail: reject
  130. });
  131. });
  132. } else {
  133. chooseFile = new Promise((resolve, reject) => {
  134. wx.chooseMessageFile({
  135. count: multiple ? newMaxCount : 1,
  136. type: 'file',
  137. success: resolve,
  138. fail: reject
  139. });
  140. });
  141. }
  142. chooseFile.then((res) => {
  143. const file = multiple ? res.tempFiles : res.tempFiles[0];
  144. if (file instanceof Array) {
  145. const sizeEnable = file.every((item) => item.size <= maxSize);
  146. if (!sizeEnable) {
  147. this.$emit('oversize', {
  148. name
  149. });
  150. return;
  151. }
  152. } else if (file.size > maxSize) {
  153. this.$emit('oversize', {
  154. name
  155. });
  156. return;
  157. }
  158. if (useBeforeRead) {
  159. this.$emit('before-read', {
  160. file,
  161. name,
  162. callback: (result) => {
  163. if (result) {
  164. this.$emit('after-read', {
  165. file,
  166. name
  167. });
  168. }
  169. }
  170. });
  171. } else {
  172. this.$emit('after-read', {
  173. file,
  174. name
  175. });
  176. }
  177. });
  178. },
  179. deleteItem(event, _dataset) {
  180. /* ---处理dataset begin--- */
  181. this.handleDataset(event, _dataset);
  182. /* ---处理dataset end--- */
  183. const { index } = event.currentTarget.dataset;
  184. this.$emit('delete', {
  185. index,
  186. name: this.name
  187. });
  188. },
  189. doPreviewImage(event) {
  190. if (!this.previewFullImage) {
  191. return;
  192. }
  193. const curUrl = event.currentTarget.dataset.url;
  194. const images = this.lists.filter((item) => item.isImage).map((item) => item.url || item.path);
  195. this.$emit('click-preview', {
  196. url: curUrl,
  197. name: this.name
  198. });
  199. wx.previewImage({
  200. urls: images,
  201. current: curUrl,
  202. fail() {
  203. wx.showToast({
  204. title: '预览图片失败',
  205. icon: 'none'
  206. });
  207. }
  208. });
  209. }
  210. },
  211. watch: {
  212. previewSize: {
  213. handler: function (val) {
  214. this.setData({
  215. computedPreviewSize: addUnit(val)
  216. });
  217. },
  218. immediate: true
  219. },
  220. fileList: {
  221. handler: function () {
  222. const { fileList = [], maxCount } = this;
  223. const lists = fileList.map((item) =>
  224. Object.assign(Object.assign({}, item), {
  225. isImage: typeof item.isImage === 'undefined' ? isImageFile(item) : item.isImage
  226. })
  227. );
  228. this.setData({
  229. lists,
  230. isInCount: lists.length < maxCount
  231. });
  232. },
  233. immediate: true,
  234. deep: true
  235. }
  236. }
  237. };
  238. </script>
  239. <style>
  240. @import '../common/index.ttss';
  241. .van-uploader {
  242. position: relative;
  243. display: inline-block;
  244. }
  245. .van-uploader__wrapper {
  246. display: -webkit-flex;
  247. display: flex;
  248. -webkit-flex-wrap: wrap;
  249. flex-wrap: wrap;
  250. }
  251. .van-uploader__upload {
  252. position: relative;
  253. display: -webkit-flex;
  254. display: flex;
  255. -webkit-flex-direction: column;
  256. flex-direction: column;
  257. -webkit-align-items: center;
  258. align-items: center;
  259. -webkit-justify-content: center;
  260. justify-content: center;
  261. box-sizing: border-box;
  262. width: 80px;
  263. height: 80px;
  264. margin: 0 8px 8px 0;
  265. background-color: #fff;
  266. border: 1px dashed #ebedf0;
  267. border-radius: 4px;
  268. }
  269. .van-uploader__upload-icon {
  270. display: inline-block;
  271. width: 24px;
  272. height: 24px;
  273. color: #969799;
  274. font-size: 24px;
  275. }
  276. .van-uploader__upload-text {
  277. margin-top: 8px;
  278. color: #969799;
  279. font-size: 12px;
  280. }
  281. .van-uploader__preview {
  282. position: relative;
  283. margin: 0 8px 8px 0;
  284. }
  285. .van-uploader__preview-image {
  286. display: block;
  287. width: 80px;
  288. height: 80px;
  289. border-radius: 4px;
  290. }
  291. .van-uploader__preview-delete {
  292. position: absolute;
  293. top: -8px;
  294. right: -8px;
  295. color: #969799;
  296. font-size: 18px;
  297. background-color: #fff;
  298. border-radius: 100%;
  299. }
  300. .van-uploader__file {
  301. display: -webkit-flex;
  302. display: flex;
  303. -webkit-flex-direction: column;
  304. flex-direction: column;
  305. -webkit-align-items: center;
  306. align-items: center;
  307. -webkit-justify-content: center;
  308. justify-content: center;
  309. width: 80px;
  310. height: 80px;
  311. background-color: #f7f8fa;
  312. border-radius: 4px;
  313. }
  314. .van-uploader__file-icon {
  315. display: inline-block;
  316. width: 20px;
  317. height: 20px;
  318. color: #646566;
  319. font-size: 20px;
  320. }
  321. .van-uploader__file-name {
  322. box-sizing: border-box;
  323. width: 100%;
  324. margin-top: 8px;
  325. padding: 0 5px;
  326. color: #646566;
  327. font-size: 12px;
  328. text-align: center;
  329. }
  330. </style>