vue图片裁剪组件封装

约定不等于承诺〃 2023-03-01 10:49 72阅读 0赞

本文封装了一个上传图片裁剪功能的组件,这里引入了vue-cropper插件。
安装

  1. npm install vue-cropper

使用

  1. import VueCropper from 'vue-cropper'
  2. Vue.use(VueCropper)

创建个子组件

  1. <template>
  2. <div class="cropper-box" >
  3. <VueCropper
  4. :style="{width:option.autoCropWidth+'px',height:option.autoCropHeight+'px',margin:'0 auto'}"
  5. ref="cropper"
  6. :img="option.img"
  7. :outputSize="option.outputSize"
  8. :outputType="option.outputType"
  9. :infoTrue="true"
  10. :full="option.full"
  11. :canMove="option.canMove"
  12. :canMoveBox="option.canMoveBox"
  13. :fixedBox="option.fixedBox"
  14. :original="option.original"
  15. :autoCrop="option.autoCrop"
  16. :fixed="option.fixed"
  17. :fixedNumber="option.fixedNumber"
  18. :centerBox="option.centerBox"
  19. :autoCropWidth="option.autoCropWidth"
  20. :autoCropHeight="option.autoCropHeight"
  21. :enlarge="option.enlarge"
  22. :mode="option.mode"
  23. ></VueCropper>
  24. <div style="margin-top:10px;text-align:center">
  25. <el-button type="info" @click="closeFun">取消</el-button>
  26. <el-button type="primary" @click="finish('blob')">裁剪</el-button>
  27. <el-button type="primary" @click="useOriginImage()">使用原图</el-button>
  28. </div>
  29. </div>
  30. </template>
  31. <script>
  32. import VueCropper from 'vue-cropper'
  33. export default {
  34. name: 'Cropper', // 图片裁剪
  35. props: {
  36. widthSize: {
  37. type: 'Number',
  38. default: 750
  39. },
  40. heightSize: {
  41. type: 'Number',
  42. default: 750
  43. },
  44. imgUrl: {
  45. default: ''
  46. },
  47. ratio: {
  48. type: 'Number',
  49. default: 2
  50. }
  51. },
  52. data() {
  53. return {
  54. option: {
  55. img: '',//裁切图片的地址
  56. outputSize: 1,//裁剪生成图片的质量 0.1-1
  57. full: false,//是否输出原图比例的截图
  58. outputType: 'png',//裁剪生成图片的格式
  59. canMove: true,//图片是否允许滚轮缩放
  60. fixedBox: true,//固定截图框大小 不允许改变
  61. original: true,//上传图片按照原始比例渲染
  62. canMoveBox: false,//截图框能否拖动
  63. centerBox: false,// 截图框是否被限制在图片里面
  64. canMove:true,// 上传图片是否可以移动
  65. autoCropWidth: 200,
  66. autoCropHeight: 200,
  67. autoCrop:true,//是否默认生成截图框
  68. // 开启宽度和高度比例
  69. fixed: true,
  70. fixedNumber: [1, 1], // 截图框的宽高比例
  71. enlarge: 1, // 图片根据截图框输出比例倍数
  72. mode: 'contain'
  73. },
  74. previews: {}
  75. }
  76. },
  77. created() {
  78. this.option.img = this.imgUrl;
  79. this.option.fixedNumber = [this.widthSize, this.heightSize];
  80. this.option.enlarge = this.ratio;
  81. this.option.autoCropWidth = this.widthSize / this.ratio;
  82. this.option.autoCropHeight = this.heightSize / this.ratio;
  83. console.log('this.option.autoCropWidth', this.option.autoCropWidth);
  84. console.log('this.option.autoCropHeight', this.option.autoCropHeight);
  85. },
  86. methods: {
  87. /**
  88. * 裁剪返回图片(base 64)
  89. */
  90. finish (type) {
  91. var that =this;
  92. this.$refs.cropper.getCropData((data) => {
  93. //裁切生成的base64图片
  94. this.$emit("editCoverFunc", this.convertBase64UrlToBlob(data))
  95. })
  96. },
  97. useOriginImage(){//使用原图,不做裁剪
  98. this.$emit("useOriginImage")
  99. },
  100. convertBase64UrlToBlob(urlData) {
  101. const bytes = window.atob(urlData.split(',')[1])// 去掉url的头,并转换为byte
  102. // 处理异常,将ascii码小于0的转换为大于0
  103. const ab = new ArrayBuffer(bytes.length)
  104. const ia = new Uint8Array(ab)
  105. for (var i = 0; i < bytes.length; i++) {
  106. ia[i] = bytes.charCodeAt(i)
  107. }
  108. return new Blob([ab], { type: 'image/jpeg' })
  109. },
  110. closeFun() {
  111. this.$emit("editCoverFunc", false)
  112. }
  113. }
  114. };
  115. </script>
  116. <style scoped>
  117. .cropper-box{
  118. position: relative;
  119. z-index: 99999999999 !important
  120. }
  121. </style>

