ImageEditUpload.vue 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. <template>
  2. <div class="image-edit-upload">
  3. <div :class="[`${prefixCls}-main`]" v-show="!edit">
  4. <div :class="elPrefixCls">
  5. <div
  6. :class="classes"
  7. @click="handleClick"
  8. @drop.prevent="onDrop"
  9. @dragover.prevent="dragOver = true"
  10. @dragleave.prevent="dragOver = false"
  11. >
  12. <input
  13. ref="input"
  14. type="file"
  15. :class="[`${elPrefixCls}-input`]"
  16. @change="handleChange"
  17. :accept="accept"
  18. />
  19. <div :class="[`${prefixCls}-cover`]" v-if="imgUrl">
  20. <img :src="imgUrl" alt="default" />
  21. </div>
  22. <div :class="[`${prefixCls}-dcover`]" v-else>
  23. <i class="el-icon-user-solid"></i><br />
  24. <p>点击或拖拽图片到此处</p>
  25. </div>
  26. </div>
  27. </div>
  28. </div>
  29. <div :class="[`${prefixCls}-edit`]" v-show="file.url && edit">
  30. <div :class="[`${prefixCls}-edit-box`]">
  31. <img ref="editImage" :src="file.url" />
  32. <transition name="fade" v-if="file && file.showProgress">
  33. <div class="img-progress">
  34. <el-progress
  35. :show-text="false"
  36. :stroke-width="4"
  37. :percentage="file.percentage"
  38. :status="
  39. file.status === 'finished' && file.showProgress
  40. ? 'success'
  41. : null
  42. "
  43. ></el-progress>
  44. </div>
  45. </transition>
  46. </div>
  47. <div :class="[`${prefixCls}-edit-btns`]">
  48. <el-button type="error" @click="clearEdit">取消</el-button>
  49. <el-button type="primary" @click="editSave">确认</el-button>
  50. </div>
  51. </div>
  52. <p
  53. :class="[
  54. `${prefixCls}-tips`,
  55. { 'cc-tips-success': res.success, 'cc-tips-error': !res.success }
  56. ]"
  57. v-if="res.msg"
  58. >
  59. {{ res.msg }}
  60. </p>
  61. </div>
  62. </template>
  63. <script>
  64. import ajax from "../utils/ajax";
  65. import Cropper from "cropperjs";
  66. import "cropperjs/dist/cropper.min.css";
  67. const elPrefixCls = "el-upload";
  68. const prefixCls = "cc-edit-upload";
  69. const ratios = {
  70. double: 1 / 2,
  71. square: 1,
  72. rectangle: 3 / 4
  73. };
  74. export default {
  75. name: "image-edit-upload",
  76. props: {
  77. action: {
  78. type: String,
  79. required: true
  80. },
  81. headers: {
  82. type: Object,
  83. default() {
  84. return {};
  85. }
  86. },
  87. defImage: {
  88. type: String
  89. },
  90. data: {
  91. type: Object
  92. },
  93. name: {
  94. type: String,
  95. default: "file"
  96. },
  97. withCredentials: {
  98. type: Boolean,
  99. default: false
  100. },
  101. format: {
  102. type: Array,
  103. default() {
  104. return ["jpg", "png"];
  105. }
  106. },
  107. accept: {
  108. type: String
  109. },
  110. maxSize: {
  111. type: Number,
  112. default() {
  113. return 2 * 1024;
  114. }
  115. },
  116. ratioType: {
  117. type: String,
  118. default: "rectangle",
  119. validator(val) {
  120. return ["square", "rectangle", "double"].indexOf(val) > -1;
  121. }
  122. },
  123. onProgress: {
  124. type: Function,
  125. default() {
  126. return {};
  127. }
  128. },
  129. onSuccess: {
  130. type: Function,
  131. default() {
  132. return {};
  133. }
  134. },
  135. onError: {
  136. type: Function,
  137. default() {
  138. return {};
  139. }
  140. },
  141. onRemove: {
  142. type: Function,
  143. default() {
  144. return {};
  145. }
  146. },
  147. onPreview: {
  148. type: Function,
  149. default() {
  150. return {};
  151. }
  152. },
  153. onExceededSize: {
  154. type: Function,
  155. default() {
  156. return {};
  157. }
  158. },
  159. onFormatError: {
  160. type: Function,
  161. default() {
  162. return {};
  163. }
  164. }
  165. },
  166. data() {
  167. return {
  168. file: {},
  169. type: "drag",
  170. edit: false,
  171. cropper: false,
  172. imgUrl: this.defImage,
  173. prefixCls,
  174. elPrefixCls,
  175. dragOver: false,
  176. tempIndex: 1,
  177. res: {
  178. success: true,
  179. msg: ""
  180. }
  181. };
  182. },
  183. computed: {
  184. classes() {
  185. return [
  186. `${elPrefixCls}`,
  187. {
  188. [`${elPrefixCls}-dragger`]: this.type === "drag",
  189. [`${elPrefixCls}-dragOver`]: this.type === "drag" && this.dragOver
  190. }
  191. ];
  192. },
  193. aspectRatio() {
  194. return ratios[this.ratioType];
  195. },
  196. limitSize() {
  197. const mn = this.maxSize / 1024;
  198. return mn >= 1 ? mn + "M" : this.maxSize + "Kb";
  199. }
  200. // accept() {
  201. // let formats = this.format.map(item => {
  202. // return "image/" + item;
  203. // });
  204. // return formats.join();
  205. // }
  206. },
  207. watch: {
  208. edit(value) {
  209. if (value) {
  210. this.$nextTick(function() {
  211. if (!this.$refs.editImage) {
  212. return;
  213. }
  214. this.cropper = new Cropper(this.$refs.editImage, {
  215. aspectRatio: this.aspectRatio,
  216. minCropBoxWidth: 120,
  217. minCropBoxHeight: 120 / this.aspectRatio,
  218. viewMode: 1
  219. });
  220. });
  221. } else {
  222. if (this.cropper) {
  223. this.cropper.destroy();
  224. this.cropper = false;
  225. }
  226. }
  227. },
  228. defImage(val) {
  229. this.imgUrl = val;
  230. }
  231. },
  232. methods: {
  233. isIe() {
  234. let userAgent = navigator.userAgent;
  235. let isIE =
  236. userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1;
  237. let isEdge = userAgent.indexOf("Edge") > -1 && !isIE;
  238. let isIE11 =
  239. userAgent.indexOf("Trident") > -1 && userAgent.indexOf("rv:11.0") > -1;
  240. return isEdge || isIE11 || isIE;
  241. },
  242. handleClick() {
  243. this.res = { success: true, msg: "" };
  244. this.$refs.input.click();
  245. },
  246. handleChange(e) {
  247. const files = e.target.files;
  248. if (!files) return;
  249. this.uploadFiles(files);
  250. this.$refs.input.value = null;
  251. },
  252. onDrop(e) {
  253. this.res = { success: true, msg: "" };
  254. this.dragOver = false;
  255. this.uploadFiles(e.dataTransfer.files);
  256. },
  257. uploadFiles(files) {
  258. let postFiles = Array.prototype.slice.call(files);
  259. if (postFiles.length === 0) return;
  260. if (this.checkValid(postFiles[0])) {
  261. this.openEdit(postFiles[0]);
  262. }
  263. },
  264. checkValid(file) {
  265. // check format
  266. if (this.format.length) {
  267. const _file_format = file.name
  268. .split(".")
  269. .pop()
  270. .toLocaleLowerCase();
  271. const checked = this.format.some(
  272. item => item.toLocaleLowerCase() === _file_format
  273. );
  274. if (!checked) {
  275. this.res = {
  276. success: false,
  277. msg: "只支持文件格式为" + this.format.join("/")
  278. };
  279. this.onFormatError(file);
  280. return false;
  281. }
  282. }
  283. // check maxSize
  284. if (this.maxSize) {
  285. if (file.size > this.maxSize * 1024) {
  286. this.res = {
  287. success: false,
  288. msg: "文件大小不能超过" + this.limitSize
  289. };
  290. this.onExceededSize(file);
  291. return false;
  292. }
  293. }
  294. this.res = {
  295. success: true,
  296. msg: ""
  297. };
  298. return true;
  299. },
  300. openEdit(file) {
  301. this.initFile(file);
  302. this.$nextTick(function() {
  303. this.edit = true;
  304. });
  305. },
  306. initFile(file) {
  307. this.file = {
  308. status: "uploading",
  309. name: file.name,
  310. size: file.size,
  311. type: file.type,
  312. url: this.createUrl(file),
  313. response: {},
  314. percentage: 0,
  315. showProgress: true
  316. };
  317. },
  318. createUrl(file) {
  319. let url = "";
  320. let URL = window.URL || window.webkitURL;
  321. if (URL && URL.createObjectURL) {
  322. url = URL.createObjectURL(file);
  323. }
  324. return url;
  325. },
  326. clearEdit() {
  327. this.edit = false;
  328. },
  329. editSave() {
  330. const { name, type } = this.file;
  331. const cropBoxSize = this.cropper.getCropBoxData();
  332. // 配置width,height之后会压缩原始图片
  333. const binStr = atob(
  334. this.cropper
  335. .getCroppedCanvas({
  336. width: cropBoxSize.width,
  337. height: cropBoxSize.height
  338. })
  339. .toDataURL(type)
  340. .split(",")[1]
  341. );
  342. let arr = new Uint8Array(binStr.length);
  343. for (let i = 0; i < binStr.length; i++) {
  344. arr[i] = binStr.charCodeAt(i);
  345. }
  346. let file;
  347. if (this.isIe()) {
  348. file = new Blob([arr], { type });
  349. } else {
  350. file = new File([arr], name, { type });
  351. }
  352. this.post(file);
  353. },
  354. post(file) {
  355. ajax({
  356. headers: this.headers,
  357. withCredentials: this.withCredentials,
  358. file: file,
  359. data: this.data,
  360. filename: this.name,
  361. action: this.action,
  362. onProgress: e => {
  363. this.handleProgress(e);
  364. },
  365. onSuccess: res => {
  366. this.edit = false;
  367. this.handleSuccess(res, file);
  368. },
  369. onError: (err, response) => {
  370. this.edit = false;
  371. this.handleError(err, response);
  372. }
  373. });
  374. },
  375. handleProgress(e) {
  376. this.onProgress(e, this.file);
  377. this.file.percentage = parseFloat(e.percent) || 0;
  378. },
  379. handleSuccess(res, file) {
  380. this.file.status = "finished";
  381. this.file.response = res;
  382. this.res = {
  383. success: true,
  384. msg: "图片上传成功!"
  385. };
  386. this.file.url = this.createUrl(file);
  387. this.imgUrl = this.file.url;
  388. this.onSuccess(res, this.file);
  389. setTimeout(() => {
  390. this.file.showProgress = false;
  391. }, 300);
  392. },
  393. handleError(err, response) {
  394. this.file.status = "fail";
  395. this.res = {
  396. success: false,
  397. msg: `图片上传失败:${response.msg || "请求错误"}`
  398. };
  399. this.onError(err, response, this.file);
  400. }
  401. }
  402. };
  403. </script>