lex 1 ano atrás
pai
commit
b2d4de1d9c

+ 11 - 0
package-lock.json

@@ -34,6 +34,7 @@
         "vudio.js": "^1.0.3",
         "vue": "^3.2.47",
         "vue-awesome-swiper": "^5.0.1",
+        "vue-cropper": "^1.1.1",
         "vue-router": "^4.1.6",
         "vue3-lottie": "^2.7.0",
         "vuedraggable": "^4.1.0",
@@ -8491,6 +8492,11 @@
         "vue": "3.x"
       }
     },
+    "node_modules/vue-cropper": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/vue-cropper/-/vue-cropper-1.1.1.tgz",
+      "integrity": "sha512-WsqKMpaBf9Osi1LQlE/5AKdD0nHWOy1asLXocaG8NomOWO07jiZi968+/PbMmnD0QbPJOumDQaGuGa13qys85A=="
+    },
     "node_modules/vue-demi": {
       "version": "0.14.5",
       "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.5.tgz",
@@ -15536,6 +15542,11 @@
       "resolved": "https://registry.npmmirror.com/vue-awesome-swiper/-/vue-awesome-swiper-5.0.1.tgz",
       "integrity": "sha512-mWjFJzUqA4lG+DmsmibvMpoiBnl+IH2SSeiiQ3i5M0t1y9FknTxnGT0DsMb2YdJLgjYMEK3sYOWzqgLnZMH8Lg=="
     },