创建个父组件

  1. <template>
  2. <div>
  3. <el-upload class="avatar-uploader" :style="{ width: styleWidth+'px' }" :auto-upload="false" :show-file-list="false"
  4. ref="uploadCover" :on-change="handleFilechange" :before-upload="beforeAvatarUpload" name="files">
  5. <div :class="{'imageUrl-wp': imageUrl}"
  6. :style="{ width: styleWidth+'px' ,height:styleHeight+'px' ,lineHeight:styleHeight+'px' }">
  7. <img v-if="imageUrl" :src="imageUrl" class="avatar">
  8. <i v-else class="el-icon-plus avatar-uploader-icon"
  9. :style="{ width: styleWidth+'px' ,height:styleHeight+'px' ,lineHeight:styleHeight+'px' }"></i>
  10. </div>
  11. </el-upload>
  12. <span :class="explain" v-if="crop">建议尺寸: {
  13. {widthSize}} x {
  14. {heightSize}}</span>
  15. <el-dialog title="图片裁剪" :visible.sync="editCover" :close-on-click-modal="false" width="60%" center append-to-body
  16. @close="close">
  17. <CropperIndex v-if="editCover" :widthSize="widthSize" :heightSize="heightSize" :imgUrl="imageUrl" :ratio="ratio"
  18. @editCoverFunc="handleCropped" @useOriginImage="handleNoCrop"></CropperIndex>
  19. </el-dialog>
  20. </div>
  21. </template>
  22. <script>
  23. import CropperIndex from "@/components/cropperIndex";
  24. import {
  25. uploadFile
  26. } from "@/api/table";
  27. export default {
  28. components: {
  29. CropperIndex
  30. },
  31. props: {
  32. widthSize: {
  33. type: "Number",
  34. default: 750
  35. },
  36. heightSize: {
  37. type: "Number",
  38. default: 750
  39. },
  40. initImgUrl: {
  41. default: ""
  42. },
  43. styleWidth: {
  44. type: "Number",
  45. default: 70
  46. },
  47. ratio: {
  48. type: "Number",
  49. default: 2
  50. },
  51. explain: {
  52. type: String
  53. },
  54. crop: {
  55. type: Boolean,
  56. default: true
  57. },
  58. sizeM:{
  59. default:3
  60. }
  61. },
  62. mounted(){
  63. console.log(this.explain);
  64. },
  65. data() {
  66. return {
  67. editCover: false, // 控制裁剪弹窗
  68. imageUrl: this.initImgUrl, // 图片地址
  69. imageOriginUr:"",//原图地址
  70. needClose:true,//是否需要判断close,直接关闭需要判断
  71. };
  72. },
  73. computed: {
  74. styleHeight: function () {
  75. return this.styleWidth * this.heightSize / this.widthSize;
  76. }
  77. },
  78. created() {
  79. console.log('imageUrl', this.imageUrl);
  80. console.log('prop', this.initImgUrl);
  81. },
  82. methods: {
  83. handleFilechange(file, files) {
  84. let _this=this;
  85. if (this.checkImg(file)) {
  86. if (file.raw.type === 'image/gif'){
  87. const formData = new FormData();
  88. formData.append('files', file.raw);
  89. uploadFile(formData).then(res => {
  90. this.imageUrl = res.data;
  91. this.needClose=false;
  92. this.$emit('imgEdit', this.imageUrl);
  93. });
  94. }else{
  95. var image=new Image();
  96. image.onload=function(){
  97. if(image.width==parseInt(_this.widthSize)&&image.height==parseInt(_this.heightSize)){
  98. _this.crop=false;//如果图片的比例就是约定的比例,那么不会再触发裁剪
  99. }
  100. if(parseInt(image.width)*parseInt(_this.heightSize)==parseInt(image.height)*parseInt(_this.widthSize)){
  101. _this.crop=false;//如果图片的比例与约定的比例相同,那么不再触发裁剪
  102. }
  103. const formData = new FormData();
  104. formData.append('files', file.raw);
  105. uploadFile(formData).then(res => {
  106. _this.imageOriginUr = res.data;
  107. if(!_this.crop){
  108. _this.imageUrl = _this.imageOriginUr;
  109. _this.needClose=false;
  110. _this.$emit('imgEdit', _this.imageUrl);
  111. }else{
  112. _this.imageUrl = URL.createObjectURL(file.raw);
  113. _this.editCover = true;
  114. }
  115. });
  116. }
  117. image.src=URL.createObjectURL(file.raw);
  118. }
  119. }
  120. },
  121. checkImg(file) {
  122. const fileType = file.raw.type === 'image/jpeg' || file.raw.type === 'image/png' || file.raw.type ===
  123. 'image/gif';
  124. const isLt3M = file.size / 1024 / 1024 < this.sizeM;
  125. if (!fileType) {
  126. this.$message.error('上传图片只能是 jpg/png/gif 格式!');
  127. }
  128. if (!isLt3M) {
  129. this.$message.error('上传图片大小不能超过 '+this.sizeM+'MB!');
  130. }
  131. return fileType && isLt3M;
  132. },
  133. handleCropped(data) {
  134. this.needClose=false;
  135. if (data !== false) {
  136. const formData = new FormData();
  137. console.log(data)
  138. formData.append('files', data, 'jpg');
  139. uploadFile(formData).then(res => {
  140. this.imageUrl = res.data;
  141. this.$emit('imgEdit', this.imageUrl);
  142. });
  143. } else {
  144. this.imageUrl = this.initImgUrl;
  145. }
  146. this.editCover = false;
  147. },
  148. handleNoCrop(){//不做裁剪,使用原图
  149. this.imageUrl = this.imageOriginUr;
  150. this.$emit('imgEdit', this.imageUrl);
  151. this.needClose=false;
  152. this.editCover = false;
  153. },
  154. close() {
  155. if(this.needClose){
  156. this.imageUrl = this.initImgUrl;
  157. }
  158. }
  159. }
  160. };
  161. </script>
  162. <style lang="scss" scoped>
  163. .avatar-uploader-icon {
  164. font-size: 28px;
  165. color: #8c939d;
  166. width: 120px;
  167. height: 120px;
  168. line-height: 120px;
  169. text-align: center;
  170. border: 1px dashed #d9d9d9;
  171. border-radius: 4px;
  172. cursor: pointer;
  173. position: relative;
  174. overflow: hidden;
  175. }
  176. .avatar-uploader-icon:hover {
  177. border-color: #409eff;
  178. }
  179. .imageUrl-wp {
  180. // width: 120px;
  181. // height: 120px;
  182. // line-height: 120px;
  183. // margin-right: 20px;
  184. border: 1px dashed #d9d9d9;
  185. border-radius: 4px;
  186. /*box-sizing: content-box;*/
  187. }
  188. .avatar {
  189. // vertical-align: middle;
  190. display: inline-block;
  191. max-width: 100%;
  192. max-height: 100%;
  193. // display: block;
  194. /*vertical-align: middle;*/
  195. text-align: center;
  196. display: table-cell;
  197. display: inline;
  198. }
  199. .avatar-explain {
  200. display: none;
  201. }
  202. .avatar-uploader {
  203. line-height:0;
  204. }
  205. </style>

