ImageEditUpload.vue 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  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.split(".").pop().toLocaleLowerCase();
  268. const checked = this.format.some(
  269. (item) => item.toLocaleLowerCase() === _file_format
  270. );
  271. if (!checked) {
  272. this.res = {
  273. success: false,
  274. msg: "只支持文件格式为" + this.format.join("/"),
  275. };
  276. this.onFormatError(file);
  277. return false;
  278. }
  279. }
  280. // check maxSize
  281. if (this.maxSize) {
  282. if (file.size > this.maxSize * 1024) {
  283. this.res = {
  284. success: false,
  285. msg: "文件大小不能超过" + this.limitSize,
  286. };
  287. this.onExceededSize(file);
  288. return false;
  289. }
  290. }
  291. this.res = {
  292. success: true,
  293. msg: "",
  294. };
  295. return true;
  296. },
  297. openEdit(file) {
  298. this.initFile(file);
  299. this.$nextTick(function () {
  300. this.edit = true;
  301. });
  302. },
  303. initFile(file) {
  304. this.file = {
  305. status: "uploading",
  306. name: file.name,
  307. size: file.size,
  308. type: file.type,
  309. url: this.createUrl(file),
  310. response: {},
  311. percentage: 0,
  312. showProgress: true,
  313. };
  314. },
  315. createUrl(file) {
  316. let url = "";
  317. let URL = window.URL || window.webkitURL;
  318. if (URL && URL.createObjectURL) {
  319. url = URL.createObjectURL(file);
  320. }
  321. return url;
  322. },
  323. clearEdit() {
  324. this.edit = false;
  325. },
  326. editSave() {
  327. const { name, type } = this.file;
  328. const cropBoxSize = this.cropper.getCropBoxData();
  329. // 配置width,height之后会压缩原始图片
  330. const binStr = atob(
  331. this.cropper
  332. .getCroppedCanvas({
  333. width: cropBoxSize.width,
  334. height: cropBoxSize.height,
  335. })
  336. .toDataURL(type)
  337. .split(",")[1]
  338. );
  339. let arr = new Uint8Array(binStr.length);
  340. for (let i = 0; i < binStr.length; i++) {
  341. arr[i] = binStr.charCodeAt(i);
  342. }
  343. let file;
  344. if (this.isIe()) {
  345. file = new Blob([arr], { type });
  346. } else {
  347. file = new File([arr], name, { type });
  348. }
  349. this.post(file);
  350. },
  351. post(file) {
  352. ajax({
  353. headers: this.headers,
  354. withCredentials: this.withCredentials,
  355. file: file,
  356. data: this.data,
  357. filename: this.name,
  358. action: this.action,
  359. onProgress: (e) => {
  360. this.handleProgress(e);
  361. },
  362. onSuccess: (res) => {
  363. this.edit = false;
  364. this.handleSuccess(res, file);
  365. },
  366. onError: (err, response) => {
  367. this.edit = false;
  368. this.handleError(err, response);
  369. },
  370. });
  371. },
  372. handleProgress(e) {
  373. this.onProgress(e, this.file);
  374. this.file.percentage = parseFloat(e.percent) || 0;
  375. },
  376. handleSuccess(res, file) {
  377. this.file.status = "finished";
  378. this.file.response = res;
  379. this.res = {
  380. success: true,
  381. msg: "图片上传成功!",
  382. };
  383. this.file.url = this.createUrl(file);
  384. this.imgUrl = this.file.url;
  385. this.onSuccess(res, this.file);
  386. setTimeout(() => {
  387. this.file.showProgress = false;
  388. }, 300);
  389. },
  390. handleError(err, response) {
  391. this.file.status = "fail";
  392. this.res = {
  393. success: false,
  394. msg: `图片上传失败:${response.msg || "请求错误"}`,
  395. };
  396. this.onError(err, response, this.file);
  397. },
  398. },
  399. };
  400. </script>