<script lang="ts" setup>
|
import type { UploadChangeParam } from 'ant-design-vue';
|
|
import { computed, ref, unref, watch } from 'vue';
|
|
import { useGlobSetting } from '@jnpf/hooks';
|
import { createImgPreview } from '@jnpf/ui';
|
|
import { $t } from '@vben/locales';
|
import { useAccessStore, useUserStore } from '@vben/stores';
|
|
import { DeleteOutlined, EyeOutlined, LoadingOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
import { Upload as AUpload, Form, message } from 'ant-design-vue';
|
|
import { units, uploadImgSingleProps } from './props';
|
|
defineOptions({ inheritAttrs: false, name: 'JnpfUploadImgSingle' });
|
const props = defineProps(uploadImgSingleProps);
|
const emit = defineEmits(['update:value', 'change']);
|
|
// eslint-disable-next-line regexp/no-unused-capturing-group
|
const base64WithPrefixRegex = /^data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+);base64,([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$/;
|
const accessStore = useAccessStore();
|
const userStore = useUserStore();
|
const globSetting = useGlobSetting();
|
const fileList = ref([]);
|
const imageUrl = ref<string>('');
|
const loading = ref<boolean>(false);
|
const apiUrl = ref(globSetting.apiURL);
|
const formItemContext = Form.useInjectFormItemContext();
|
|
const getUrlPrefix = computed(() => (props.actionPrefix ? props.actionPrefix : apiUrl.value));
|
const getAction = computed(() => (props.action ? (props.actionPrefix || '') + props.action : `${globSetting.uploadURL}/${props.type}`));
|
const getHeaders = computed(() => ({ Authorization: accessStore.accessToken as string }));
|
const getImgSrc = computed<string>(() => {
|
if (base64WithPrefixRegex.test(imageUrl.value)) return imageUrl.value;
|
const userInfo: any = userStore.getUserInfo || {};
|
const securityKey = userInfo?.securityKey || '';
|
const url = getUrlPrefix.value + imageUrl.value;
|
if (!securityKey) return url;
|
const realUrl = `${url + (url.includes('?') ? '&' : '?')}s=${securityKey}`;
|
return realUrl;
|
});
|
const getImgList = computed<string[]>(() => (getImgSrc.value ? [getImgSrc.value] : []));
|
|
watch(
|
() => unref(props.value),
|
(val) => {
|
imageUrl.value = val;
|
},
|
{ immediate: true },
|
);
|
|
function beforeUpload(file) {
|
const isAccept = /image\/*/.test(file.type);
|
if (!isAccept) {
|
message.error($t('component.upload.uploadImg'));
|
return isAccept;
|
}
|
if (!props.fileSize) return true;
|
const unitNum = units[props.sizeUnit];
|
const isRightSize = file.size / unitNum < props.fileSize;
|
if (!isRightSize) {
|
message.error(`图片大小超过${props.fileSize}${props.sizeUnit}`);
|
return false;
|
}
|
loading.value = true;
|
return true;
|
}
|
function handleChange({ file }: UploadChangeParam) {
|
if (file.status === 'uploading') return (loading.value = true);
|
if (file.status === 'error') {
|
loading.value = false;
|
message.error($t('component.upload.uploadError'));
|
return;
|
}
|
if (file.status === 'done') {
|
loading.value = false;
|
if (file.response.code === 200) {
|
imageUrl.value = file.response.data.url;
|
emit('update:value', unref(imageUrl));
|
emit('change', unref(imageUrl));
|
formItemContext.onFieldChange();
|
} else {
|
message.error(file.response.msg);
|
}
|
}
|
}
|
function handlePreview() {
|
createImgPreview({ imageList: unref(getImgList) });
|
}
|
function handleRemove() {
|
emit('update:value', '');
|
emit('change', '');
|
formItemContext.onFieldChange();
|
}
|
</script>
|
|
<template>
|
<div class="single-img-container">
|
<div v-if="imageUrl" class="ant-upload-list ant-upload-list-picture-card">
|
<div class="ant-upload-list-picture-card-container">
|
<div class="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture-card">
|
<div class="ant-upload-list-item-info">
|
<a class="ant-upload-list-item-thumbnail">
|
<img :src="getImgSrc" class="ant-upload-list-item-image" />
|
</a>
|
</div>
|
<span class="ant-upload-list-item-actions">
|
<EyeOutlined @click="handlePreview" />
|
<DeleteOutlined v-if="!disabled" @click="handleRemove" />
|
</span>
|
</div>
|
</div>
|
</div>
|
<AUpload
|
v-else
|
v-model:file-list="fileList"
|
:accept="accept"
|
:action="getAction"
|
:before-upload="beforeUpload"
|
:class="$attrs.class"
|
:disabled="disabled"
|
:headers="getHeaders"
|
:show-upload-list="false"
|
class="img-uploader"
|
list-type="picture-card"
|
@change="handleChange">
|
<div>
|
<LoadingOutlined v-if="loading" />
|
<PlusOutlined v-else />
|
<div v-if="tipText" class="ant-upload-text">{{ tipText }}</div>
|
<div v-if="subTipText" class="ant-upload-text ant-upload-sub-text">{{ subTipText }}</div>
|
</div>
|
</AUpload>
|
</div>
|
</template>
|
<style lang="scss" scoped>
|
.single-img-container {
|
width: 100px;
|
height: 100px;
|
|
.ant-upload-list {
|
.ant-upload-list-picture-card-container {
|
width: 100px;
|
height: 100px;
|
margin: 0;
|
|
.ant-upload-list-item {
|
position: relative;
|
height: 100%;
|
padding: 0;
|
overflow: hidden;
|
border-radius: var(--radius);
|
|
&:hover {
|
.ant-upload-list-item-info::before {
|
opacity: 1;
|
}
|
}
|
|
.ant-upload-list-item-info {
|
position: relative;
|
height: 100%;
|
overflow: hidden;
|
|
&::before {
|
position: absolute;
|
z-index: 1;
|
width: 100%;
|
height: 100%;
|
content: ' ';
|
background-color: rgb(0 0 0 / 45%);
|
opacity: 0;
|
transition: all 0.3s;
|
}
|
|
.ant-upload-list-item-thumbnail {
|
height: 100%;
|
|
.ant-upload-list-item-image {
|
width: 100%;
|
height: 100%;
|
object-fit: unset;
|
}
|
}
|
}
|
|
.ant-upload-list-item-actions {
|
position: absolute;
|
top: 50%;
|
left: 50%;
|
z-index: 10;
|
white-space: nowrap;
|
opacity: 0;
|
transform: translate(-50%, -50%);
|
transition: all 0.3s;
|
|
&:hover {
|
opacity: 1;
|
}
|
|
.anticon-eye,
|
.anticon-delete {
|
margin: 0 10px;
|
font-size: 20px;
|
color: rgb(255 255 255 / 85%);
|
}
|
}
|
}
|
}
|
}
|
|
.img-uploader {
|
:deep(.ant-upload) {
|
width: 100px;
|
height: 100px;
|
|
.anticon {
|
font-size: 26px;
|
color: #8c939d;
|
}
|
|
.ant-upload-text {
|
margin-top: 10px;
|
line-height: 18px;
|
color: #8c939d;
|
|
&.ant-upload-sub-text {
|
margin-top: 0;
|
font-size: 12px;
|
}
|
}
|
}
|
}
|
}
|
</style>
|