最后在最外层组件引用

  1. <template>
  2. <div>
  3. <el-form ref="dataForm" :rules="rules" :model="info" label-position="left" label-width="90px" style="width: 500px; margin-left:50px;">
  4. <el-form-item label="明星姓名" prop="starName">
  5. <el-input v-model="info.starName" placeholder="请填写明星姓名" />
  6. </el-form-item>
  7. <el-form-item label="明星头像" prop="starAvatar">
  8. <ImgEditor :widthSize="200" :heightSize="200" :initImgUrl="info.starAvatar" :styleWidth="120" :key="info.starAvatar" @imgEdit="onImgEdited"></ImgEditor>
  9. </el-form-item>
  10. <el-form-item label="稿件ID" prop="articleId">
  11. <p class="tip_text">ID填写完毕后点击 回车键 自动获取标题和封面</p>
  12. <el-input v-model="info.articleId" placeholder="请填写稿件ID" @keyup.enter.native="findArticle(info.articleId)" />
  13. <p class="tip_text">请选择视频、图文</p>
  14. </el-form-item>
  15. <el-form-item label="标题" prop="title">
  16. <el-input v-model.trim="info.title" placeholder="请填写标题" maxlength="40" show-word-limit/>
  17. </el-form-item>
  18. <el-form-item label="图片" prop="cover">
  19. <ImgEditor :widthSize="750" :heightSize="600" :initImgUrl="info.cover" :styleWidth="120" :key="info.cover" @imgEdit="onImgEdited1"></ImgEditor>
  20. </el-form-item>
  21. </el-form>
  22. <div style="margin:0 auto;width:200px;">
  23. <el-button @click="close()">
  24. 取消
  25. </el-button>
  26. <el-button type="primary" @click="detailEdit('dataForm')">
  27. 保存
  28. </el-button>
  29. </div>
  30. </div>
  31. </template>
  32. <script>
  33. import ImgEditor from '@/components/imgEditor'
  34. import { addStarVideo, updateStarVideo } from '@/api/chunjieActivity'
  35. import { findArticle } from '@/api/table'
  36. //校验必须为正整数
  37. let isNumber = (rule, value, callback) => { //就是我们的回调函数,需要大家注意的是,这个没有在return的对象中,在data中
  38. if(value.length > 0) {
  39. var testResult = /^\d+$/.test(value);
  40. if(!testResult) {
  41. callback(new Error('必须为数字'));
  42. } else {
  43. callback();
  44. }
  45. } else {
  46. callback()
  47. }
  48. }
  49. export default {
  50. components: {
  51. ImgEditor
  52. },
  53. props: {
  54. info: {
  55. type: Object,
  56. default: {
  57. starName: '',
  58. starAvatar: '',
  59. title: '',
  60. cover: '',
  61. articleId: null,
  62. }
  63. },
  64. },
  65. data() {
  66. return {
  67. cropperImage: '',
  68. rules: {
  69. starName: [{
  70. required: true,
  71. message: '请填写明星姓名',
  72. trigger: 'change'
  73. }],
  74. starAvatar: [{
  75. required: true,
  76. message: '请上传明星头像',
  77. trigger: 'change'
  78. }],
  79. title: [{
  80. required: true,
  81. message: '请填写标题',
  82. trigger: 'change'
  83. }],
  84. cover: [{
  85. required: true,
  86. message: '请上传图片',
  87. trigger: 'change'
  88. }],
  89. articleId: [{
  90. required: true,
  91. message: '请输入稿件ID',
  92. trigger: 'change'
  93. }, {
  94. validator: isNumber,
  95. trigger: "blur"
  96. }],
  97. }
  98. }
  99. },
  100. methods: {
  101. onImgEdited(img) {
  102. this.info.starAvatar = img;
  103. },
  104. onImgEdited1(img) {
  105. this.info.cover = img;
  106. },
  107. saveItem(params) { //新增数据
  108. addStarVideo(params).then(response => {
  109. if(response.code == 200) {
  110. this.$message.success('新增成功')
  111. this.$refs.dataForm.clearValidate()
  112. this.$emit('dialogClose', {
  113. submitType: true
  114. })
  115. this.$refs.dataForm.clearValidate();
  116. this.$refs.dataForm.resetFields();
  117. } else {
  118. this.$message.error(response.message)
  119. }
  120. })
  121. },
  122. updateItem(params) { //更新数据
  123. updateStarVideo(params).then(response => {
  124. if(response.code == 200) {
  125. this.$message.success('修改成功')
  126. this.$refs.dataForm.clearValidate()
  127. this.$emit('dialogClose', {
  128. submitType: true
  129. })
  130. this.$refs.dataForm.clearValidate();
  131. this.$refs.dataForm.resetFields();
  132. } else {
  133. this.$message.error(response.message)
  134. }
  135. })
  136. },
  137. checkArticle(id, callback) { //要校验article的有效性
  138. var params = {
  139. id: id
  140. }
  141. findArticle(params).then(response => {
  142. if(response.code == 200) {
  143. if(response.data.showStatus != 1) {
  144. this.$message.error("只能选择正常稿件");
  145. return false;
  146. }
  147. var articleType = parseInt(response.data.type);
  148. //限定是1图文 2视频
  149. if(articleType != 1 && articleType != 2) {
  150. this.$message.error("不能选择此类型稿件");
  151. return false;
  152. }
  153. if(callback) {
  154. callback(response);
  155. }
  156. } else {
  157. this.$message.error(response.data.message)
  158. }
  159. })
  160. },
  161. //稿件信息
  162. findArticle(id) {
  163. this.info.title = ""
  164. this.info.cover = ""
  165. this.$refs.dataForm.validateField('articleId', (errorMsg) => {
  166. if(errorMsg != "") {
  167. return false;
  168. }
  169. let _this = this;
  170. this.checkArticle(id, function(response) {
  171. _this.info.title = response.data.title
  172. _this.info.cover = response.data.cover
  173. })
  174. });
  175. },
  176. detailEdit(formName) {
  177. this.$refs[formName].validate((valid) => {
  178. if(valid) {
  179. if(this.info.cover) {
  180. var params = {};
  181. if(this.info.id) {
  182. params = {
  183. 'id': this.info.id,
  184. 'starName': this.info.starName,
  185. 'starAvatar': this.info.starAvatar,
  186. 'title': this.info.title,
  187. 'cover': this.info.cover,
  188. 'articleId': this.info.articleId
  189. }
  190. } else {
  191. params = {
  192. 'starName': this.info.starName,
  193. 'starAvatar': this.info.starAvatar,
  194. 'title': this.info.title,
  195. 'cover': this.info.cover,
  196. 'articleId': this.info.articleId
  197. }
  198. }
  199. let _this = this;
  200. this.checkArticle(params.articleId, function() {
  201. //正式提交
  202. if(_this.info.id) {
  203. _this.updateItem(params);
  204. } else {
  205. _this.saveItem(params);
  206. }
  207. })
  208. } else {
  209. return false
  210. }
  211. } else {
  212. return false
  213. }
  214. })
  215. },
  216. close() {
  217. this.$emit('dialogClose', {
  218. submitType: false
  219. })
  220. this.$refs.dataForm.clearValidate();
  221. this.$refs.dataForm.resetFields();
  222. },
  223. }
  224. }
  225. </script>

发表评论

表情:
评论列表 (有 0 条评论,72人围观)

还没有评论,来说两句吧...

相关阅读