소스 검색

头像裁剪

yusheng 3 일 전
부모
커밋
9d235dec3a
2개의 변경된 파일157개의 추가작업 그리고 86개의 파일을 삭제
  1. 155 86
      src/components/upload/WithView.vue
  2. 2 0
      src/views/system/organization/components/org-user-edit.vue

+ 155 - 86
src/components/upload/WithView.vue

@@ -1,113 +1,182 @@
 <template>
   <div>
+    <!-- 图片预览 -->
     <div class="img-view" v-if="dialogImageUrl">
       <img :src="dialogImageUrl" alt="" srcset="" />
     </div>
     <div class="placeholder-box" v-else>
       <img src="~@/assets/upload-placeholder.svg" alt="" />
+      <span>请上传签名</span>
     </div>
+
+    <!-- 操作按钮 -->
     <div class="btn-box">
-      <el-upload class="avatar-div" action="#" accept="image/png,image/jpeg" :show-file-list="false" ref="uploadRef"
-        :on-exceed="handleExceed" :limit="1" :http-request="handlSuccess" :multiple="false">
-        <el-button type="text">上传{{ assetName }}图片</el-button>
-      </el-upload>
+      <el-button type="text" @click="triggerFileInput" :loading="uploading">
+        上传{{ assetName }}图片
+      </el-button>
       <el-button type="text" @click="clearImg">清除图片</el-button>
     </div>
+
+    <!-- 隐藏的文件选择器 -->
+    <input
+      type="file"
+      ref="fileInput"
+      accept="image/png,image/jpeg"
+      style="display: none"
+      @change="handleFileChange"
+    />
+
+    <!-- ele-cropper-modal 裁剪弹窗 -->
+    <EleCropperDialog
+      :show.sync="showCropper"
+      :src="cropperSrc"
+      :aspect-ratio="1 / 1"
+      append-to-body
+      image-type="image/png"
+      :to-blob="true"
+      @crop="handleCropSuccess"
+    />
   </div>
 </template>
 
 <script>
