|
|
@@ -0,0 +1,191 @@
|
|
|
+<script setup lang="ts">
|
|
|
+import { computed, onMounted, type PropType, reactive, ref, watch } from 'vue';
|
|
|
+
|
|
|
+import { useAccessStore } from '@vben/stores';
|
|
|
+
|
|
|
+import {
|
|
|
+ Button,
|
|
|
+ message,
|
|
|
+ Upload,
|
|
|
+ type UploadChangeParam,
|
|
|
+ type UploadFile,
|
|
|
+} from 'ant-design-vue';
|
|
|
+
|
|
|
+import { FileApi } from '#/api';
|
|
|
+import { Icon } from '#/components/icon';
|
|
|
+
|
|
|
+type UploadParams = {
|
|
|
+ allowSuffix: string;
|
|
|
+ directory: string;
|
|
|
+ folderId: Number | String;
|
|
|
+ path: string;
|
|
|
+};
|
|
|
+
|
|
|
+defineOptions({
|
|
|
+ name: 'Upload',
|
|
|
+ inheritAttrs: false,
|
|
|
+});
|
|
|
+const props = defineProps({
|
|
|
+ value: {
|
|
|
+ type: [Number, String] as PropType<number | string>,
|
|
|
+ default: 0,
|
|
|
+ },
|
|
|
+ action: {
|
|
|
+ // 上传的地址
|
|
|
+ type: String,
|
|
|
+ default: `${import.meta.env.VITE_GLOB_API_URL}/file/upload`,
|
|
|
+ },
|
|
|
+ headers: {
|
|
|
+ // 上传请求的 headers
|
|
|
+ type: Object,
|
|
|
+ default: () => ({}),
|
|
|
+ },
|
|
|
+ disabled: {
|
|
|
+ // 是否禁用
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+ listType: {
|
|
|
+ // 上传类型
|
|
|
+ type: String as PropType<'picture' | 'picture-card' | 'text'>,
|
|
|
+ default: 'text',
|
|
|
+ },
|
|
|
+ maxCount: {
|
|
|
+ // 最大上传数量
|
|
|
+ type: Number,
|
|
|
+ default: undefined,
|
|
|
+ },
|
|
|
+ uploadName: {
|
|
|
+ // 文件上传字段名,name被字段名覆写了,要换成uploadName
|
|
|
+ type: String,
|
|
|
+ default: undefined,
|
|
|
+ },
|
|
|
+ params: {
|
|
|
+ type: Object as PropType<UploadParams>,
|
|
|
+ default: () => ({}),
|
|
|
+ },
|
|
|
+});
|
|
|
+const emits = defineEmits(['update:modelValue']);
|
|
|
+const accessStore = useAccessStore();
|
|
|
+const fileList = ref<UploadFile[]>([]);
|
|
|
+// const attrs = useAttrs();
|
|
|
+const bindProps = computed(() => {
|
|
|
+ return {
|
|
|
+ // ...omit(attrs, ['name']), // 'onChange','onInput', 'onBlur',
|
|
|
+ ...props,
|
|
|
+ name: props.uploadName || 'file',
|
|
|
+ };
|
|
|
+});
|
|
|
+const getFileList = async (val: number | string | undefined) => {
|
|
|
+ if (!val) {
|
|
|
+ if (fileList.value.length > 0) fileList.value = [];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const data = await FileApi.getList(Number(val));
|
|
|
+ fileList.value =
|
|
|
+ data && data.length > 0
|
|
|
+ ? data.map((item) => {
|
|
|
+ return {
|
|
|
+ uid: item.id.toString(),
|
|
|
+ size: item.sizeKb,
|
|
|
+ name: item.fileName,
|
|
|
+ url: item.filePath,
|
|
|
+ } as UploadFile;
|
|
|
+ })
|
|
|
+ : [];
|
|
|
+};
|
|
|
+
|
|
|
+const uploadParams = reactive<UploadParams>({
|
|
|
+ allowSuffix: '',
|
|
|
+ directory: '',
|
|
|
+ folderId: 0,
|
|
|
+ path: '',
|
|
|
+});
|
|
|
+const modelValue = computed({
|
|
|
+ get() {
|
|
|
+ return props.value;
|
|
|
+ },
|
|
|
+ set(val) {
|
|
|
+ Object.assign(uploadParams, props.params, { folderId: val });
|
|
|
+ emits('update:modelValue', val);
|
|
|
+ },
|
|
|
+});
|
|
|
+
|
|
|
+const getHeaders = {
|
|
|
+ Authorization: `Bearer ${accessStore.accessToken}`,
|
|
|
+ ...props.headers,
|
|
|
+};
|
|
|
+const handleChange = (info: UploadChangeParam) => {
|
|
|
+ if (info.file.status === 'done') {
|
|
|
+ // 更新file-list当前file的uid
|
|
|
+ // console.log('handleChange', info.file, fileList.value);
|
|
|
+ if (info.file.response.code === 200) {
|
|
|
+ fileList.value = fileList.value.map((item) => {
|
|
|
+ if (item.uid === info.file.uid) {
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ uid: info.file.response.data.id,
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return item;
|
|
|
+ });
|
|
|
+ modelValue.value = info.file.response.data.folderId;
|
|
|
+ } else {
|
|
|
+ fileList.value.splice(fileList.value.indexOf(info.file), 1);
|
|
|
+ message.error(info.file.response.message);
|
|
|
+ }
|
|
|
+ } else if (info.file.status === 'error') {
|
|
|
+ message.error(`${info.file.name} file upload failed.`);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const handleRemove = async (file: UploadFile) => {
|
|
|
+ // console.log('handleRemove', file, fileList.value);
|
|
|
+ await FileApi.deleteDetail(Number(file.uid));
|
|
|
+ fileList.value.splice(fileList.value.indexOf(file), 1);
|
|
|
+ if (fileList.value.length === 0) {
|
|
|
+ modelValue.value = 0;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => props.value,
|
|
|
+ async (val) => {
|
|
|
+ await getFileList(val);
|
|
|
+ },
|
|
|
+);
|
|
|
+onMounted(async () => {
|
|
|
+ await getFileList(props.value);
|
|
|
+});
|
|
|
+</script>
|
|
|
+<template>
|
|
|
+ <Upload
|
|
|
+ v-model:file-list="fileList"
|
|
|
+ v-bind="bindProps"
|
|
|
+ :action="action"
|
|
|
+ :data="uploadParams"
|
|
|
+ :disabled="disabled"
|
|
|
+ :headers="getHeaders"
|
|
|
+ :list-type="listType"
|
|
|
+ :max-count="maxCount"
|
|
|
+ @change="handleChange"
|
|
|
+ @remove="handleRemove"
|
|
|
+ >
|
|
|
+ <template #default>
|
|
|
+ <Icon
|
|
|
+ v-if="listType === 'picture-card'"
|
|
|
+ :size="24"
|
|
|
+ icon="ant-design:plus-outlined"
|
|
|
+ />
|
|
|
+ <Button v-else>
|
|
|
+ 上传文件
|
|
|
+ <template #icon>
|
|
|
+ <Icon icon="ant-design:cloud-upload-outlined" />
|
|
|
+ </template>
|
|
|
+ </Button>
|
|
|
+ </template>
|
|
|
+ <template v-for="item in Object.keys($slots)" #[item]="data">
|
|
|
+ <slot :name="item" v-bind="data || {}"></slot>
|
|
|
+ </template>
|
|
|
+ </Upload>
|
|
|
+</template>
|