+    "vue-cropper": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/vue-cropper/-/vue-cropper-1.1.1.tgz",
+      "integrity": "sha512-WsqKMpaBf9Osi1LQlE/5AKdD0nHWOy1asLXocaG8NomOWO07jiZi968+/PbMmnD0QbPJOumDQaGuGa13qys85A=="
+    },
     "vue-demi": {
       "version": "0.14.5",
       "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.5.tgz",

+ 1 - 0
package.json

@@ -48,6 +48,7 @@
     "vudio.js": "^1.0.3",
     "vue": "^3.2.47",
     "vue-awesome-swiper": "^5.0.1",
+    "vue-cropper": "^1.1.1",
     "vue-router": "^4.1.6",
     "vue3-lottie": "^2.7.0",
     "vuedraggable": "^4.1.0",

Diferenças do arquivo suprimidas por serem muito extensas
+ 54 - 0
src/components/m-cropper/index.module.less


+ 423 - 0
src/components/m-cropper/index.tsx

@@ -0,0 +1,423 @@
+import { defineComponent } from 'vue';
+import 'vue-cropper/dist/index.css';
+import { VueCropper } from 'vue-cropper';
+import styles from './index.module.less';
+
+export default defineComponent({
+  name: 'col-cropper',
+  components: { VueCropper },
+  props: {
+    hideInput: {
+      type: Boolean,
+      default: false
+    },
+    option: {
+      type: Object
+    },
+    onCancelTailor: {
+      type: Function,
+      default: () => {}
+    }, // 取消
+    getBase64Data: {
+      type: Function,
+      default: () => {}
+    },
+    getBlob: {
+      type: Function,
+      default: () => {}
+    },
+    getFile: {
+      type: Function,
+      default: () => {}
+    },
+    imgOriginF: {
+      type: Function,
+      default: () => {}
+    }
+  },
+  data() {
+    return {
+      img: '',
+      config: {
+        ceilbutton: false, //顶部按钮,默认底部
+        outputSize: 1, //裁剪生成图片的质量
+        outputType: 'png', //裁剪生成图片的格式,默认png
+        info: false, //裁剪框的大小信息
+        canScale: true, //图片是否允许滚轮缩放
+        autoCrop: false, //是否默认生成截图框
+        autoCropWidth: 0, //默认生成截图框宽度
+        autoCropHeight: 0, //默认生成截图框高度
+        fixed: true, //是否开启截图框宽高固定比例
+        fixedNumber: [1, 1], //截图框的宽高比例
+        full: false, //是否输出原图比例的截图
+        fixedBox: true, //固定截图框大小 不允许改变
+        canMove: true, //上传图片是否可以移动
+        canMoveBox: false, //截图框能否拖动
+        original: false, //上传图片按照原始比例渲染
+        centerBox: true, //截图框是否被限制在图片里面
+        high: true, //是否按照设备的dpr 输出等比例图片
+        infoTrue: false, //true 为展示真实输出图片宽高 false 展示看到的截图框宽高
+        maxImgSize: 2000, //限制图片最大宽度和高度
+        enlarge: 1, //图片根据截图框输出比例倍数
+        mode: '100%', //图片默认渲染方式
+        cancelButtonText: '取消', //取消按钮文本
+        confirmButtonText: '确定', //确定按钮文本
+        cancelButtonBackgroundColor: '#606266', //取消按钮背景色
+        confirmButtonBackgroundColor: '#ed594c', //确定按钮背景色
+        cancelButtonTextColor: '#ffffff', //取消按钮字体色
+        confirmButtonTextColor: '#ffffff' //确定按钮字体色
+      }
+    };
+  },
+  mounted() {
+    this.config = Object.assign(this.config, this.option);
+  },
+  methods: {
+    //添加网格线
+    addSlide() {
+      if (document.getElementById('vertical') == null) {
+        let box = document.getElementsByClassName('cropper-crop-box')[0];
+        //左网格线
+        let verticalLeft = document.createElement('div');
+        verticalLeft.id = 'vertical';
+        verticalLeft.style.width = '1px';
+        verticalLeft.style.height = '100%';
+        verticalLeft.style.top = '0px';
+        verticalLeft.style.left = '33%';
+        verticalLeft.style.position = 'absolute';
+        verticalLeft.style.backgroundColor = '#fff';
+        verticalLeft.style.zIndex = '522';
+        verticalLeft.style.opacity = '0.5';
+        //右网格线
+        let verticalRight = document.createElement('div');
+        verticalRight.style.width = '1px';
+        verticalRight.style.height = '100%';
+        verticalRight.style.top = '0px';
+        verticalRight.style.right = '33%';
+        verticalRight.style.position = 'absolute';
+        verticalRight.style.backgroundColor = '#fff';
+        verticalRight.style.zIndex = '522';
+        verticalRight.style.opacity = '0.5';
+        //上网格线
+        let verticalTop = document.createElement('div');
+        verticalTop.style.width = '100%';
+        verticalTop.style.height = '1px';
+        verticalTop.style.top = '33%';
+        verticalTop.style.left = '0px';
+        verticalTop.style.position = 'absolute';
+        verticalTop.style.backgroundColor = '#fff';
+        verticalTop.style.zIndex = '522';
+        verticalTop.style.opacity = '0.5';
+        //下网格线
+        let verticalBottom = document.createElement('div');
+        verticalBottom.style.width = '100%';
+        verticalBottom.style.height = '1px';
+        verticalBottom.style.bottom = '33%';
+        verticalBottom.style.left = '0px';
+        verticalBottom.style.position = 'absolute';
+        verticalBottom.style.backgroundColor = '#fff';
+        verticalBottom.style.zIndex = '522';
+        verticalBottom.style.opacity = '0.5';
+        //左上边线
+        let LeftTopSide = document.createElement('div');
+        LeftTopSide.style.width = '30px';
+        LeftTopSide.style.height = '4px';
+        LeftTopSide.style.top = '-4px';
+        LeftTopSide.style.left = '-4px';
+        LeftTopSide.style.position = 'absolute';
+        LeftTopSide.style.backgroundColor = '#fff';
+        LeftTopSide.style.zIndex = '522';
+        LeftTopSide.style.opacity = '1';
+        //上左边线
+        let TopListSide = document.createElement('div');
+        TopListSide.style.width = '4px';
+        TopListSide.style.height = '30px';
+        TopListSide.style.top = '-4px';
+        TopListSide.style.left = '-4px';
+        TopListSide.style.position = 'absolute';
+        TopListSide.style.backgroundColor = '#fff';
+        TopListSide.style.zIndex = '522';
+        TopListSide.style.opacity = '1';
+        //右上边线
+        let RightTopSide = document.createElement('div');
+        RightTopSide.style.width = '30px';
+        RightTopSide.style.height = '4px';
+        RightTopSide.style.top = '-4px';
+        RightTopSide.style.right = '-4px';
+        RightTopSide.style.position = 'absolute';
+        RightTopSide.style.backgroundColor = '#fff';
+        RightTopSide.style.zIndex = '522';
+        RightTopSide.style.opacity = '1';
+        //上右边线
+        let TopRightSide = document.createElement('div');
+        TopRightSide.style.width = '4px';
+        TopRightSide.style.height = '30px';
+        TopRightSide.style.top = '-4px';
+        TopRightSide.style.right = '-4px';
+        TopRightSide.style.position = 'absolute';
+        TopRightSide.style.backgroundColor = '#fff';
+        TopRightSide.style.zIndex = '522';
+        TopRightSide.style.opacity = '1';
+        //左下边线
+        let LeftBottomSide = document.createElement('div');
+        LeftBottomSide.style.width = '30px';
+        LeftBottomSide.style.height = '4px';
+        LeftBottomSide.style.bottom = '-4px';
+        LeftBottomSide.style.left = '-4px';
+        LeftBottomSide.style.position = 'absolute';
+        LeftBottomSide.style.backgroundColor = '#fff';
+        LeftBottomSide.style.zIndex = '522';
+        LeftBottomSide.style.opacity = '1';
+        //下左边线
+        let BottomListSide = document.createElement('div');
+        BottomListSide.style.width = '4px';
+        BottomListSide.style.height = '30px';
+        BottomListSide.style.bottom = '-4px';
+        BottomListSide.style.left = '-4px';
+        BottomListSide.style.position = 'absolute';
+        BottomListSide.style.backgroundColor = '#fff';
+        BottomListSide.style.zIndex = '522';
+        BottomListSide.style.opacity = '1';
+        //右下边线
+        let RightBottomSide = document.createElement('div');
+        RightBottomSide.style.width = '30px';
+        RightBottomSide.style.height = '4px';
+        RightBottomSide.style.bottom = '-4px';
+        RightBottomSide.style.right = '-4px';
+        RightBottomSide.style.position = 'absolute';
+        RightBottomSide.style.backgroundColor = '#fff';
+        RightBottomSide.style.zIndex = '522';
+        RightBottomSide.style.opacity = '1';
+        //下右边线
+        let BottomRightSide = document.createElement('div');
+        BottomRightSide.style.width = '4px';
+        BottomRightSide.style.height = '30px';
+        BottomRightSide.style.bottom = '-4px';
+        BottomRightSide.style.right = '-4px';
+        BottomRightSide.style.position = 'absolute';
+        BottomRightSide.style.backgroundColor = '#fff';
+        BottomRightSide.style.zIndex = '522';
+        BottomRightSide.style.opacity = '1';
+        //一起生成
+        box.appendChild(verticalLeft);
+        box.appendChild(verticalRight);
+        box.appendChild(verticalTop);
+        box.appendChild(verticalBottom);
+        box.appendChild(LeftTopSide);
+        box.appendChild(TopListSide);
+        box.appendChild(RightTopSide);
+        box.appendChild(TopRightSide);
+        box.appendChild(LeftBottomSide);
+        box.appendChild(BottomListSide);
+        box.appendChild(RightBottomSide);
+        box.appendChild(BottomRightSide);
+      }
+    },
+    //异步onload图片
+    onLoadImg(photoUrl: any) {
+      return new Promise(function (resolve, reject) {
+        let reader = new FileReader();
+        reader.readAsDataURL(photoUrl);
+        reader.onload = (e: any) => {
+          resolve(e.target['result']);
+        };
+      });
+    },
+    /**
+     * 载入文件
+     * template:
+     *    <h5-cropper hide-input ref="cropper">
+     *
+     * javascript:
+     *    this.$refs.cropper.loadFile()
+     *
+     * @param file
+     */
+    loadFile(file: any) {
+      if (file instanceof File) {
+        this.onLoadImg(file).then((base64: any) => {
+          this.img = base64;
+          setTimeout(() => {
+            this.config.autoCrop = true;
+            this.addSlide();
+          }, 10);
+        });
+      } else {
+        throw new Error('Arguments file is not File');
+      }
+    },
+    /**
+     *
+     * @param base64
+     */
+    loadBase64(base64: string) {
+      if (typeof base64 !== 'string') {
+        throw new Error('Arguments base64 is not string');
+      }
+      const base = base64.split(',');
+      if (!/^data:image\/(.*?);base64$/.test(base[0])) {
+        throw new Error('Arguments base64 MIME is not image/*');
+      }
+      // Base64 Regex @see https://learnku.com/articles/42295
+      if (
+        !/^[\/]?([\da-zA-Z]+[\/+]+)*[\da-zA-Z]+([+=]{1,2}|[\/])?$/.test(base[1])
+      ) {
+        throw new Error('Not standard base64');
+      }
+      this.img = base64;
+      setTimeout(() => {
+        this.config.autoCrop = true;
+        this.addSlide();
+      }, 10);
+    },
+    rotating(e: any) {
+      (this as any).$refs.cropper.rotateRight();
+      // document.getElementsByClassName("cropper-modal")[0].style = "background-color: rgba(0,0,0,0.5);transition: 0.88s";
+    },
+    canceltailor() {
+      this.img = '';
+      this.onCancelTailor();
+    },
+    tailoring() {
+      // 获取截图的base64数据
+      (this as any).$refs.cropper.getCropData((data: any) => {
+        this.getBase64Data(data);
+        this.getBlob(data);
+        this.img = '';
+        this.config.autoCrop = false;
+      });
+      // 获取截图的blob数据
+      (this as any).$refs.cropper.getCropBlob((data: BlobPart) => {
+        this.getBase64Data(data);
+        this.getBlob(data);
+        // Blob 转 File
+        const suffix = {
+          jpeg: 'jpg',
+          png: 'png',
+          webp: 'webp'
+        }[this.config.outputType];
+        const time = new Date().getTime();
+        const file = new File([data], `${time}.${suffix}`, {
+          type: `image/${this.config.outputType}`
+        });
+        this.getFile(file);
+        this.img = '';
+        this.config.autoCrop = false;
+      });
+    },
+    async upPhoto(e: any) {
+      let photoUrl = e.target.files[0];
+      (this as any).$refs.headInput.value = null;
+      if (photoUrl != undefined) {
+        this.imgOriginF(photoUrl);
+        this.img = (await this.onLoadImg(photoUrl)) as string;
+        this.config.autoCrop = true;
+        setTimeout(() => {
+          this.addSlide();
+        }, 20);
+      }
+    },
+    onCropMoving(e: any) {
+      // console.log('onCropMoving')
+    },
+    onImgMoving(e: any) {
+      // console.log('onCropMoving')
+    }
+  },
+  render() {
+    return (
+      <div class={[styles.upbtn, styles.uploadWarper]}>
+        {this.hideInput}
+        {!this.hideInput ? (
+          <input
+            style="opacity: 0;"
+            class={styles.upbtn}
+            type="file"
+            accept="image/*"
+            onChange={this.upPhoto}
+            ref="headInput"
+          />
+        ) : null}
+        {this.img != '' ? (
+          <div class={styles.bg}>
+            {this.config.ceilbutton ? (
+              <div class={styles.btndiv}>
+                <div
+                  class={styles.btn}
+                  onClick={this.canceltailor}
+                  style={{
+                    backgroundColor: this.config.cancelButtonBackgroundColor,
+                    color: this.config.cancelButtonTextColor
+                  }}>
+                  {this.config.cancelButtonText}
+                </div>
+                <div class={styles.img} onClick={this.rotating}></div>
+                <div
+                  class={styles.btn}
+                  onClick={this.tailoring}
+                  style={{
+                    backgroundColor: this.config.confirmButtonBackgroundColor,
+                    color: this.config.confirmButtonTextColor
+                  }}>
+                  {this.config.confirmButtonText}
+                </div>
+              </div>
+            ) : null}
+
+            <div class={styles.wrapper}>
+              <VueCropper
+                ref="cropper"
+                img={this.img}
+                outputSize={this.config.outputSize}
+                outputType={this.config.outputType}
+                info={this.config.info}
+                canScale={this.config.canScale}
+                autoCrop={this.config.autoCrop}
+                autoCropWidth={this.config.autoCropWidth}
+                autoCropHeight={this.config.autoCropHeight}
+                fixedBox={this.config.fixedBox}
+                fixed={this.config.fixed}
+                fixedNumber={this.config.fixedNumber}
+                full={this.config.full}
+                canMove={this.config.canMove}
+                canMoveBox={this.config.canMoveBox}
+                original={this.config.original}
+                centerBox={this.config.centerBox}
+                high={this.config.high}
+                infoTrue={this.config.infoTrue}
+                maxImgSize={this.config.maxImgSize}
+                enlarge={this.config.enlarge}
+                mode={this.config.mode}
+                onCropMoving={this.onCropMoving}
+                onImgMoving={this.onImgMoving}></VueCropper>
+            </div>
+
+            {!this.config.ceilbutton ? (
+              <div class={styles.btndiv}>
+                <div
+                  class={styles.btn}
+                  onClick={this.canceltailor}
+                  style={{
+                    backgroundColor: this.config.cancelButtonBackgroundColor,
+                    color: this.config.cancelButtonTextColor
+                  }}>
+                  {this.config.cancelButtonText}
+                </div>
+                <div class={styles.img} onClick={this.rotating}></div>
+                <div
+                  class={styles.btn}
+                  onClick={this.tailoring}
+                  style={{
+                    backgroundColor: this.config.confirmButtonBackgroundColor,
+                    color: this.config.confirmButtonTextColor
+                  }}>
+                  {this.config.confirmButtonText}
+                </div>
+              </div>
+            ) : null}
+          </div>
+        ) : null}
+      </div>
+    );
+  }
+});

+ 1 - 0
src/components/m-uploader/index.module.less

@@ -30,6 +30,7 @@
 
   .uploader {
     position: relative;
+    font-size: 0;
 
     &.default {
       :global {

+ 27 - 0
src/components/m-uploader/index.tsx

@@ -15,6 +15,7 @@ import iconUploader from '@common/images/icon-upload.png';
 // import iconUploadClose from '@common/images/icon-upload-close.png';
 import iconVideoDefault from '@common/images/icon-video-c.png';
 import request from '@/helpers/request';
+import ColCropper from '@components/m-cropper';
 import { getOssUploadUrl } from '@/state';
 import { getUploadSign, onOnlyFileUpload } from '@/helpers/oss-file-upload';
 
@@ -51,6 +52,15 @@ export default defineComponent({
       type: String,
       default: 'image/*'
     },
+    cropper: {
+      type: Boolean,
+      default: false
+    },
+    options: {
+      // 裁切需要参数
+      type: Object,
+      default: {}
+    },
     bucket: {
       type: String,
       default: 'gyt'
@@ -344,6 +354,23 @@ export default defineComponent({
               accept={this.accept}
             />
           )
+        ) : this.cropper && this.uploadType === 'IMAGE' ? (
+          <div class={[styles.uploader, styles[this.size]]}>
+            {this.modelValue.length > 0 ? (
+              <Image
+                fit="cover"
+                position="center"
+                class={styles.uploadImg}
+                src={this.modelValue[0] as any}
+              />
+            ) : (
+              <div class={styles.uploader}>
+                <Icon name={iconUploader} size="32" />
+                {/* <p class={styles.uploaderText}>{this.tips}</p> */}
+              </div>
+            )}
+            <ColCropper option={this.options} getFile={this.getFile} />
+          </div>
         ) : (
           <Uploader
             class={['van-uploader', styles.uploader, styles[this.size]]}

+ 2 - 0
src/shims-vue.d.ts

@@ -17,3 +17,5 @@ declare global {
 declare module 'tcplayer.js';
 
 declare module 'qrcode';
+
+declare module 'vue-cropper';

+ 1 - 0
src/views/creation/edit/index.tsx

@@ -70,6 +70,7 @@ export default defineComponent({
             <MUploader
               class={styles.muploader}
               // native
+              cropper
               deletable={false}
               v-model:modelValue={state.img}
             />

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff