vue3中上传组件upload简单使用

1、upload组件上传

主页面

html 复制代码
<template>
  <el-col :span="24">
    <el-form-item label="个人风采" prop="avatar">
      <imageCut
        v-model="serviceForm.avatar"
        :fixedNumber="[86, 114]"
        :disabled="disabled1"
      >
      </imageCut> 
    </el-form-item>
    <el-form-item label="姓名" prop="name">
      <el-input
        v-model="serviceForm.name"
        :disabled="disabled1"
      />
    </el-form-item>
    <el-form-item label="职称" prop="jobTitle">
      <el-input
        v-model="serviceForm.jobTitle"
        :disabled="disabled1"
      />
    </el-form-item>
  </el-col>
</template>
<script setup>
import imageCut from "@/components/imageCutUpload/index";

</script>

2、组件主要逻辑页面(components/imageCutUpload/index.vue)

html 复制代码
<template>
  <div class="component-upload-image">
    <el-upload
      ref="imageUpload"
      :disabled="disabled"
      action=""
      list-type="picture-card"
      :auto-upload="false"
      :show-file-list="show"
      :file-list="fileList"
      :before-remove="handleDelete"
      :class="{ hide: fileList.length >= limit }"
      :on-change="handleChangeUpload"
      :on-preview="handlePictureCardPreview"
      :on-success="handleSuccess"
    >
      <el-icon class="avatar-uploader-icon">
        <plus />
      </el-icon>
    </el-upload>
    <!-- 上传提示 -->
    <div class="el-upload__tip">
      请上传
      <template v-if="fileType">
        格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
      </template>
      的文件
    </div>
    <el-dialog
      v-model="showCropper"
      title="图片裁剪预览"
      width="800px"
      align-center
      append-to-body
      destory-on-close
    >
      <div class="box">
        <div class="left">
          <VueCropper
            v-if="showCropper"
            ref="cropper"
            :img="option.img"
            :outputSize="option.outputSize"
            :outputType="option.outputType"
            :info="option.info"
            :full="option.full"
            :canMove="option.canMove"
            :canMoveBox="option.canMoveBox"
            :original="option.original"
            :autoCrop="option.autoCrop"
            :fixed="option.fixed"
            :fixedNumber="option.fixedNumber"
            :centerBox="option.centerBox"
            :infoTrue="option.infoTrue"
            :fixedBox="option.fixedBox"
            :high="option.high"
            :mode="option.mode"
            @realTime="realTime"
          >
          </VueCropper>
        </div>
        <div class="right">
          <h1 class="imgshow">图片展示</h1>
          <img :src="previewImg" alt="" class="previewImg" />
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="uploadImg">确 定</el-button>
          <el-button @click="cancel">取 消</el-button>
        </div>
      </template>
    </el-dialog>
    <el-dialog
      v-model="dialogVisible"
      title="预览"
      align-center
      width="800px"
      append-to-body
    >
      <img
        :src="dialogImageUrl"
        style="display: block; max-width: 100%; margin: 0 auto"
      />
    </el-dialog>
  </div>
</template>

<script setup>
import "vue-cropper/dist/index.css"; //引入裁剪
import { VueCropper } from "vue-cropper";
import { uploadImages,getstorage } from "@/api/common";
const props = defineProps({
  modelValue: [String, Object, Array],
  // 图片数量限制
  limit: {
    type: Number,
    default: 1,
  },
  // 大小限制(MB)
  fileSize: {
    type: Number,
    default: 5,
  },
  fileType: {
    type: Array,
    default: () => ["png", "jpg", "jpeg"],
  },
  // 是否显示提示
  isShowTip: {
    type: Boolean,
    default: true,
  },
  //是否禁用
  disabled: {
    type: Boolean,
    default: false,
  },
  fixedNumber: {
    type: Array,
    default: [1, 1],
  },
  contentType: {
    type: String,
    default: "RoleCert",
  },
});

