|
|
@@ -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>
|