app-uploader.vue 13 KB

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