const { proxy } = getCurrentInstance();
const emit = defineEmits();
const number = ref(0);
const show = ref(false);
const uploadList = ref([]);
const dialogImageUrl = ref("");
const dialogVisible = ref(false);
const baseUrl = import.meta.env.VITE_APP_BASE_URL;
const fileList = ref([]);
// const multipartFile = ref();
const showTip = computed(
  () => props.isShowTip && (props.fileType || props.fileSize)
);
const option = ref({
  //裁剪參數
  img: "",
  outputSize: 1,
  info: false, // 裁剪框的大小信息
  outputType: "jpg", // 裁剪生成图片的格式
  canScale: false, // 图片是否允许滚轮缩放
  autoCrop: true, // 是否默认生成截图框
  // high: true, // 是否按照设备的dpr 输出等比例图片
  fixedBox: true, // 固定截图框大小 不允许改变
  fixed: true, // 是否开启截图框宽高固定比例
  fixedNumber: props.fixedNumber, // 截图框的宽高比例
  full: true, // 是否输出原图比例的截图
  canMoveBox: true, // 截图框能否拖动
  original: false, // 上传图片按照原始比例渲染
  centerBox: false, // 截图框是否被限制在图片里面
  infoTrue: true, // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
  mode: "100% auto", // 图片默认渲染方式
});
const showCropper = ref(false);
const previewImg = ref("");
const cropper = ref(null);
const uid = ref("");
const filename = ref("");
const file = ref(undefined);
watch(
  () => props.modelValue,
  (val) => {
    if (val) {
      show.value = true;
      // 首先将值转为数组
      const list = Array.isArray(val) ? val : props.modelValue.split(",");
      // 然后将数组转为对象数组
      fileList.value = list.map((item, index) => {
        if (typeof item === "string") {
          item = { name: "img" + index, url: item };
        }
        return item;
      });
    } else {
      show.value = false;
      fileList.value = [];
      return [];
    }
  },
  { deep: true, immediate: true }
);

function handleChangeUpload(file, fileList) {
  console.log("上传进来了")
  let isImg = false;
  if (fileList.length > props.limit) {
    show.value = false;
  }
  uid.value = file.raw.uid;
  filename.value = file.raw.name;
  if (props.fileType.length) {
    let fileExtension = "";
    if (file.raw.name.lastIndexOf(".") > -1) {
      fileExtension = file.raw.name.slice(file.raw.name.lastIndexOf(".") + 1);
    }
    isImg = props.fileType.some((type) => {
      if (file.raw.type.indexOf(type) > -1) return true;
      if (fileExtension && fileExtension.indexOf(type) > -1) return true;
      return false;
    });
  } else {
    isImg = file.raw.type.indexOf("image") > -1;
  }
  if (!isImg) {
    proxy.$modal.msgError(
      `文件格式不正确, 请上传${props.fileType.join("/")}图片格式文件!`
    );
    return false;
  }
  // nextTick(() => {
  var reader = new FileReader();
  reader.readAsDataURL(file.raw);
  reader.onload = (e) => {   
    option.value.img = e.target.result;
    showCropper.value = true;
  };
  // });
}

function handleSuccess(file) {
  console.log(file)
  console.log("成功");
}

// 删除图片
function handleDelete(file) {
  const findex = fileList.value.map((f) => f.name).indexOf(file.name);
  if (findex > -1 && uploadList.value.length === number.value) {
    fileList.value.splice(findex, 1);
    emit("update:modelValue", listToString(fileList.value));
    return false;
  }
}
function cancel() {
  showCropper.value = false;
}
// 预览
function handlePictureCardPreview(file) {
  dialogImageUrl.value = file.url;
  dialogVisible.value = true;
}

// 对象转成指定字符串分隔
function listToString(list, separator) {
  let strs = "";
  separator = separator || ",";
  for (let i in list) {
    if (undefined !== list[i].url && list[i].url.indexOf("blob:") !== 0) {
      strs += list[i].url.replace(baseUrl, "") + separator;
    }
  }

  return strs != "" ? strs.substr(0, strs.length - 1) : "";
}

function blobToDataURI(blob, callback) {
  var reader = new FileReader();
  reader.readAsDataURL(blob);
  reader.onload = function (e) {
    callback(e.target.result);
  };
}
//获取裁剪图片
function realTime(data) {
  cropper.value.getCropBlob((data) => {
    // , {
    //   uid: uid.value,
    //   type: "image/png",
    //   lastModified: Date.now(),
    // }
    file.value = new File([data], filename.value, {
      uid: uid.value,
      type: "image/png",
      lastModified: Date.now(),
    });
    blobToDataURI(data, function (res) {
      previewImg.value = res;
    });
  });
}
//uploadImg

