367 lines
9.0 KiB
Vue
367 lines
9.0 KiB
Vue
<template>
|
|
<n-space vertical>
|
|
<n-button @click="showModal = true" size="small">
|
|
<template #icon
|
|
><n-icon><PlusOutlined /></n-icon> </template
|
|
>选择{{ buttonText }}
|
|
</n-button>
|
|
<div class="w-full">
|
|
<div class="upload">
|
|
<div class="upload-card" v-if="fileList.length > 0">
|
|
<div
|
|
class="upload-card-item"
|
|
:style="getCSSProperties"
|
|
v-for="(item, index) in fileList"
|
|
:key="`img_${index}`"
|
|
>
|
|
<div class="upload-card-item-info">
|
|
<div class="img-box">
|
|
<template v-if="fileType === 'image'">
|
|
<img :src="item" @error="errorImg($event)" alt="" />
|
|
</template>
|
|
<template v-else>
|
|
<n-avatar :style="fileAvatarCSS">{{ getFileExt(item) }}</n-avatar>
|
|
</template>
|
|
</div>
|
|
<div class="img-box-actions">
|
|
<template v-if="fileType === 'image'">
|
|
<n-icon size="18" class="mx-2 action-icon" @click="handlePreview(item)">
|
|
<EyeOutlined />
|
|
</n-icon>
|
|
</template>
|
|
<template v-else>
|
|
<n-icon size="18" class="mx-2 action-icon" @click="handleDownload(item)">
|
|
<CloudDownloadOutlined />
|
|
</n-icon>
|
|
</template>
|
|
<n-icon size="18" class="mx-2 action-icon" @click="handleRemove(index)">
|
|
<DeleteOutlined />
|
|
</n-icon>
|
|
</div>
|
|
</div>
|
|
</div> </div></div
|
|
></div>
|
|
</n-space>
|
|
|
|
<n-modal
|
|
v-model:show="showModal"
|
|
:on-after-leave="handleCancel"
|
|
:style="{
|
|
width: dialogWidth,
|
|
}"
|
|
>
|
|
<n-card title="文件选择">
|
|
<template #header-extra>
|
|
<n-space>
|
|
<n-button @click="handleUpload" ghost>
|
|
<template #icon>
|
|
<n-icon :component="UploadOutlined" />
|
|
</template>
|
|
上传文件
|
|
</n-button>
|
|
</n-space>
|
|
</template>
|
|
<n-card style="overflow: auto" content-style="padding: 0;">
|
|
<Chooser
|
|
ref="chooserRef"
|
|
:file-type="fileType"
|
|
:maxNumber="maxNumber"
|
|
:fileList="fileList"
|
|
@saveChange="saveChange"
|
|
/>
|
|
</n-card>
|
|
<template #footer>
|
|
<n-space justify="end">
|
|
<n-button @click="handleCancel"> 取消 </n-button>
|
|
<n-button type="primary" @click="handleSelectFile"> 确定 </n-button>
|
|
</n-space>
|
|
</template>
|
|
</n-card>
|
|
</n-modal>
|
|
|
|
<FileUpload
|
|
ref="fileUploadRef"
|
|
:width="dialogWidth"
|
|
:finish-call="handleFinishCall"
|
|
:max-upload="20"
|
|
/>
|
|
|
|
<Preview ref="previewRef" />
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { NButton, NSpace, NCard, NModal, NIcon, useDialog } from 'naive-ui';
|
|
import { cloneDeep } from 'lodash-es';
|
|
import FileUpload from '@/components/FileChooser/src/Upload.vue';
|
|
import Chooser from '@/components/FileChooser/src/Chooser.vue';
|
|
import Preview from '@/components/FileChooser/src/Preview.vue';
|
|
import { computed, onMounted, ref, watch } from 'vue';
|
|
import { adaModalWidth, errorImg } from '@/utils/hotgo';
|
|
import { getFileExt } from '@/utils/urlUtils';
|
|
import {
|
|
UploadOutlined,
|
|
PlusOutlined,
|
|
CloudDownloadOutlined,
|
|
DeleteOutlined,
|
|
EyeOutlined,
|
|
} from '@vicons/antd';
|
|
import { Attachment, FileType, getFileType } from '@/components/FileChooser/src/model';
|
|
import { isArrayString, isString } from '@/utils/is';
|
|
|
|
export interface Props {
|
|
value: string | string[] | null;
|
|
maxNumber?: number;
|
|
fileType?: FileType;
|
|
width?: number;
|
|
height?: number;
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
value: '',
|
|
maxNumber: 1,
|
|
fileType: 'default',
|
|
width: 100,
|
|
height: 100,
|
|
});
|
|
|
|
const emit = defineEmits(['update:value']);
|
|
const fileUploadRef = ref();
|
|
const dialog = useDialog();
|
|
const showModal = ref(false);
|
|
const chooserRef = ref();
|
|
const previewRef = ref();
|
|
const fileList = ref<string[]>([]);
|
|
const dialogWidth = computed(() => {
|
|
return adaModalWidth(1080);
|
|
});
|
|
|
|
const getCSSProperties = computed(() => {
|
|
return {
|
|
width: `${props.width}px`,
|
|
height: `${props.height}px`,
|
|
};
|
|
});
|
|
|
|
const fileAvatarCSS = computed(() => {
|
|
return {
|
|
'--n-merged-size': `var(--n-avatar-size-override, ${props.width * 0.8}px)`,
|
|
'--n-font-size': `18px`,
|
|
};
|
|
});
|
|
|
|
const buttonText = computed(() => {
|
|
return getFileType(props.fileType);
|
|
});
|
|
|
|
// 预览
|
|
function handlePreview(url: string) {
|
|
previewRef.value.openPreview(url);
|
|
}
|
|
|
|
// 下载
|
|
function handleDownload(url: string) {
|
|
window.open(url);
|
|
}
|
|
|
|
// 删除
|
|
function handleRemove(index: number) {
|
|
dialog.info({
|
|
title: '提示',
|
|
content: '你确定要删除吗?',
|
|
positiveText: '确定',
|
|
negativeText: '取消',
|
|
onPositiveClick: () => {
|
|
fileList.value.splice(index, 1);
|
|
if (props.maxNumber === 1) {
|
|
emit('update:value', '');
|
|
} else {
|
|
emit('update:value', fileList.value);
|
|
}
|
|
},
|
|
onNegativeClick: () => {},
|
|
});
|
|
}
|
|
|
|
function handleSelectFile() {
|
|
showModal.value = false;
|
|
if (props.maxNumber === 1) {
|
|
emit('update:value', fileList.value.length > 0 ? fileList.value[0] : '');
|
|
} else {
|
|
emit('update:value', fileList.value);
|
|
}
|
|
}
|
|
|
|
function handleCancel() {
|
|
showModal.value = false;
|
|
loadImage();
|
|
}
|
|
|
|
function handleUpload() {
|
|
fileUploadRef.value.openModal();
|
|
}
|
|
|
|
function handleFinishCall(result: Attachment, success: boolean) {
|
|
if (success) {
|
|
chooserRef.value.reloadTable();
|
|
}
|
|
}
|
|
|
|
function saveChange(list: string[]) {
|
|
fileList.value = list;
|
|
}
|
|
|
|
function loadImage() {
|
|
const value = cloneDeep(props.value);
|
|
if (props.maxNumber === 1) {
|
|
fileList.value = [];
|
|
if (value !== '') {
|
|
if (!isString(value)) {
|
|
console.warn(
|
|
'When the file picker is currently in single-file mode, but the passed value is not of type string, there may be potential issues.'
|
|
);
|
|
}
|
|
fileList.value.push(value as string);
|
|
}
|
|
} else {
|
|
if (!isArrayString(value)) {
|
|
console.warn(
|
|
'When the file picker is currently in multiple-file mode, but the passed value is not of type string array, there may be potential issues.'
|
|
);
|
|
}
|
|
if (!value) {
|
|
fileList.value = [];
|
|
} else {
|
|
fileList.value = value as string[];
|
|
}
|
|
}
|
|
}
|
|
|
|
watch(
|
|
() => props.value,
|
|
() => {
|
|
loadImage();
|
|
},
|
|
{
|
|
immediate: true,
|
|
deep: true,
|
|
}
|
|
);
|
|
|
|
onMounted(async () => {
|
|
loadImage();
|
|
});
|
|
</script>
|
|
|
|
<style lang="less">
|
|
.n-upload {
|
|
width: auto; /** 居中 */
|
|
}
|
|
|
|
.upload {
|
|
width: 100%;
|
|
overflow: hidden;
|
|
|
|
&-card {
|
|
width: auto;
|
|
height: auto;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
|
|
&-item {
|
|
margin: 0 8px 8px 0;
|
|
position: relative;
|
|
padding: 8px;
|
|
border: 1px solid #d9d9d9;
|
|
border-radius: 2px;
|
|
display: flex;
|
|
justify-content: center;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
|
|
&:hover {
|
|
background: 0 0;
|
|
|
|
.upload-card-item-info::before {
|
|
opacity: 1;
|
|
}
|
|
|
|
&-info::before {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
&-info {
|
|
position: relative;
|
|
height: 100%;
|
|
padding: 0;
|
|
overflow: hidden;
|
|
|
|
&:hover {
|
|
.img-box-actions {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
&::before {
|
|
position: absolute;
|
|
z-index: 1;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
opacity: 0;
|
|
transition: all 0.3s;
|
|
content: ' ';
|
|
}
|
|
|
|
.img-box {
|
|
position: relative;
|
|
//padding: 8px;
|
|
//border: 1px solid #d9d9d9;
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.img-box-actions {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
z-index: 10;
|
|
white-space: nowrap;
|
|
transform: translate(-50%, -50%);
|
|
opacity: 0;
|
|
transition: all 0.3s;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
|
|
&:hover {
|
|
background: 0 0;
|
|
}
|
|
|
|
.action-icon {
|
|
color: rgba(255, 255, 255, 0.85);
|
|
|
|
&:hover {
|
|
cursor: pointer;
|
|
color: #fff;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
&-item-select-picture {
|
|
border: 1px dashed #d9d9d9;
|
|
border-radius: 2px;
|
|
cursor: pointer;
|
|
background: #fafafa;
|
|
color: #666;
|
|
|
|
.upload-title {
|
|
color: #666;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|