upload-image.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. <template>
  2. <view class="uni-file-picker__container">
  3. <view class="file-picker__box" v-for="(item, index) in filesList" :key="index" :style="boxStyle">
  4. <view class="file-picker__box-content" :style="borderStyle">
  5. <image class="file-image" :src="item.url" mode="aspectFill" @click.stop="prviewImage(item, index)">
  6. </image>
  7. <view v-if="delIcon && !readonly" class="icon-del-box" @click.stop="delFile(index)">
  8. <view class="icon-del"></view>
  9. <view class="icon-del rotate"></view>
  10. </view>
  11. <view v-if="(item.progress && item.progress !== 100) || item.progress === 0"
  12. class="file-picker__progress">
  13. <progress class="file-picker__progress-item" :percent="item.progress === -1 ? 0 : item.progress"
  14. stroke-width="4" :backgroundColor="item.errMsg ? '#ff5a5f' : '#EBEBEB'" />
  15. </view>
  16. <view v-if="item.errMsg" class="file-picker__mask" @click.stop="uploadFiles(item, index)">
  17. 点击重试
  18. </view>
  19. </view>
  20. </view>
  21. <view v-if="filesList.length < limit" class="file-picker__box" :style="boxStyle">
  22. <view class="file-picker__box-content is-add" :style="borderStyle" @click="choose">
  23. <slot></slot>
  24. </view>
  25. </view>
  26. </view>
  27. </template>
  28. <script>
  29. export default {
  30. name: "uploadImage",
  31. emits: ['uploadFiles', 'choose', 'delFile'],
  32. props: {
  33. filesList: {
  34. type: Array,
  35. default() {
  36. return []
  37. }
  38. },
  39. disabled: {
  40. type: Boolean,
  41. default: false
  42. },
  43. disablePreview: {
  44. type: Boolean,
  45. default: false
  46. },
  47. limit: {
  48. type: [Number, String],
  49. default: 9
  50. },
  51. imageStyles: {
  52. type: Object,
  53. default() {
  54. return {
  55. width: 'auto',
  56. height: 'auto',
  57. border: {}
  58. }
  59. }
  60. },
  61. delIcon: {
  62. type: Boolean,
  63. default: true
  64. },
  65. readonly: {
  66. type: Boolean,
  67. default: false
  68. }
  69. },
  70. computed: {
  71. styles() {
  72. let styles = {
  73. width: 'auto',
  74. height: 'auto',
  75. border: {}
  76. }
  77. return Object.assign(styles, this.imageStyles)
  78. },
  79. boxStyle() {
  80. const {
  81. width = 'auto',
  82. height = 'auto'
  83. } = this.styles
  84. let obj = {}
  85. if (height === 'auto') {
  86. if (width !== 'auto') {
  87. obj.height = this.value2px(width)
  88. obj['padding-top'] = 0
  89. } else {
  90. obj.height = 0
  91. }
  92. } else {
  93. obj.height = this.value2px(height)
  94. obj['padding-top'] = 0
  95. }
  96. if (width === 'auto') {
  97. if (height !== 'auto') {
  98. obj.width = this.value2px(height)
  99. } else {
  100. obj.width = '33.3%'
  101. }
  102. } else {
  103. obj.width = this.value2px(width)
  104. }
  105. let classles = ''
  106. for (let i in obj) {
  107. classles += `${i}:${obj[i]};`
  108. }
  109. return classles
  110. },
  111. borderStyle() {
  112. let {
  113. border
  114. } = this.styles
  115. let obj = {}
  116. const widthDefaultValue = 1
  117. const radiusDefaultValue = 3
  118. if (typeof border === 'boolean') {
  119. obj.border = border ? '1px #eee solid' : 'none'
  120. } else {
  121. let width = (border && border.width) || widthDefaultValue
  122. width = this.value2px(width)
  123. let radius = (border && border.radius) || radiusDefaultValue
  124. radius = this.value2px(radius)
  125. obj = {
  126. 'border-width': width,
  127. 'border-style': (border && border.style) || 'solid',
  128. 'border-color': (border && border.color) || '#eee',
  129. 'border-radius': radius
  130. }
  131. }
  132. let classles = ''
  133. for (let i in obj) {
  134. classles += `${i}:${obj[i]};`
  135. }
  136. return classles
  137. }
  138. },
  139. methods: {
  140. uploadFiles(item, index) {
  141. this.$emit("uploadFiles", item)
  142. },
  143. choose() {
  144. if (this.readonly) return
  145. this.$emit("choose")
  146. },
  147. delFile(index) {
  148. if (this.readonly) return
  149. this.$emit('delFile', index)
  150. },
  151. prviewImage(img, index) {
  152. if (this.readonly) return
  153. let urls = []
  154. if (Number(this.limit) === 1 && this.disablePreview && !this.disabled) {
  155. this.$emit("choose")
  156. }
  157. if (this.disablePreview) return
  158. this.filesList.forEach(i => {
  159. urls.push(i.url)
  160. })
  161. uni.previewImage({
  162. urls: urls,
  163. current: index
  164. });
  165. },
  166. value2px(value) {
  167. if (typeof value === 'number') {
  168. value += 'px'
  169. } else {
  170. if (value.indexOf('%') === -1) {
  171. value = value.indexOf('px') !== -1 ? value : value + 'px'
  172. }
  173. }
  174. return value
  175. }
  176. }
  177. }
  178. </script>
  179. <style lang="scss">
  180. .uni-file-picker__container {
  181. /* #ifndef APP-NVUE */
  182. display: flex;
  183. box-sizing: border-box;
  184. /* #endif */
  185. flex-wrap: wrap;
  186. margin: -5px;
  187. }
  188. .file-picker__box {
  189. position: relative;
  190. // flex: 0 0 33.3%;
  191. width: 33.3%;
  192. height: 0;
  193. padding-top: 33.33%;
  194. /* #ifndef APP-NVUE */
  195. box-sizing: border-box;
  196. /* #endif */
  197. }
  198. .file-picker__box-content {
  199. position: absolute;
  200. top: 0;
  201. right: 0;
  202. bottom: 0;
  203. left: 0;
  204. margin: 5px;
  205. border: 1px #eee solid;
  206. border-radius: 5px;
  207. overflow: hidden;
  208. }
  209. .file-picker__progress {
  210. position: absolute;
  211. bottom: 0;
  212. left: 0;
  213. right: 0;
  214. /* border: 1px red solid; */
  215. z-index: 2;
  216. }
  217. .file-picker__progress-item {
  218. width: 100%;
  219. }
  220. .file-picker__mask {
  221. /* #ifndef APP-NVUE */
  222. display: flex;
  223. /* #endif */
  224. justify-content: center;
  225. align-items: center;
  226. position: absolute;
  227. right: 0;
  228. top: 0;
  229. bottom: 0;
  230. left: 0;
  231. color: #fff;
  232. font-size: 12px;
  233. background-color: rgba(0, 0, 0, 0.4);
  234. }
  235. .file-image {
  236. width: 100%;
  237. height: 100%;
  238. }
  239. .is-add {
  240. /* #ifndef APP-NVUE */
  241. display: flex;
  242. /* #endif */
  243. align-items: center;
  244. justify-content: center;
  245. }
  246. .rotate {
  247. position: absolute;
  248. transform: rotate(90deg);
  249. }
  250. .icon-del-box {
  251. /* #ifndef APP-NVUE */
  252. display: flex;
  253. /* #endif */
  254. align-items: center;
  255. justify-content: center;
  256. position: absolute;
  257. top: 3px;
  258. right: 3px;
  259. height: 26px;
  260. width: 26px;
  261. border-radius: 50%;
  262. background-color: rgba(0, 0, 0, 0.5);
  263. z-index: 2;
  264. transform: rotate(-45deg);
  265. }
  266. .icon-del {
  267. width: 15px;
  268. height: 2px;
  269. background-color: rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important;
  270. border-radius: 2px;
  271. }
  272. </style>