-import { uploadFile, removeFile } from '@/api/system/file/index.js';
-import { getImageUrl } from '@/utils/file';
-export default {
-  props: {
-    assetName: {
-      type: String,
-      default: '设备'
+  import { uploadFile, removeFile } from '@/api/system/file/index.js';
+  import { getImageUrl } from '@/utils/file';
+
+  // 如果 ele-cropper-modal 没有全局注册,需要在此处导入
+  import EleCropperDialog from 'ele-admin/es/ele-cropper-dialog';
+
+  export default {
+    // 如果未全局注册,取消下面注释
+    components: { EleCropperDialog },
+    props: {
+      assetName: {
+        type: String,
+        default: '设备'
+      },
+      value: {
+        type: Object,
+        default: () => ({})
+      },
+      // 所属模块
+      module: {
+        type: String,
+        default: 'main'
+      }
     },
-    value: {
-      type: Object,
-      default: () => []
+    data() {
+      return {
+        showCropper: false, // 控制裁剪弹窗显示
+        cropperSrc: null, // 待裁剪图片的临时地址
+        uploading: false // 上传状态
+      };
     },
-    // 所属模块
-    module: {
-      type: String,
-      default: 'main'
-    }
-  },
-  data() {
-    return {};
-  },
-  computed: {
-    dialogImageUrl() {
-      return this.value?.storePath && getImageUrl(this.value.storePath);
-    }
-  },
-  methods: {
-    // 清空已上传的文件列表
-    clearUploadFiles() { },
-    //图片添加
-    async handlSuccess(params) {
-      let res = await uploadFile({
-        multiPartFile: params.file,
-        module: this.module
-      });
-      if (res?.data) {
-        this.$emit('input', res.data);
+    computed: {
+      dialogImageUrl() {
+        return this.value?.storePath && getImageUrl(this.value.storePath);
       }
     },
-    async clearImg() {
-      if(!this.value.id){
-          return
+    methods: {
+      // 触发隐藏的文件选择器
+      triggerFileInput() {
+        this.$refs.fileInput.click();
+      },
+
+      // 文件选择后的处理
+      handleFileChange(event) {
+        const file = event.target.files[0];
+        if (!file) return;
+
+        // 读取文件为 Data URL,用作裁剪器的图片源
+        const reader = new FileReader();
+        reader.onload = (e) => {
+          this.cropperSrc = e.target.result;
+          this.showCropper = true;
+          // 重置 input,允许重复选择同一文件
+          event.target.value = '';
+        };
+        reader.readAsDataURL(file);
+      },
+
+      // 裁剪成功回调
+      async handleCropSuccess(blob) {
+        // 因为设置了 :to-blob="true",所以直接拿到 Blob 对象
+        if (!blob) {
+          this.showCropper = false;
+          return;
+        }
+
+        this.uploading = true;
+        try {
+          const file = new File([blob], 'signature.png', { type: 'image/png' });
+          // const formData = new FormData();
+          // formData.append('multiPartFile', file);
+          // formData.append('module', this.module);
+
+          const res = await uploadFile({
+            multiPartFile: file,
+            module: this.module
+          });
+          if (res?.data) {
+            this.$emit('input', res.data);
+            this.$message.success('上传成功');
+          }
+        } catch (error) {
+          console.error(error);
+          this.$message.error('上传失败,请稍后重试');
+        } finally {
+          this.uploading = false;
+          this.showCropper = false;
+        }
+      },
+
+      // 清除图片
+      async clearImg() {
+        if (!this.value.id) return;
+        try {
+          await removeFile({ fileId: this.value.id });
+          this.$emit('input', {});
+          this.$message.success('已清除');
+        } catch (error) {
+          this.$message.error('清除失败');
+        }
       }
-      await removeFile({ fileId: this.value.id });
-      this.$emit('input', {});
-      this.$refs.uploadRef.clearFiles();
-    },
-    // 限制上传的数量
-    handleExceed(files, fileList) {
-      this.$message.warning(`最多允许上传一张图片!`);
     }
-  }
-};
+  };
 </script>
+
 <style lang="scss" scoped>
-.img-view {
-  width: 280px;
-  height: 342px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  border-width: 1px;
-  border-style: solid;
-  border-color: rgba(215, 215, 215, 1);
-
-  img {
-    max-width: 100%;
-    max-height: 100%;
+  .img-view {
+    width: 280px;
+    height: 342px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    border: 1px solid #d7d7d7;
+
+    img {
+      max-width: 100%;
+      max-height: 100%;
+    }
   }
-}
-
-.placeholder-box {
-  width: 250px;
-  height: 200px;
-  text-align: center;
-
-  background-color: rgba(242, 242, 242, 1);
-  box-sizing: border-box;
-  border-width: 1px;
-  border-style: solid;
-  border-color: rgba(215, 215, 215, 1);
-  padding-top: 60px;
-
-  img {
-    width: 100px;
-    height: 100px;
+
+  .placeholder-box {
+    width: 250px;
+    height: 200px;
+    text-align: center;
+    background-color: #f2f2f2;
+    box-sizing: border-box;
+    border: 1px solid #d7d7d7;
+    padding-top: 60px;
+
+    img {
+      width: 100px;
+      height: 100px;
+    }
   }
-}
 
-.btn-box {
-  display: flex;
-  justify-content: space-around;
-}
+  .btn-box {
+    display: flex;
+    justify-content: space-around;
+  }
 </style>

+ 2 - 0
src/views/system/organization/components/org-user-edit.vue

@@ -2,6 +2,8 @@
 <template>
   <ele-modal
     width="60%"
+      append-to-body
+
     :visible="visible"
     :close-on-click-modal="false"
     custom-class="ele-dialog-form"