|
|
@@ -0,0 +1,173 @@
|
|
|
+<template>
|
|
|
+ <el-upload
|
|
|
+ v-model:file-list="fileList"
|
|
|
+ class="single-upload"
|
|
|
+ :action="uploadUrl"
|
|
|
+ :show-file-list="false"
|
|
|
+ :on-success="onSuccess"
|
|
|
+ :before-upload="beforeUpload"
|
|
|
+ accept="image/*"
|
|
|
+ @on-change="onChange"
|
|
|
+ >
|
|
|
+ <template #trigger>
|
|
|
+ <div
|
|
|
+ class="w-40 h-40 bg-neutral-100 dark:bg-neutral-900 rounded-md cursor-pointer border border-dashed border-gray-500 hover:border-blue-500 flex justify-center items-center overflow-hidden relative"
|
|
|
+ v-loading="loading"
|
|
|
+ >
|
|
|
+ <img v-if="imageUrl" :src="imageUrl" class="w-full h-full object-cover" />
|
|
|
+ <Plus v-if="!imageUrl && !loading" class="w-10 h-10 text-gray-400"> </Plus>
|
|
|
+ <div v-if="imageUrl" class="absolute bottom-4 left-0 right-0 flex items-center justify-center">
|
|
|
+ <Eye
|
|
|
+ class="w-5 h-5 text-neutral-100 drop-shadow-md cursor-pointer"
|
|
|
+ @click.stop="showPreview = true"
|
|
|
+ />
|
|
|
+ <Trash class="w-5 h-5 text-neutral-100 drop-shadow-md cursor-pointer ml-4" @click.stop="clear" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <template #tip>
|
|
|
+ <div class="el-upload__tip">
|
|
|
+ <slot></slot>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-upload>
|
|
|
+ <ElImageViewer v-if="showPreview" :url-list="[imageUrl]" teleported @close="showPreview = false" />
|
|
|
+</template>
|
|
|
+<script setup>
|
|
|
+import { ref, unref, watch } from 'vue'
|
|
|
+import resolveUrl from 'resolve-url'
|
|
|
+import { Plus, Upload, Trash, Eye } from '@vicons/tabler'
|
|
|
+import { useImageSize } from '@/utils/imageSize'
|
|
|
+const props = defineProps({
|
|
|
+ modelValue: String,
|
|
|
+ usePrefix: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true
|
|
|
+ },
|
|
|
+ url: {
|
|
|
+ type: String
|
|
|
+ },
|
|
|
+ maxWidth: {
|
|
|
+ type: Number,
|
|
|
+ default: -1
|
|
|
+ },
|
|
|
+ maxHeight: {
|
|
|
+ type: Number,
|
|
|
+ default: -1
|
|
|
+ },
|
|
|
+ maxSize: {
|
|
|
+ type: Number,
|
|
|
+ default: 1024 * 1024
|
|
|
+ }
|
|
|
+})
|
|
|
+const emit = defineEmits(['update:modelValue'])
|
|
|
+const uploadUrl = resolveUrl(import.meta.env.VITE_API_BASE_URL, '/api/file/upload')
|
|
|
+const loading = ref(false)
|
|
|
+const fileList = ref([])
|
|
|
+const imageUrl = ref(null)
|
|
|
+const showPreview = ref(false)
|
|
|
+
|
|
|
+if (props.modelValue) {
|
|
|
+ imageUrl.value = unref(props.modelValue)
|
|
|
+ fileList.value = [
|
|
|
+ {
|
|
|
+ url: unref(props.modelValue)
|
|
|
+ }
|
|
|
+ ]
|
|
|
+}
|
|
|
+watch(
|
|
|
+ () => props.modelValue,
|
|
|
+ (val) => {
|
|
|
+ imageUrl.value = val
|
|
|
+ fileList.value = [
|
|
|
+ {
|
|
|
+ url: val
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+)
|
|
|
+function onSuccess(res, file) {
|
|
|
+ loading.value = false
|
|
|
+ imageUrl.value = URL.createObjectURL(file.raw)
|
|
|
+ emit('update:modelValue', res.url)
|
|
|
+}
|
|
|
+function onError(err, file, fileList) {
|
|
|
+ loading.value = false
|
|
|
+}
|
|
|
+async function beforeUpload(file) {
|
|
|
+ if (!/^image/.test(file.type)) {
|
|
|
+ this.$message.error('只能上传图片格式!')
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ if (this.maxSize > 0 && file.size > this.maxSize) {
|
|
|
+ this.$message.error('上传图片大小不能超过 ' + this.maxSize / 1024 + 'KB!')
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ const { width, height } = await useImageSize(URL.createObjectURL(file))
|
|
|
+ if (props.maxWidth > 0 && width > props.maxWidth) {
|
|
|
+ this.$message.error('上传图片宽度不能超过 ' + props.maxWidth + 'px!')
|
|
|
+ return false
|
|
|
+ } else if (props.maxHeight > 0 && height > props.maxHeight) {
|
|
|
+ this.$message.error('上传图片高度不能超过 ' + props.maxHeight + 'px!')
|
|
|
+ return false
|
|
|
+ } else {
|
|
|
+ loading.value = true
|
|
|
+ return true
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function onChange(e) {
|
|
|
+ console.log(e)
|
|
|
+}
|
|
|
+function clear() {
|
|
|
+ emit('update:modelValue', null)
|
|
|
+}
|
|
|
+</script>
|
|
|
+<style lang="less" scoped>
|
|
|
+.single-uploader-icon {
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+ border: 1px dashed var(--el-border-color);
|
|
|
+ border-radius: var(--el-border-radius-base);
|
|
|
+ background-color: var(--el-bg-color);
|
|
|
+}
|
|
|
+
|
|
|
+.upload-image {
|
|
|
+ height: 178px;
|
|
|
+ display: block;
|
|
|
+ border: 1px dashed #d9d9d9;
|
|
|
+ border-radius: 6px;
|
|
|
+ cursor: pointer;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ border-color: #409eff;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.wrapper {
|
|
|
+ position: relative;
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.single-upload .el-upload {
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.loading {
|
|
|
+ position: absolute;
|
|
|
+ top: 1px;
|
|
|
+ bottom: 1px;
|
|
|
+ left: 1px;
|
|
|
+ right: 1px;
|
|
|
+ margin: auto;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: rgba(255, 255, 255, 0.6);
|
|
|
+ color: #333;
|
|
|
+ font-size: 24px;
|
|
|
+ border-radius: 7px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+</style>
|