yys 1 týždeň pred
rodič
commit
1dcf9a4bde
1 zmenil súbory, kde vykonal 241 pridanie a 0 odobranie
  1. 241 0
      src/components/SingleImageUpload/index.vue

+ 241 - 0
src/components/SingleImageUpload/index.vue

@@ -0,0 +1,241 @@
+<template>
+  <div class="single-image-upload" :class="{ 'is-contain': contain }">
+    <el-upload
+      class="image-uploader"
+      :action="uploadUrl"
+      :headers="uploadHeaders"
+      :show-file-list="false"
+      :on-success="handleSuccess"
+      :before-upload="beforeUpload"
+      accept="image/jpeg,image/png,image/jpg"
+    >
+      <div v-if="value" class="image-preview">
+        <img :src="value" :alt="text.preview" />
+        <div class="image-overlay">
+          <i class="el-icon-camera"></i>
+          <span>{{ text.replaceImage }}</span>
+        </div>
+      </div>
+      <div v-else class="upload-placeholder">
+        <div class="upload-icon-wrap">
+          <i class="el-icon-plus"></i>
+        </div>
+        <span class="upload-text">{{ placeholder }}</span>
+        <span v-if="tip" class="upload-tip">{{ tip }}</span>
+      </div>
+    </el-upload>
+    <el-button
+      v-if="value && clearable"
+      type="text"
+      size="mini"
+      icon="el-icon-delete"
+      class="clear-btn"
+      @click="handleClear"
+    >{{ text.clear }}</el-button>
+  </div>
+</template>
+
+<script>
+import { getToken } from "@/utils/auth";
+
+const TEXT = {
+  preview: "\u9884\u89c8",
+  replaceImage: "\u66f4\u6362\u56fe\u7247",
+  clear: "\u6e05\u9664",
+  defaultPlaceholder: "\u70b9\u51fb\u4e0a\u4f20",
+  defaultTip: "jpg/png\uff0c\u4e0d\u8d85\u8fc71MB",
+  uploadFailed: "\u4e0a\u4f20\u5931\u8d25",
+  invalidFormat: "\u8bf7\u4e0a\u4f20 JPG \u6216 PNG \u683c\u5f0f\u56fe\u7247",
+  sizeExceeded: "\u4e0a\u4f20\u56fe\u7247\u5927\u5c0f\u4e0d\u80fd\u8d85\u8fc7",
+};
+
+export default {
+  name: "SingleImageUpload",
+  props: {
+    value: {
+      type: String,
+      default: "",
+    },
+    size: {
+      type: String,
+      default: "avatar",
+    },
+    contain: {
+      type: Boolean,
+      default: false,
+    },
+    placeholder: {
+      type: String,
+      default: TEXT.defaultPlaceholder,
+    },
+    tip: {
+      type: String,
+      default: TEXT.defaultTip,
+    },
+    clearable: {
+      type: Boolean,
+      default: true,
+    },
+    maxSize: {
+      type: Number,
+      default: 1,
+    },
+  },
+  data() {
+    return {
+      uploadUrl: process.env.VUE_APP_BASE_API + "/common/uploadOSS",
+      text: TEXT,
+    };
+  },
+  computed: {
+    uploadHeaders() {
+      return {
+        Authorization: "Bearer " + getToken(),
+      };
+    },
+  },
+  methods: {
+    handleSuccess(res) {
+      if (res.code === 200) {
+        this.$emit("input", res.url);
+      } else {
+        this.$message.error(res.msg || TEXT.uploadFailed);
+      }
+    },
+    beforeUpload(file) {
+      const isImage = ["image/jpeg", "image/png", "image/jpg"].includes(file.type);
+      if (!isImage) {
+        this.$message.error(TEXT.invalidFormat);
+        return false;
+      }
+      const isLt = file.size / 1024 / 1024 < this.maxSize;
+      if (!isLt) {
+        this.$message.error(`${TEXT.sizeExceeded} ${this.maxSize}MB`);
+      }
+      return isLt;
+    },
+    handleClear() {
+      this.$emit("input", "");
+    },
+  },
+};
+</script>
+
+<style scoped lang="scss">
+.single-image-upload {
+  display: inline-flex;
+  flex-direction: column;
+  align-items: flex-start;
+}
+
+.image-uploader ::v-deep .el-upload {
+  border: none;
+  cursor: pointer;
+  overflow: hidden;
+  line-height: normal;
+}
+
+.upload-placeholder,
+.image-preview {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  width: 160px;
+  height: 160px;
+  background: #fafbfc;
+  border: 1px dashed #dcdfe6;
+  border-radius: 8px;
+  transition: border-color 0.25s, box-shadow 0.25s, background 0.25s;
+  overflow: hidden;
+}
+
+.image-uploader ::v-deep .el-upload:hover .upload-placeholder,
+.image-uploader ::v-deep .el-upload:hover .image-preview {
+  border-color: #409eff;
+  background: #f0f7ff;
+  box-shadow: 0 2px 12px rgba(64, 158, 255, 0.12);
+}
+
+.upload-icon-wrap {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  background: #ecf5ff;
+  margin-bottom: 8px;
+
+  i {
+    font-size: 18px;
+    color: #409eff;
+  }
+}
+
+.upload-text {
+  font-size: 13px;
+  color: #606266;
+  line-height: 1.4;
+}
+
+.upload-tip {
+  margin-top: 4px;
+  font-size: 12px;
+  color: #c0c4cc;
+  line-height: 1.4;
+}
+
+.image-preview {
+  position: relative;
+  padding: 0;
+
+  img {
+    display: block;
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+  }
+}
+
+.image-overlay {
+  position: absolute;
+  inset: 0;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 4px;
+  background: rgba(0, 0, 0, 0.45);
+  color: #fff;
+  opacity: 0;
+  transition: opacity 0.25s;
+
+  i {
+    font-size: 22px;
+  }
+
+  span {
+    font-size: 12px;
+  }
+}
+
+.image-preview:hover .image-overlay {
+  opacity: 1;
+}
+
+.clear-btn {
+  margin-top: 6px;
+  padding: 0;
+  color: #f56c6c;
+
+  &:hover {
+    color: #f78989;
+  }
+}
+
+.is-contain .image-preview img {
+  object-fit: contain;
+  background: #fff;
+}
+</style>