app-uploader.vue 14 KB

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