function uploadImg() {
  proxy.$modal.loading("正在上传图片,请稍候...");
  number.value++;
  let formData = new FormData();
  formData.append("contentType", props.contentType);
  formData.append("fileType", 2);
  formData.append("file", file.value);
  // data = qs.stringify(data);
  uploadImages(formData)
    .then((res) => {
      if (res) {
        proxy.$modal.closeLoading();
        show.value = true;
        showCropper.value = false;
        
        //uploadedSuccessfully();
        getstorage(props.contentType).then((res2)=>{
          //baseUrl=res2;
          //baseUrl=baseUrl.replace(props.contentType, "");
          res2=res2.replace(props.contentType, "");
          console.log(baseUrl);
          uploadList.value.push({ name: "expert", url: res2+res });//提交拼接下完整url
          uploadedSuccessfully();
        });
        
      } else {
        number.value--;
        proxy.$modal.closeLoading();
        showCropper.value = false;
        proxy.$modal.msgError("上传图片失败");
        proxy.$refs.imageUpload.handleRemove(file.value);
        uploadedSuccessfully();
      }
    })
    .catch(() => {
      number.value--;
      proxy.$modal.closeLoading();
      showCropper.value = false;
      proxy.$modal.msgError("上传图片失败");
      proxy.$refs.imageUpload.handleRemove(file.value);
      uploadedSuccessfully();
    })
    .finally(() => {
      // previewImg.value = "1";
    });
}


// 上传结束处理
function uploadedSuccessfully() {
  if (number.value > 0 && uploadList.value.length === number.value) {
    fileList.value = fileList.value
      .filter((f) => f.url !== undefined)
      .concat(uploadList.value);
    uploadList.value = [];
    number.value = 0;
    emit("update:modelValue", listToString(fileList.value));
  }
}
</script>
<style scoped lang="scss">
.previewImg {
  width: 100%;
}

// .el-upload--picture-card 控制加号部分
:deep(.hide .el-upload--picture-card) {
  display: none;
}

.box {
  width: 100%;
  height: 500px;
  display: flex;

  .left {
    width: 68%;
  }

  .right {
    flex: 1;
    padding-left: 20px;

    .imgshow {
      line-height: var(--el-dialog-font-line-height);
      font-size: var(--el-dialog-title-font-size);
      color: var(--el-text-color-primary);
    }
  }
}
</style>

3、接口文件代码(common.js)

javascript 复制代码
import request from "@/utils/request";

export function uploadImages(data) {
  return request({
    url: "/api/base/storage/upload",
    method: "post",
    headers: { "Content-Type": "multipart/form-data" },
    data,
    isGreen:true
  });
}

注意,以下代码是由于后端接口返回的图片路径是不完整的,所以又简单调用了个接口才拼接成完整的路径,若返回的是完整的则不需要此处代码,只要uploadedSuccessfully();

getstorage(props.contentType).then((res2)=>{

//baseUrl=res2;

//baseUrl=baseUrl.replace(props.contentType, "");

res2=res2.replace(props.contentType, "");

console.log(baseUrl);

uploadList.value.push({ name: "expert", url: res2+res });//提交拼接下完整url

uploadedSuccessfully();

});

相关推荐
Sheldon一蓑烟雨任平生1 小时前
Vue3 插件(可选独立模块复用)
vue.js·vue3·插件·vue3 插件·可选独立模块·插件使用方式·插件中的依赖注入
鱼与宇3 小时前
苍穹外卖-VUE
前端·javascript·vue.js
用户47949283569153 小时前
Safari 中文输入法的诡异 Bug:为什么输入 @ 会变成 @@? ## 开头 做 @ 提及功能的时候,测试同学用 Safari 测出了个奇怪的问题
前端·javascript·浏览器
裴嘉靖3 小时前
Vue 生成 PDF 完整教程
前端·vue.js·pdf
毕设小屋vx ylw2824263 小时前
Java开发、Java Web应用、前端技术及Vue项目
java·前端·vue.js
冴羽4 小时前
今日苹果 App Store 前端源码泄露,赶紧 fork 一份看看
前端·javascript·typescript
蒜香拿铁4 小时前
Angular【router路由】
前端·javascript·angular.js
时间的情敌4 小时前
Vite 大型项目优化方案
vue.js
西洼工作室5 小时前
高效管理搜索历史:Vue持久化实践
前端·javascript·vue.js
樱花开了几轉5 小时前
element ui下拉框踩坑
开发语言·javascript·ui