app-uploader.vue 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. <template>
  2. <view>
  3. <div class="upload-box">
  4. <slot>
  5. <ul>
  6. <li class="list_img" v-for="(item,index) in showImgList" :key="index" :style="{ width: size + 'rpx', height: size + 'rpx' }">
  7. <view class="iconfont icondelete" @click="delImg(index)"></view>
  8. <img :src="item" @click="viewImg(item)" :style="{ width: size + 'rpx', height: size + 'rpx' }" :mode="imgMode" />
  9. </li>
  10. <li v-if="showImgList.length < num" class="uploadImg" @click="chooseImage" :style="{ width: size + 'rpx', height: size + 'rpx' }">
  11. <view class="iconfont icontupiantianjia"></view>
  12. </li>
  13. </ul>
  14. </slot>
  15. </div>
  16. </view>
  17. </template>
  18. <script>
  19. import { uploadPic } from '@/api/common'
  20. export default {
  21. name: 'app-uploader',
  22. props: {
  23. // 传入的图片数组
  24. imgList: {
  25. type: Array,
  26. default: () => []
  27. },
  28. manualDel: {
  29. type: Boolean,
  30. default: false
  31. },
  32. // 图片的尺寸
  33. size: {
  34. type: Number,
  35. default: 200
  36. },
  37. // 上传图片数量
  38. num: {
  39. type: Number,
  40. default: 9
  41. },
  42. // 压缩图片
  43. compress: {
  44. type: Boolean,
  45. default: true
  46. },
  47. // 默认限制图片1.5M,单位为M
  48. maxSize: {
  49. type: String,
  50. default: '1.5M'
  51. },
  52. // 是否记录用户的选择记录
  53. isSave: {
  54. type: Boolean,
  55. default: false
  56. },
  57. // 记录用户的缓存字段
  58. saveStr: {
  59. type: String,
  60. default: 'chooseImage'
  61. },
  62. // 是否转base64 受数据传输长度限制,不建议在组件中使用,如果一定要使用,在返回结果中自己转换
  63. isBase64: {
  64. type: Boolean,
  65. default: false
  66. },
  67. multiple: {
  68. // 是否多图上传
  69. type: Boolean,
  70. default: true
  71. },
  72. disabled: {
  73. // 是否只读
  74. type: Boolean,
  75. default: false
  76. },
  77. // 图片展示模式
  78. imgMode: {
  79. type: String,
  80. default: 'scaleToFill'
  81. },
  82. uploadPicApi: {
  83. type: Function,
  84. default: uploadPic
  85. }
  86. },
  87. data() {
  88. return {
  89. resultData: {},
  90. showImgList: [],
  91. base64ImgList: [],
  92. xssarr: ['data:', 'text', 'txt', 'html', 'js', 'css', 'json', 'exe', 'sql', 'xml', 'doc', 'docx']
  93. }
  94. },
  95. onLoad() {
  96. if (this.isSave) {
  97. let str = uni.getStorageSync(this.saveStr)
  98. if (str !== '') {
  99. str = str.split(',')
  100. if (str.length > this.num) {
  101. str = str.slice(0, this.num)
  102. }
  103. this.showImgList = str
  104. }
  105. } else {
  106. uni.removeStorageSync(this.saveStr)
  107. }
  108. this.showImgList = [...this.imgList]
  109. },
  110. methods: {
  111. chooseImage: async function() {
  112. if (this.disabled) {
  113. return
  114. }
  115. let that = this
  116. let tempFilePaths = await that.getImage()
  117. await that.blobToBase64(tempFilePaths)
  118. await that.autoUploadImg()
  119. await that.postMsg()
  120. return this.showImgList
  121. },
  122. postMsg() {
  123. this.$emit('update:imgList', this.showImgList)
  124. this.$emit('chooseImage', this.showImgList)
  125. this.$emit('resultData', this.resultData)
  126. },
  127. autoUploadImg() {
  128. let that = this
  129. let base64ImgList = that.base64ImgList
  130. let params = {}
  131. if (base64ImgList.length === 1) {
  132. params['file[content]'] = base64ImgList[0]
  133. } else {
  134. base64ImgList.forEach((item, index) => {
  135. params[`file[${index}][content]`] = item
  136. })
  137. }
  138. return new Promise((resolve, reject) => {
  139. this.uploadPicApi({
  140. ...params,
  141. relative_path: 1
  142. }).then(res => {
  143. if (res.code === 1) {
  144. let imgArr = [res.data.url]
  145. that.showImgList = [...that.showImgList, ...imgArr]
  146. that.resultData = res.data
  147. this.$emit('currentUpload', imgArr)
  148. this.$emit('getResult', that.showImgList)
  149. resolve(that.showImgList)
  150. } else {
  151. reject(res)
  152. }
  153. })
  154. })
  155. },
  156. // 转64
  157. async blobToBase64(tempFilePaths) {
  158. let that = this
  159. that.base64ImgList = []
  160. let newImgList = [...tempFilePaths]
  161. for (let i = 0; i < newImgList.length; i++) {
  162. let blob = newImgList[i]
  163. // 转64位后可以看文件类型 data:text/
  164. // #ifdef H5
  165. // 如果是服务器返回的图片不转base64
  166. // if (blob.indexOf('blob') !== 0) {
  167. // that.imgPathList.push(blob)
  168. // continue
  169. // }
  170. await that.h5b64(blob).then(res => {
  171. let startindex = res.indexOf(':')
  172. let endindex = res.indexOf('/')
  173. let type = res.substring(startindex + 1, endindex)
  174. if (that.xssarr.indexOf(type) === -1) {
  175. that.base64ImgList.push(res)
  176. }
  177. })
  178. // #endif
  179. // #ifdef MP-WEIXIN
  180. // 微信临时文件 http://tmp/wx3a16...
  181. // if (blob.indexOf('http://tmp/') !== 0) {
  182. // that.imgPathList.push(blob)
  183. // continue
  184. // }
  185. await that.WEIXINb64(blob).then(res => {
  186. that.base64ImgList.push(res)
  187. })
  188. // #endif
  189. }
  190. },
  191. WEIXINb64(blob) {
  192. return new Promise((resolve, reject) => {
  193. uni.getFileSystemManager().readFile({
  194. filePath: blob, // 选择图片返回的相对路径
  195. encoding: 'base64', // 编码格式
  196. success: function(res) {
  197. let b64str = 'data:image/jpeg;base64,' + res.data
  198. resolve(b64str)
  199. },
  200. fail: function(err) {
  201. console.log(err)
  202. }
  203. })
  204. })
  205. },
  206. h5b64(blob) {
  207. return fetch(blob, {
  208. method: 'GET',
  209. headers: new Headers({
  210. 'Access-Control-Allow-Origin': '*',
  211. 'Content-Type': 'text/plain'
  212. }),
  213. mode: 'no-cors'
  214. })
  215. .then(data => {
  216. const blob = data.blob()
  217. return blob
  218. })
  219. .then(async blob => {
  220. let reader = new FileReader()
  221. reader.readAsDataURL(blob)
  222. return await new Promise((res, reg) => {
  223. reader.onloadend = function() {
  224. const data = reader.result
  225. res(data)
  226. }
  227. })
  228. })
  229. },
  230. // 上传图片
  231. getImage() {
  232. let that = this
  233. let showImgList = that.showImgList
  234. let count = that.num - showImgList.length
  235. return new Promise((resolve, reject) => {
  236. uni.chooseImage({
  237. count: this.multiple ? count : 1, // 默认9
  238. sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
  239. sourceType: ['album', 'camera'], // 从相册选择
  240. success: async function(res) {
  241. await that.fileValid(res)
  242. var tempFilePaths = res.tempFilePaths
  243. if (that.isSave) {
  244. uni.setStorageSync(that.saveStr, showImgList.join(','))
  245. }
  246. resolve(tempFilePaths)
  247. }
  248. })
  249. })
  250. },
  251. // 文件过滤
  252. fileValid(res) {
  253. let tempFiles = res.tempFiles
  254. let tempFilePaths = res.tempFilePaths
  255. let newtempFiles = []
  256. let newtempFilePaths = []
  257. for (let i = 0; i < tempFiles.length; i++) {
  258. // 大于1.5m就不让上传
  259. let fileSize = tempFiles[i].size
  260. if (fileSize > 1024 * 1024 * 1.5) {
  261. uni.showToast({
  262. title: '过滤不能大于1.5m的文件',
  263. duration: 2000,
  264. icon: 'none'
  265. })
  266. continue
  267. }
  268. // 文件类型设置
  269. let spl = tempFiles[i].path.split('.')
  270. let fileType = spl[spl.length - 1]
  271. if (fileType && this.xssarr.indexOf(fileType) >= 0) {
  272. uni.showToast({
  273. title: '过滤不符合规范文件',
  274. duration: 2000,
  275. icon: 'none'
  276. })
  277. continue
  278. }
  279. newtempFiles.push(tempFiles[i])
  280. newtempFilePaths.push(tempFilePaths[i])
  281. }
  282. res.tempFiles = newtempFiles
  283. res.tempFilePaths = newtempFilePaths
  284. // console.log('过滤完了')
  285. },
  286. // 删除
  287. async delImg(index) {
  288. if (this.disabled) {
  289. return
  290. }
  291. if (this.isSave) {
  292. uni.setStorageSync(this.saveStr, this.showImgList.join(','))
  293. }
  294. if (!this.manualDel) {
  295. this.showImgList.splice(index, 1)
  296. } else {
  297. this.$emit('delImage', index)
  298. }
  299. await this.postMsg()
  300. },
  301. // 预览
  302. viewImg(path) {
  303. uni.previewImage({
  304. urls: this.showImgList,
  305. current: path
  306. })
  307. },
  308. // 获取图片信息
  309. getImageInfo(image, cb) {
  310. uni.getImageInfo({
  311. src: image,
  312. success: function(image) {
  313. cb(image)
  314. return image
  315. }
  316. })
  317. },
  318. // 图片压缩
  319. compressImage() {
  320. uni.compressImage({
  321. src: image,
  322. quality: 80,
  323. complete: res => {}
  324. })
  325. }
  326. },
  327. watch: {
  328. imgList(val) {
  329. console.log('监听')
  330. this.showImgList = [...val]
  331. }
  332. }
  333. }
  334. </script>
  335. <style scoped lang="scss">
  336. .uni-system-preview-image {
  337. background-color: rgba(0, 0, 0, 0.9);
  338. .uni-preview-image {
  339. max-width: 100%;
  340. }
  341. }
  342. .upload-box {
  343. padding: 0 30px;
  344. &:first-child {
  345. padding-left: 0;
  346. }
  347. & > ul {
  348. display: inline-block;
  349. display: flex;
  350. flex-wrap: wrap;
  351. & > li {
  352. list-style: none;
  353. width: 210px;
  354. height: 210px;
  355. margin: 0 20px 20px 0;
  356. position: relative;
  357. background-color: #fff;
  358. .icondelete {
  359. font-size: 25px;
  360. z-index: 999;
  361. position: absolute;
  362. right: 0;
  363. bottom: 0;
  364. color: #fff;
  365. padding: 0 2px;
  366. background-color: rgba(0, 0, 0, 0.45);
  367. }
  368. }
  369. }
  370. & .list_img {
  371. & > img {
  372. width: 100%;
  373. height: 100%;
  374. }
  375. }
  376. & .uploadImg {
  377. border: 1px dashed #e5e5e5;
  378. display: flex;
  379. flex-direction: column;
  380. align-items: center;
  381. justify-content: center;
  382. box-sizing: border-box;
  383. .upload_ipt {
  384. position: absolute;
  385. width: 100%;
  386. height: 100%;
  387. /*overflow:hidden;*/
  388. cursor: pointer;
  389. opacity: 0;
  390. }
  391. .iconfont {
  392. color: rgb(150, 151, 153);
  393. font-size: 40px;
  394. }
  395. }
  396. }
  397. </style>