mirror of
https://github.com/vbenjs/vben-admin-thin-next.git
synced 2025-02-02 18:08:40 +08:00
wip: add upload component
This commit is contained in:
parent
2b95be8013
commit
746d4a745d
@ -5,7 +5,7 @@ VITE_USE_MOCK = true
|
||||
VITE_PUBLIC_PATH = /
|
||||
|
||||
# Cross-domain proxy, you can configure multiple
|
||||
VITE_PROXY=[["/api","http://localhost:3000"]]
|
||||
VITE_PROXY=[["/api","http://localhost:3000"],["/upload","http://localhost:3001/upload"]]
|
||||
# VITE_PROXY=[["/api","https://vvbin.cn/test"]]
|
||||
|
||||
# Delete console
|
||||
|
5
src/api/demo/model/uploadModel.ts
Normal file
5
src/api/demo/model/uploadModel.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface UploadApiResult {
|
||||
message: string;
|
||||
code: number;
|
||||
url: string;
|
||||
}
|
23
src/api/demo/upload.ts
Normal file
23
src/api/demo/upload.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { UploadApiResult } from './model/uploadModel';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { UploadFileParams } from '/@/utils/http/axios/types';
|
||||
|
||||
enum Api {
|
||||
UPLOAD_URL = '/upload',
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 上传接口
|
||||
*/
|
||||
export function uploadApi(
|
||||
params: UploadFileParams,
|
||||
onUploadProgress: (progressEvent: ProgressEvent) => void
|
||||
) {
|
||||
return defHttp.uploadFile<UploadApiResult>(
|
||||
{
|
||||
url: Api.UPLOAD_URL,
|
||||
onUploadProgress,
|
||||
},
|
||||
params
|
||||
);
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
export interface ActionItem {
|
||||
on?: any;
|
||||
onClick?: any;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
color?: 'success' | 'error' | 'warning';
|
||||
|
2
src/components/Upload/index.ts
Normal file
2
src/components/Upload/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { default as UploadContainer } from './src/UploadContainer.vue';
|
||||
// export * from './src/types';
|
29
src/components/Upload/src/ThumnUrl.vue
Normal file
29
src/components/Upload/src/ThumnUrl.vue
Normal file
@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<span>
|
||||
<img v-if="fileUrl" :src="fileUrl" />
|
||||
<span v-else>{{ fileType }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
fileUrl: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
fileType: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
fileName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
</script>
|
62
src/components/Upload/src/UploadContainer.vue
Normal file
62
src/components/Upload/src/UploadContainer.vue
Normal file
@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="openUploadModal">上传</a-button>
|
||||
<a-button @click="openPreviewModal">
|
||||
<Icon icon="ant-design:eye-outlined" />
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
<UploadModal v-bind="$props" @register="registerUploadModal" @change="handleChange" />
|
||||
<UploadPreviewModal
|
||||
:value="fileListRef"
|
||||
@register="registerPreviewModal"
|
||||
@change="handlePreviewChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch, unref } from 'vue';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import UploadModal from './UploadModal.vue';
|
||||
import { uploadContainerProps } from './props';
|
||||
import UploadPreviewModal from './UploadPreviewModal.vue';
|
||||
import Icon from '/@/components/Icon/index';
|
||||
export default defineComponent({
|
||||
components: { UploadModal, UploadPreviewModal, Icon },
|
||||
props: uploadContainerProps,
|
||||
setup(props, { emit }) {
|
||||
// 上传modal
|
||||
const [registerUploadModal, { openModal: openUploadModal }] = useModal();
|
||||
// 预览modal
|
||||
const [registerPreviewModal, { openModal: openPreviewModal }] = useModal();
|
||||
|
||||
const fileListRef = ref<string[]>([]);
|
||||
watch(
|
||||
() => props.value,
|
||||
(value) => {
|
||||
fileListRef.value = [...(value || [])];
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
// 上传modal保存操作
|
||||
function handleChange(urls: string[]) {
|
||||
fileListRef.value = [...unref(fileListRef), ...(urls || [])];
|
||||
emit('change', fileListRef.value);
|
||||
}
|
||||
// 预览modal保存操作
|
||||
function handlePreviewChange(urls: string[]) {
|
||||
fileListRef.value = [...(urls || [])];
|
||||
emit('change', fileListRef.value);
|
||||
}
|
||||
return {
|
||||
registerUploadModal,
|
||||
openUploadModal,
|
||||
handleChange,
|
||||
handlePreviewChange,
|
||||
registerPreviewModal,
|
||||
openPreviewModal,
|
||||
fileListRef,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
244
src/components/Upload/src/UploadModal.vue
Normal file
244
src/components/Upload/src/UploadModal.vue
Normal file
@ -0,0 +1,244 @@
|
||||
<template>
|
||||
<BasicModal
|
||||
v-bind="$attrs"
|
||||
@register="register"
|
||||
@ok="handleOk"
|
||||
:closeFunc="handleCloseFunc"
|
||||
:maskClosable="false"
|
||||
width="800px"
|
||||
title="上传组件"
|
||||
wrapClassName="upload-modal"
|
||||
:okButtonProps="{ disabled: isUploadingRef }"
|
||||
:cancelButtonProps="{ disabled: isUploadingRef }"
|
||||
>
|
||||
<template #centerdFooter>
|
||||
<a-button @click="handleStartUpload" color="success" :loading="isUploadingRef">
|
||||
{{ isUploadingRef ? '上传中' : '开始上传' }}
|
||||
</a-button>
|
||||
</template>
|
||||
<Upload :accept="getStringAccept" :multiple="multiple" :before-upload="beforeUpload">
|
||||
<a-button type="primary"> 选择文件 </a-button>
|
||||
<span class="px-2">{{ getHelpText }}</span>
|
||||
</Upload>
|
||||
<BasicTable @register="registerTable" :dataSource="fileListRef" />
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, ref, toRef, unref } from 'vue';
|
||||
import { Upload } from 'ant-design-vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { BasicTable, useTable } from '/@/components/Table';
|
||||
// hooks
|
||||
import { useUploadType } from './useUpload';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
// types
|
||||
import { FileItem, UploadResultStatus } from './types';
|
||||
import { basicProps } from './props';
|
||||
import { createTableColumns, createActionColumn } from './data';
|
||||
// utils
|
||||
import { checkFileType, checkImgType, getBase64WithFile } from './utils';
|
||||
import { buildUUID } from '/@/utils/uuid';
|
||||
import { createImgPreview } from '/@/components/Preview/index';
|
||||
import { uploadApi } from '/@/api/demo/upload';
|
||||
|
||||
export default defineComponent({
|
||||
components: { BasicModal, Upload, BasicTable },
|
||||
props: basicProps,
|
||||
setup(props, { emit }) {
|
||||
const [register, { closeModal }] = useModalInner();
|
||||
const { getAccept, getStringAccept, getHelpText } = useUploadType({
|
||||
acceptRef: toRef(props, 'accept'),
|
||||
helpTextRef: toRef(props, 'helpText'),
|
||||
maxNumberRef: toRef(props, 'maxNumber'),
|
||||
maxSizeRef: toRef(props, 'maxSize'),
|
||||
});
|
||||
|
||||
const fileListRef = ref<FileItem[]>([]);
|
||||
const state = reactive<{ fileList: FileItem[] }>({ fileList: [] });
|
||||
const { createMessage } = useMessage();
|
||||
// 上传前校验
|
||||
function beforeUpload(file: File) {
|
||||
const { size, name } = file;
|
||||
const { maxSize } = props;
|
||||
const accept = unref(getAccept);
|
||||
|
||||
// 设置最大值,则判断
|
||||
if (maxSize && file.size / 1024 / 1024 >= maxSize) {
|
||||
createMessage.error(`只能上传不超过${maxSize}MB的文件!`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置类型,则判断
|
||||
if (accept.length > 0 && !checkFileType(file, accept)) {
|
||||
createMessage.error!(`只能上传${accept.join(',')}格式文件`);
|
||||
return false;
|
||||
}
|
||||
// 生成图片缩略图
|
||||
if (checkImgType(file)) {
|
||||
// beforeUpload,如果异步会调用自带上传方法
|
||||
// file.thumbUrl = await getBase64(file);
|
||||
getBase64WithFile(file).then(({ result: thumbUrl }) => {
|
||||
fileListRef.value = [
|
||||
...unref(fileListRef),
|
||||
{
|
||||
uuid: buildUUID(),
|
||||
file,
|
||||
thumbUrl,
|
||||
size,
|
||||
name,
|
||||
percent: 0,
|
||||
type: name.split('.').pop(),
|
||||
},
|
||||
];
|
||||
});
|
||||
} else {
|
||||
fileListRef.value = [
|
||||
...unref(fileListRef),
|
||||
{
|
||||
uuid: buildUUID(),
|
||||
|
||||
file,
|
||||
size,
|
||||
name,
|
||||
percent: 0,
|
||||
type: name.split('.').pop(),
|
||||
},
|
||||
];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// 删除
|
||||
function handleRemove(record: FileItem) {
|
||||
const index = fileListRef.value.findIndex((item) => item.uuid === record.uuid);
|
||||
index !== -1 && fileListRef.value.splice(index, 1);
|
||||
}
|
||||
// 预览
|
||||
function handlePreview(record: FileItem) {
|
||||
const { thumbUrl = '' } = record;
|
||||
createImgPreview({
|
||||
imageList: [thumbUrl],
|
||||
});
|
||||
}
|
||||
const [registerTable] = useTable({
|
||||
columns: createTableColumns(),
|
||||
actionColumn: createActionColumn(handleRemove, handlePreview),
|
||||
pagination: false,
|
||||
});
|
||||
// 是否正在上传
|
||||
const isUploadingRef = ref(false);
|
||||
async function uploadApiByItem(item: FileItem) {
|
||||
try {
|
||||
item.status = UploadResultStatus.UPLOADING;
|
||||
|
||||
const { data } = await uploadApi(
|
||||
{
|
||||
file: item.file,
|
||||
},
|
||||
function onUploadProgress(progressEvent: ProgressEvent) {
|
||||
const complete = ((progressEvent.loaded / progressEvent.total) * 100) | 0;
|
||||
item.percent = complete;
|
||||
}
|
||||
);
|
||||
item.status = UploadResultStatus.SUCCESS;
|
||||
item.responseData = data;
|
||||
return {
|
||||
success: true,
|
||||
error: null,
|
||||
};
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
item.status = UploadResultStatus.ERROR;
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
};
|
||||
}
|
||||
}
|
||||
// 点击开始上传
|
||||
async function handleStartUpload() {
|
||||
try {
|
||||
isUploadingRef.value = true;
|
||||
const data = await Promise.all(
|
||||
unref(fileListRef).map((item) => {
|
||||
return uploadApiByItem(item);
|
||||
})
|
||||
);
|
||||
isUploadingRef.value = false;
|
||||
// 生产环境:抛出错误
|
||||
const errorList = data.filter((item) => !item.success);
|
||||
if (errorList.length > 0) {
|
||||
throw errorList;
|
||||
}
|
||||
} catch (e) {
|
||||
isUploadingRef.value = false;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
// 点击保存
|
||||
function handleOk() {
|
||||
// TODO: 没起作用:okButtonProps={{ disabled: state.isUploading }}
|
||||
if (isUploadingRef.value) {
|
||||
createMessage.warning('请等待文件上传后,保存');
|
||||
return;
|
||||
}
|
||||
const fileList: string[] = [];
|
||||
|
||||
for (const item of fileListRef.value) {
|
||||
const { status, responseData } = item;
|
||||
if (status === UploadResultStatus.SUCCESS && responseData) {
|
||||
fileList.push(responseData.url);
|
||||
}
|
||||
}
|
||||
|
||||
// 存在一个上传成功的即可保存
|
||||
|
||||
if (fileList.length <= 0) {
|
||||
createMessage.warning('没有上传成功的文件,无法保存');
|
||||
return;
|
||||
}
|
||||
console.log(fileList);
|
||||
emit('change', fileList);
|
||||
fileListRef.value = [];
|
||||
closeModal();
|
||||
}
|
||||
// 点击关闭:则所有操作不保存,包括上传的
|
||||
function handleCloseFunc() {
|
||||
if (!isUploadingRef.value) {
|
||||
fileListRef.value = [];
|
||||
return true;
|
||||
} else {
|
||||
createMessage.warning('请等待文件上传结束后操作');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return {
|
||||
register,
|
||||
closeModal,
|
||||
getHelpText,
|
||||
getStringAccept,
|
||||
beforeUpload,
|
||||
registerTable,
|
||||
fileListRef,
|
||||
state,
|
||||
isUploadingRef,
|
||||
handleStartUpload,
|
||||
handleOk,
|
||||
handleCloseFunc,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
// /deep/ .ant-upload-list {
|
||||
// display: none;
|
||||
// }
|
||||
.upload-modal {
|
||||
.ant-upload-list {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ant-table-wrapper .ant-spin-nested-loading {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
93
src/components/Upload/src/UploadPreviewModal.vue
Normal file
93
src/components/Upload/src/UploadPreviewModal.vue
Normal file
@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<BasicModal
|
||||
wrapClassName="upload-preview-modal"
|
||||
v-bind="$attrs"
|
||||
width="800px"
|
||||
@register="register"
|
||||
title="预览"
|
||||
:showOkBtn="false"
|
||||
>
|
||||
<BasicTable @register="registerTable" :dataSource="fileListRef" />
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, watch, ref, unref } from 'vue';
|
||||
import { BasicTable, useTable } from '/@/components/Table';
|
||||
import { createPreviewColumns, createPreviewActionColumn } from './data';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { priviewProps } from './props';
|
||||
import { PreviewFileItem } from './types';
|
||||
import { createImgPreview } from '/@/components/Preview/index';
|
||||
import { downloadByUrl } from '/@/utils/file/FileDownload';
|
||||
|
||||
export default defineComponent({
|
||||
components: { BasicModal, BasicTable },
|
||||
props: priviewProps,
|
||||
setup(props, { emit }) {
|
||||
const [register, { closeModal }] = useModalInner();
|
||||
const fileListRef = ref<PreviewFileItem[]>([]);
|
||||
watch(
|
||||
() => props.value,
|
||||
(value) => {
|
||||
fileListRef.value = [];
|
||||
value.forEach((item) => {
|
||||
fileListRef.value = [
|
||||
...unref(fileListRef),
|
||||
{
|
||||
url: item,
|
||||
type: item.split('.').pop() || '',
|
||||
name: item.split('/').pop() || '',
|
||||
},
|
||||
];
|
||||
});
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
// 删除
|
||||
function handleRemove(record: PreviewFileItem) {
|
||||
const index = fileListRef.value.findIndex((item) => item.url === record.url);
|
||||
if (index !== -1) {
|
||||
fileListRef.value.splice(index, 1);
|
||||
emit(
|
||||
'change',
|
||||
fileListRef.value.map((item) => item.url)
|
||||
);
|
||||
}
|
||||
}
|
||||
// 预览
|
||||
function handlePreview(record: PreviewFileItem) {
|
||||
const { url = '' } = record;
|
||||
createImgPreview({
|
||||
imageList: [url],
|
||||
});
|
||||
}
|
||||
// 下载
|
||||
function handleDownload(record: PreviewFileItem) {
|
||||
const { url = '' } = record;
|
||||
downloadByUrl({ url });
|
||||
}
|
||||
const [registerTable] = useTable({
|
||||
columns: createPreviewColumns(),
|
||||
pagination: false,
|
||||
actionColumn: createPreviewActionColumn({ handleRemove, handlePreview, handleDownload }),
|
||||
});
|
||||
return {
|
||||
register,
|
||||
closeModal,
|
||||
fileListRef,
|
||||
registerTable,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
.upload-preview-modal {
|
||||
.ant-upload-list {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ant-table-wrapper .ant-spin-nested-loading {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
159
src/components/Upload/src/data.tsx
Normal file
159
src/components/Upload/src/data.tsx
Normal file
@ -0,0 +1,159 @@
|
||||
// import { BasicColumn, TableAction, ActionItem } from '@/components/table';
|
||||
import { checkImgType, isImgTypeByName } from './utils';
|
||||
// import ThumnUrl from './ThumbUrl.vue';
|
||||
import { Progress } from 'ant-design-vue';
|
||||
import { FileItem, PreviewFileItem, UploadResultStatus } from './types';
|
||||
// import { ElecArchivesSaveResult } from '@/api/biz/file/model/fileModel';
|
||||
// import { quryFile } from '@/api/biz/file/file';
|
||||
import { BasicColumn, ActionItem, TableAction } from '/@/components/Table/index';
|
||||
|
||||
// 文件上传列表
|
||||
export function createTableColumns(): BasicColumn[] {
|
||||
return [
|
||||
{
|
||||
dataIndex: 'thumbUrl',
|
||||
title: '图例',
|
||||
width: 100,
|
||||
customRender: ({ record }) => {
|
||||
const { thumbUrl, type } = (record as FileItem) || {};
|
||||
return <span>{thumbUrl ? <img src={thumbUrl} style={{ width: '50px' }} /> : type}</span>;
|
||||
// return <ThumnUrl fileUrl={thumbUrl} fileType={type} fileName={type} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: '文件名',
|
||||
align: 'left',
|
||||
customRender: ({ text, record }) => {
|
||||
const { percent, status: uploadStatus } = (record as FileItem) || {};
|
||||
let status = 'normal';
|
||||
if (uploadStatus === UploadResultStatus.ERROR) {
|
||||
status = 'exception';
|
||||
} else if (uploadStatus === UploadResultStatus.UPLOADING) {
|
||||
status = 'active';
|
||||
} else if (uploadStatus === UploadResultStatus.SUCCESS) {
|
||||
status = 'success';
|
||||
}
|
||||
return (
|
||||
<span>
|
||||
<p class="ellipsis mb-1" title={text}>
|
||||
{text}
|
||||
</p>
|
||||
<Progress percent={percent} size="small" status={status} />
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'size',
|
||||
title: '文件大小',
|
||||
width: 100,
|
||||
customRender: ({ text = 0 }) => {
|
||||
return text && (text / 1024).toFixed(2) + 'KB';
|
||||
},
|
||||
},
|
||||
// {
|
||||
// dataIndex: 'type',
|
||||
// title: '文件类型',
|
||||
// width: 100,
|
||||
// },
|
||||
{
|
||||
dataIndex: 'status',
|
||||
title: '状态',
|
||||
width: 100,
|
||||
customRender: ({ text }) => {
|
||||
if (text === UploadResultStatus.SUCCESS) {
|
||||
return '上传成功';
|
||||
} else if (text === UploadResultStatus.ERROR) {
|
||||
return '上传失败';
|
||||
} else if (text === UploadResultStatus.UPLOADING) {
|
||||
return '上传中';
|
||||
}
|
||||
|
||||
return text;
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
export function createActionColumn(handleRemove: Function, handlePreview: Function): BasicColumn {
|
||||
return {
|
||||
width: 120,
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
fixed: false,
|
||||
customRender: ({ record }) => {
|
||||
const actions: ActionItem[] = [
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleRemove.bind(null, record),
|
||||
},
|
||||
];
|
||||
if (checkImgType(record)) {
|
||||
actions.unshift({
|
||||
label: '预览',
|
||||
onClick: handlePreview.bind(null, record),
|
||||
});
|
||||
}
|
||||
return <TableAction actions={actions} />;
|
||||
},
|
||||
};
|
||||
}
|
||||
// 文件预览列表
|
||||
export function createPreviewColumns(): BasicColumn[] {
|
||||
return [
|
||||
{
|
||||
dataIndex: 'url',
|
||||
title: '图例',
|
||||
width: 100,
|
||||
customRender: ({ record }) => {
|
||||
const { url, type } = (record as PreviewFileItem) || {};
|
||||
return (
|
||||
<span>{isImgTypeByName(url) ? <img src={url} style={{ width: '50px' }} /> : type}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: '文件名',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function createPreviewActionColumn({
|
||||
handleRemove,
|
||||
handlePreview,
|
||||
handleDownload,
|
||||
}: {
|
||||
handleRemove: Function;
|
||||
handlePreview: Function;
|
||||
handleDownload: Function;
|
||||
}): BasicColumn {
|
||||
return {
|
||||
width: 160,
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
fixed: false,
|
||||
customRender: ({ record }) => {
|
||||
const { url } = (record as PreviewFileItem) || {};
|
||||
|
||||
const actions: ActionItem[] = [
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleRemove.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '下载',
|
||||
onClick: handleDownload.bind(null, record),
|
||||
},
|
||||
];
|
||||
if (isImgTypeByName(url)) {
|
||||
actions.unshift({
|
||||
label: '预览',
|
||||
onClick: handlePreview.bind(null, record),
|
||||
});
|
||||
}
|
||||
return <TableAction actions={actions} />;
|
||||
},
|
||||
};
|
||||
}
|
42
src/components/Upload/src/props.ts
Normal file
42
src/components/Upload/src/props.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
export const basicProps = {
|
||||
helpText: {
|
||||
type: String as PropType<string>,
|
||||
default: '',
|
||||
},
|
||||
// 文件最大多少MB
|
||||
maxSize: {
|
||||
type: Number as PropType<number>,
|
||||
default: 2,
|
||||
},
|
||||
// 最大数量的文件,0不限制
|
||||
maxNumber: {
|
||||
type: Number as PropType<number>,
|
||||
default: 0,
|
||||
},
|
||||
// 根据后缀,或者其他
|
||||
accept: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const uploadContainerProps = {
|
||||
value: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
},
|
||||
...basicProps,
|
||||
};
|
||||
|
||||
export const priviewProps = {
|
||||
value: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
},
|
||||
};
|
25
src/components/Upload/src/types.ts
Normal file
25
src/components/Upload/src/types.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { UploadApiResult } from '/@/api/demo/model/uploadModel';
|
||||
|
||||
export enum UploadResultStatus {
|
||||
SUCCESS = 'success',
|
||||
ERROR = 'error',
|
||||
UPLOADING = 'uploading',
|
||||
}
|
||||
|
||||
export interface FileItem {
|
||||
thumbUrl?: string;
|
||||
name: string;
|
||||
size: string | number;
|
||||
type?: string;
|
||||
percent: number;
|
||||
file: File;
|
||||
status?: UploadResultStatus;
|
||||
responseData?: UploadApiResult;
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
export interface PreviewFileItem {
|
||||
url: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
55
src/components/Upload/src/useUpload.ts
Normal file
55
src/components/Upload/src/useUpload.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { Ref, unref, computed } from 'vue';
|
||||
|
||||
export function useUploadType({
|
||||
acceptRef,
|
||||
// uploadTypeRef,
|
||||
helpTextRef,
|
||||
maxNumberRef,
|
||||
maxSizeRef,
|
||||
}: {
|
||||
acceptRef: Ref<string[]>;
|
||||
// uploadTypeRef: Ref<UploadTypeEnum>;
|
||||
helpTextRef: Ref<string>;
|
||||
maxNumberRef: Ref<number>;
|
||||
maxSizeRef: Ref<number>;
|
||||
}) {
|
||||
// 文件类型限制
|
||||
const getAccept = computed(() => {
|
||||
// const uploadType = unref(uploadTypeRef);
|
||||
const accept = unref(acceptRef);
|
||||
if (accept && accept.length > 0) {
|
||||
return accept;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
const getStringAccept = computed(() => {
|
||||
return unref(getAccept)
|
||||
.map((item) => `.${item}`)
|
||||
.join(',');
|
||||
});
|
||||
// 支持jpg、jpeg、png格式,不超过2M,最多可选择10张图片,。
|
||||
const getHelpText = computed(() => {
|
||||
const helpText = unref(helpTextRef);
|
||||
if (helpText) {
|
||||
return helpText;
|
||||
}
|
||||
const helpTexts: string[] = [];
|
||||
|
||||
const accept = unref(acceptRef);
|
||||
if (accept.length > 0) {
|
||||
helpTexts.push(`支持${accept.join(',')}格式`);
|
||||
}
|
||||
|
||||
const maxSize = unref(maxSizeRef);
|
||||
if (maxSize) {
|
||||
helpTexts.push(`不超过${maxSize}MB`);
|
||||
}
|
||||
|
||||
const maxNumber = unref(maxNumberRef);
|
||||
if (maxNumber) {
|
||||
helpTexts.push(`最多可选择${maxNumber}个文件`);
|
||||
}
|
||||
return helpTexts.join(',');
|
||||
});
|
||||
return { getAccept, getStringAccept, getHelpText };
|
||||
}
|
28
src/components/Upload/src/utils.ts
Normal file
28
src/components/Upload/src/utils.ts
Normal file
@ -0,0 +1,28 @@
|
||||
export function checkFileType(file: File, accepts: string[]) {
|
||||
const newTypes = accepts.join('|');
|
||||
// const reg = /\.(jpg|jpeg|png|gif|txt|doc|docx|xls|xlsx|xml)$/i;
|
||||
const reg = new RegExp('\\.(' + newTypes + ')$', 'i');
|
||||
|
||||
if (!reg.test(file.name)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
export function checkImgType(file: File) {
|
||||
return /\.(jpg|jpeg|png|gif)$/i.test(file.name);
|
||||
}
|
||||
export function isImgTypeByName(name: string) {
|
||||
return /\.(jpg|jpeg|png|gif)$/i.test(name);
|
||||
}
|
||||
export function getBase64WithFile(file: File) {
|
||||
return new Promise<{
|
||||
result: string;
|
||||
file: File;
|
||||
}>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => resolve({ result: reader.result as string, file });
|
||||
reader.onerror = (error) => reject(error);
|
||||
});
|
||||
}
|
@ -38,6 +38,10 @@ const menu: MenuModule = {
|
||||
path: 'strength-meter',
|
||||
name: '密码强度组件',
|
||||
},
|
||||
{
|
||||
path: 'upload',
|
||||
name: '上传组件',
|
||||
},
|
||||
{
|
||||
path: 'scroll',
|
||||
name: '滚动组件',
|
||||
|
@ -170,5 +170,13 @@ export default {
|
||||
title: '密码强度组件',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/upload',
|
||||
name: 'UploadDemo',
|
||||
component: () => import('/@/views/demo/comp/upload/index.vue'),
|
||||
meta: {
|
||||
title: '上传组件',
|
||||
},
|
||||
},
|
||||
],
|
||||
} as AppRouteModule;
|
||||
|
@ -5,9 +5,10 @@ import { AxiosCanceler } from './axiosCancel';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import type { RequestOptions, CreateAxiosOptions, Result } from './types';
|
||||
import type { RequestOptions, CreateAxiosOptions, Result, UploadFileParams } from './types';
|
||||
// import { ContentTypeEnum } from '/@/enums/httpEnum';
|
||||
import { errorResult } from './const';
|
||||
import { ContentTypeEnum } from '/@/enums/httpEnum';
|
||||
|
||||
export * from './axiosTransform';
|
||||
|
||||
@ -107,25 +108,42 @@ export class VAxios {
|
||||
this.axiosInstance.interceptors.response.use(undefined, responseInterceptorsCatch);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @description: 文件上传
|
||||
// */
|
||||
// uploadFiles(config: AxiosRequestConfig, params: File[]) {
|
||||
// const formData = new FormData();
|
||||
/**
|
||||
* @description: 文件上传
|
||||
*/
|
||||
uploadFile<T = any>(config: AxiosRequestConfig, params: UploadFileParams) {
|
||||
const formData = new window.FormData();
|
||||
|
||||
// Object.keys(params).forEach((key) => {
|
||||
// formData.append(key, params[key as any]);
|
||||
// });
|
||||
if (params.data) {
|
||||
Object.keys(params.data).forEach((key) => {
|
||||
if (!params.data) return;
|
||||
const value = params.data[key];
|
||||
// support key-value array data
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((item) => {
|
||||
// { list: [ 11, 22 ] }
|
||||
// formData.append('list[]', 11);
|
||||
formData.append(`${key}[]`, item);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// return this.request({
|
||||
// ...config,
|
||||
// method: 'POST',
|
||||
// data: formData,
|
||||
// headers: {
|
||||
// 'Content-type': ContentTypeEnum.FORM_DATA,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
formData.append(key, params.data[key]);
|
||||
});
|
||||
}
|
||||
|
||||
formData.append(params.name || 'file', params.file, params.filename);
|
||||
|
||||
return this.axiosInstance.request<T>({
|
||||
...config,
|
||||
method: 'POST',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-type': ContentTypeEnum.FORM_DATA,
|
||||
ignoreCancelToken: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 请求方法
|
||||
|
@ -28,3 +28,14 @@ export interface Result<T = any> {
|
||||
message: string;
|
||||
result: T;
|
||||
}
|
||||
// multipart/form-data:上传文件
|
||||
export interface UploadFileParams {
|
||||
// 其他参数
|
||||
data?: { [key: string]: any };
|
||||
// 文件参数的接口字段名
|
||||
name?: string;
|
||||
// 文件
|
||||
file: File | Blob;
|
||||
// 文件名
|
||||
filename?: string;
|
||||
}
|
||||
|
17
src/views/demo/comp/upload/index.vue
Normal file
17
src/views/demo/comp/upload/index.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<UploadContainer :maxSize="5" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { UploadContainer } from '/@/components/Upload/index';
|
||||
|
||||
// import { Alert } from 'ant-design-vue';
|
||||
export default defineComponent({
|
||||
components: { UploadContainer },
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
</script>
|
Loading…
Reference in New Issue
Block a user