app-uploader.vue 12 KB

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