新增前端vue
This commit is contained in:
4
web-vue/packages/core/components/Upload/index.ts
Normal file
4
web-vue/packages/core/components/Upload/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { withInstall } from '@jeesite/core/utils';
|
||||
import basicUpload from './src/BasicUpload.vue';
|
||||
|
||||
export const BasicUpload = withInstall(basicUpload);
|
||||
155
web-vue/packages/core/components/Upload/src/BasicUpload.vue
Normal file
155
web-vue/packages/core/components/Upload/src/BasicUpload.vue
Normal file
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<div class="jeesite-basic-upload">
|
||||
<Space>
|
||||
<a-button
|
||||
v-if="!(readonly || disabled)"
|
||||
type="primary"
|
||||
@click="openUploadModal"
|
||||
preIcon="i-carbon:cloud-upload"
|
||||
:size="props.size"
|
||||
>
|
||||
{{ uploadText || t('component.upload.upload') }}
|
||||
</a-button>
|
||||
<Tooltip placement="bottom" v-if="getShowPreview">
|
||||
<template #title>
|
||||
{{ t('component.upload.uploaded') }}
|
||||
<template v-if="fileList.length">
|
||||
{{ fileList.length }}
|
||||
</template>
|
||||
</template>
|
||||
<a-button @click="props.showPreviewList ? null : openPreviewModal()" :size="props.size">
|
||||
<Icon icon="i-bi:eye" />
|
||||
<template v-if="fileList.length && props.showPreviewNumber">
|
||||
{{ fileList.length }}
|
||||
</template>
|
||||
</a-button>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
<UploadModal
|
||||
v-bind="bindValue"
|
||||
:previewFileList="fileList"
|
||||
:apiUploadUrl="apiUploadUrl"
|
||||
:apiDownloadUrl="apiDownloadUrl"
|
||||
@register="registerUploadModal"
|
||||
@change="handleChange"
|
||||
@delete="handleDelete"
|
||||
/>
|
||||
<UploadPreviewModal
|
||||
:value="fileList"
|
||||
:readonly="readonly || disabled"
|
||||
:imageThumbName="imageThumbName"
|
||||
:apiDownloadUrl="apiDownloadUrl"
|
||||
@register="registerPreviewModal"
|
||||
@change="handlePreviewChange"
|
||||
@delete="handleDelete"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup name="BasicUpload">
|
||||
import { defineComponent, ref, watch, unref, computed, useAttrs } from 'vue';
|
||||
import UploadModal from './UploadModal.vue';
|
||||
import UploadPreviewModal from './UploadPreviewModal.vue';
|
||||
import { Icon } from '@jeesite/core/components/Icon';
|
||||
import { Tooltip, Space } from 'ant-design-vue';
|
||||
import { useModal } from '@jeesite/core/components/Modal';
|
||||
import { uploadContainerProps } from './props';
|
||||
import { omit } from 'lodash-es';
|
||||
import { useI18n } from '@jeesite/core/hooks/web/useI18n';
|
||||
import { isArray } from '@jeesite/core/utils/is';
|
||||
import { FileUpload, uploadFileList } from '@jeesite/core/api/sys/upload';
|
||||
|
||||
const props = defineProps(uploadContainerProps);
|
||||
const emit = defineEmits(['change', 'delete', 'update:value', 'click']);
|
||||
const attrs = useAttrs();
|
||||
|
||||
const { t } = useI18n();
|
||||
const [registerUploadModal, { openModal }] = useModal();
|
||||
const [registerPreviewModal, { openModal: openPreviewModal }] = useModal();
|
||||
|
||||
function openUploadModal() {
|
||||
openModal();
|
||||
emit('click');
|
||||
}
|
||||
|
||||
const dataMap = ref<object>({});
|
||||
const fileList = ref<FileUpload[]>([]);
|
||||
const fileListDel = ref<FileUpload[]>([]);
|
||||
|
||||
const getShowPreview = computed(() => {
|
||||
const { showPreview, emptyHidePreview, showPreviewList, showPreviewNumber } = props;
|
||||
if (showPreviewList && !showPreviewNumber) return false;
|
||||
if (!showPreview) return false;
|
||||
if (!emptyHidePreview) return true;
|
||||
return emptyHidePreview ? fileList.value.length > 0 : true;
|
||||
});
|
||||
|
||||
const bindValue = computed(() => {
|
||||
const value = { ...attrs, ...props };
|
||||
return omit(value, 'onChange', 'class');
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(value) => {
|
||||
dataMap.value = value;
|
||||
emit('update:value', dataMap.value);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => [props.bizKey, props.loadTime],
|
||||
() => {
|
||||
loadFileList();
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
function loadFileList() {
|
||||
fileList.value = [];
|
||||
fileListDel.value = [];
|
||||
if (props.bizKey != '') {
|
||||
uploadFileList(
|
||||
{
|
||||
bizKey: props.bizKey,
|
||||
bizType: props.bizType,
|
||||
},
|
||||
props.apiFileListUrl,
|
||||
).then((res) => {
|
||||
if (isArray(res)) {
|
||||
fileList.value = res;
|
||||
dataMap.value[props.bizType + '__len'] = fileList.value.length;
|
||||
emit('update:value', dataMap.value);
|
||||
emit('change', dataMap.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 上传modal保存操作
|
||||
function handleChange(records: FileUpload[]) {
|
||||
fileList.value = [...unref(fileList), ...(records || [])];
|
||||
dataMap.value[props.bizType] = fileList.value.map((item) => item.id).join(',');
|
||||
dataMap.value[props.bizType + '__len'] = fileList.value.length;
|
||||
emit('update:value', dataMap.value);
|
||||
emit('change', dataMap.value, fileList.value);
|
||||
}
|
||||
|
||||
// 预览modal保存操作
|
||||
function handlePreviewChange(records: FileUpload[]) {
|
||||
fileList.value = [...(records || [])];
|
||||
dataMap.value[props.bizType] = fileList.value.map((item) => item.id).join(',');
|
||||
dataMap.value[props.bizType + '__len'] = fileList.value.length;
|
||||
emit('update:value', dataMap.value);
|
||||
emit('change', dataMap.value, fileList.value);
|
||||
}
|
||||
|
||||
function handleDelete(record: FileUpload) {
|
||||
fileListDel.value.push(record);
|
||||
dataMap.value[props.bizType + '__del'] = fileListDel.value.map((item) => item.id).join(',');
|
||||
dataMap.value[props.bizType + '__len'] = fileList.value.length;
|
||||
emit('delete', record);
|
||||
emit('update:value', dataMap.value);
|
||||
emit('change', dataMap.value, fileList.value);
|
||||
}
|
||||
</script>
|
||||
126
web-vue/packages/core/components/Upload/src/FileList.vue
Normal file
126
web-vue/packages/core/components/Upload/src/FileList.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<script lang="tsx">
|
||||
import { defineComponent, CSSProperties, nextTick, ref } from 'vue';
|
||||
import { useResizeObserver } from '@vueuse/core';
|
||||
import { fileListProps } from './props';
|
||||
import { isFunction } from '@jeesite/core/utils/is';
|
||||
import { useModalContext } from '@jeesite/core/components/Modal/src/hooks/useModalContext';
|
||||
import { get } from 'lodash-es';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FileList',
|
||||
props: fileListProps,
|
||||
setup(props) {
|
||||
const modalFn = useModalContext();
|
||||
const tableRef = ref<HTMLTableElement>();
|
||||
useResizeObserver(tableRef, () => {
|
||||
nextTick(() => {
|
||||
modalFn?.redoModalHeight?.();
|
||||
});
|
||||
});
|
||||
return () => {
|
||||
const { columns, actionColumn, dataSource } = props;
|
||||
const columnList = [...columns, actionColumn];
|
||||
return (
|
||||
<table class="file-table" ref={tableRef}>
|
||||
<colgroup>
|
||||
{columnList.map((item) => {
|
||||
const { width = 0, dataIndex } = item;
|
||||
const style: CSSProperties = {
|
||||
width: `${width}px`,
|
||||
minWidth: `${width}px`,
|
||||
};
|
||||
return <col style={width ? style : {}} key={dataIndex} />;
|
||||
})}
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr class="file-table-tr">
|
||||
{columnList.map((item) => {
|
||||
const { title = '', align = 'center', dataIndex } = item;
|
||||
return (
|
||||
dataIndex && (
|
||||
<th class={['file-table-th', align]} key={dataIndex}>
|
||||
{title}
|
||||
</th>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dataSource.map((record = {}, index) => {
|
||||
return (
|
||||
<tr class="file-table-tr" key={`${index + record.name || ''}`}>
|
||||
{columnList.map((item) => {
|
||||
const { dataIndex = '', customRender, align = 'center' } = item;
|
||||
const render = customRender && isFunction(customRender);
|
||||
return (
|
||||
dataIndex && (
|
||||
<td class={['file-table-td', align]} key={dataIndex}>
|
||||
{render
|
||||
? customRender?.({ text: get(record, dataIndex), record, index })
|
||||
: get(record, dataIndex)}
|
||||
</td>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
{dataSource.length == 0 && (
|
||||
<tr class="file-table-tr">
|
||||
<td class="file-table-td center" colspan={columnList.length}>
|
||||
{props.emptyText}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
.file-table {
|
||||
width: 100%;
|
||||
border-top: 1px solid @border-color-base;
|
||||
border-right: 1px solid @border-color-base;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
|
||||
&-th,
|
||||
&-td {
|
||||
border-left: 1px solid @border-color-base;
|
||||
border-bottom: 1px solid @border-color-base;
|
||||
padding: 12px 8px;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-all;
|
||||
white-space: normal;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
thead {
|
||||
background-color: @background-color-light;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.right {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme='dark'] {
|
||||
.file-table {
|
||||
thead {
|
||||
background-color: #1a1a1a;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
33
web-vue/packages/core/components/Upload/src/ThumbUrl.vue
Normal file
33
web-vue/packages/core/components/Upload/src/ThumbUrl.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<span class="thumb">
|
||||
<Image
|
||||
v-if="fileUrl"
|
||||
:src="fileUrl"
|
||||
:preview="{
|
||||
src: previewUrl || fileUrl,
|
||||
}"
|
||||
:width="104"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { propTypes } from '@jeesite/core/utils/propTypes';
|
||||
import { Image } from 'ant-design-vue';
|
||||
|
||||
defineProps({
|
||||
fileUrl: propTypes.string.def(''),
|
||||
fileName: propTypes.string.def(''),
|
||||
previewUrl: propTypes.string.def(''),
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
.thumb {
|
||||
img {
|
||||
position: static;
|
||||
display: block;
|
||||
cursor: zoom-in;
|
||||
border-radius: 4px;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
319
web-vue/packages/core/components/Upload/src/UploadModal.vue
Normal file
319
web-vue/packages/core/components/Upload/src/UploadModal.vue
Normal file
@@ -0,0 +1,319 @@
|
||||
<template>
|
||||
<BasicModal
|
||||
width="80%"
|
||||
:title="t('component.upload.upload')"
|
||||
:okText="t('component.upload.save')"
|
||||
v-bind="$attrs"
|
||||
@register="register"
|
||||
@ok="handleOk"
|
||||
:closeFunc="handleCloseFunc"
|
||||
:maskClosable="false"
|
||||
:keyboard="false"
|
||||
wrapClassName="upload-modal"
|
||||
:okButtonProps="getOkButtonProps"
|
||||
:cancelButtonProps="{ disabled: uploading }"
|
||||
>
|
||||
<template #centerFooter>
|
||||
<a-button
|
||||
@click="handleStartUpload"
|
||||
color="success"
|
||||
:disabled="!getIsSelectFile"
|
||||
:loading="uploading"
|
||||
v-show="isLazy"
|
||||
>
|
||||
{{ getUploadBtnText }}
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<div class="upload-modal-toolbar">
|
||||
<Alert :message="getHelpText" type="info" banner class="upload-modal-toolbar__text" />
|
||||
<Upload
|
||||
:accept="getStringAccept"
|
||||
:multiple="multiple"
|
||||
:before-upload="beforeUpload"
|
||||
:directory="directory"
|
||||
:show-upload-list="false"
|
||||
class="upload-modal-toolbar__btn"
|
||||
>
|
||||
<a-button type="primary">
|
||||
{{ t('component.upload.choose') }}
|
||||
</a-button>
|
||||
</Upload>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<FileList
|
||||
:dataSource="fileItemList"
|
||||
:columns="columns"
|
||||
:actionColumn="actionColumn"
|
||||
:emptyText="t('component.upload.fileListEmpty')"
|
||||
/>
|
||||
</div>
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
* @author ThinkGem
|
||||
*/
|
||||
import { ref, unref, computed } from 'vue';
|
||||
import { Upload, Alert } from 'ant-design-vue';
|
||||
import { BasicModal, useModalInner } from '@jeesite/core/components/Modal';
|
||||
// import { BasicTable, useTable } from '@jeesite/core/components/Table';
|
||||
import FileList from './FileList.vue';
|
||||
import { uploadProps } from './props';
|
||||
import { useUpload } from './useUpload';
|
||||
import { useMessage } from '@jeesite/core/hooks/web/useMessage';
|
||||
import { FileItem, UploadResultStatus } from './typing';
|
||||
import { createTableColumns, createActionColumn } from './data';
|
||||
import { checkImgType, getBase64WithFile } from './helper';
|
||||
import { buildUUID } from '@jeesite/core/utils/uuid';
|
||||
import { isFunction } from '@jeesite/core/utils/is';
|
||||
import { warn } from '@jeesite/core/utils/log';
|
||||
import { useI18n } from '@jeesite/core/hooks/web/useI18n';
|
||||
import { useGlobSetting } from '@jeesite/core/hooks/setting';
|
||||
import { FileUpload } from '@jeesite/core/api/sys/upload';
|
||||
import { openWindowLayer } from '@jeesite/core/utils';
|
||||
|
||||
const props = defineProps(uploadProps);
|
||||
const emit = defineEmits(['change', 'register', 'delete']);
|
||||
|
||||
const { t } = useI18n();
|
||||
const [register, { closeModal }] = useModalInner();
|
||||
const fileItemList = ref<FileItem[]>([]);
|
||||
const uploading = ref(false);
|
||||
|
||||
const { getStringAccept, getHelpText, getMaxFileSize, getUploadParams } = useUpload(props);
|
||||
|
||||
const { createMessage } = useMessage();
|
||||
|
||||
const getIsSelectFile = computed(() => {
|
||||
return (
|
||||
fileItemList.value.length > 0 && !fileItemList.value.every((item) => item.status === UploadResultStatus.SUCCESS)
|
||||
);
|
||||
});
|
||||
|
||||
const getOkButtonProps = computed(() => {
|
||||
const someSuccess = fileItemList.value.some((item) => item.status === UploadResultStatus.SUCCESS);
|
||||
return {
|
||||
disabled: uploading.value || fileItemList.value.length === 0 || !someSuccess,
|
||||
};
|
||||
});
|
||||
|
||||
const getUploadBtnText = computed(() => {
|
||||
const someError = fileItemList.value.some((item) => item.status === UploadResultStatus.ERROR);
|
||||
return uploading.value
|
||||
? t('component.upload.uploading')
|
||||
: someError
|
||||
? t('component.upload.reUploadFailed')
|
||||
: t('component.upload.startUpload');
|
||||
});
|
||||
|
||||
// 上传前校验
|
||||
function beforeUpload(file: File) {
|
||||
const { size, name } = file;
|
||||
const { bizKey, bizType, uploadType } = props;
|
||||
// 设置最大值,则判断
|
||||
if (file.size >= unref(getMaxFileSize)) {
|
||||
createMessage.error(t('component.upload.maxSizeMultiple', [unref(getMaxFileSize) / 1024 / 1024]));
|
||||
return false;
|
||||
}
|
||||
const id = buildUUID();
|
||||
const commonItem = {
|
||||
id,
|
||||
file,
|
||||
size,
|
||||
name,
|
||||
percent: 0,
|
||||
type: name.split('.').pop(),
|
||||
fileMd5: id,
|
||||
fileName: name,
|
||||
fileUploadId: '',
|
||||
fileEntityId: '',
|
||||
bizKey,
|
||||
bizType,
|
||||
uploadType,
|
||||
} as FileItem;
|
||||
function addFileItem() {
|
||||
// 生成图片缩略图
|
||||
if (checkImgType(file)) {
|
||||
getBase64WithFile(file).then(({ result: fileUrl }) => {
|
||||
addFileItemList({
|
||||
fileUrl,
|
||||
...commonItem,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
addFileItemList(commonItem);
|
||||
}
|
||||
}
|
||||
addFileItem();
|
||||
return false;
|
||||
}
|
||||
|
||||
function addFileItemList(record: FileItem) {
|
||||
const { maxNumber } = props;
|
||||
if (fileItemList.value.length + (props.previewFileList?.length || 0) >= maxNumber) {
|
||||
createMessage.warning(t('component.upload.maxNumber', [maxNumber]));
|
||||
return;
|
||||
}
|
||||
fileItemList.value = [...unref(fileItemList), record];
|
||||
if (!props.isLazy) {
|
||||
uploadApiByItem(fileItemList.value[fileItemList.value.length - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
function handleRemove(record: FileItem) {
|
||||
const index = fileItemList.value.findIndex((item) => item.id === record.id);
|
||||
if (index !== -1) {
|
||||
const removed = fileItemList.value.splice(index, 1);
|
||||
const item = removed[0] as FileItem;
|
||||
if (item && item.responseData?.fileUpload) {
|
||||
emit('delete', item.responseData?.fileUpload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 开始上传文件
|
||||
async function uploadApiByItem(item: FileItem) {
|
||||
const { api } = props;
|
||||
if (!api || !isFunction(api)) {
|
||||
return warn('upload api must exist and be a function');
|
||||
}
|
||||
|
||||
try {
|
||||
item.status = UploadResultStatus.UPLOADING;
|
||||
if (item.percent != 100) {
|
||||
await uploadFile(item);
|
||||
}
|
||||
item.status = UploadResultStatus.SUCCESS;
|
||||
return {
|
||||
success: true,
|
||||
error: null,
|
||||
};
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
item.status = UploadResultStatus.ERROR;
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
async function uploadFile(item: FileItem) {
|
||||
const { api } = props;
|
||||
try {
|
||||
const params = unref(getUploadParams);
|
||||
const { data } = await api(
|
||||
{
|
||||
bizKey: item.bizKey,
|
||||
bizType: item.bizType,
|
||||
uploadType: item.uploadType,
|
||||
fileMd5: item.fileMd5,
|
||||
fileName: item.fileName,
|
||||
fileUploadId: item.fileUploadId,
|
||||
fileEntityId: item.fileEntityId,
|
||||
imageMaxWidth: params.imageMaxWidth || '',
|
||||
imageMaxHeight: params.imageMaxHeight || '',
|
||||
...(props.uploadParams || {}),
|
||||
file: item.file,
|
||||
},
|
||||
(progressEvent: ProgressEvent) => {
|
||||
item.percent = ((progressEvent.loaded / progressEvent.total) * 100) | 0;
|
||||
},
|
||||
props.apiUploadUrl,
|
||||
);
|
||||
item.responseData = data;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// 点击开始上传
|
||||
async function handleStartUpload() {
|
||||
if (uploading.value) return;
|
||||
uploading.value = true;
|
||||
try {
|
||||
// 只上传不是成功状态的
|
||||
const uploadFileList = fileItemList.value.filter((item) => item.status !== UploadResultStatus.SUCCESS) || [];
|
||||
const data = await Promise.all(
|
||||
uploadFileList.map((item) => {
|
||||
return uploadApiByItem(item);
|
||||
}),
|
||||
);
|
||||
// 生产环境:抛出错误
|
||||
const errorList = data.filter((item: any) => item.result === 'false');
|
||||
if (errorList.length > 0) throw errorList;
|
||||
} finally {
|
||||
uploading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 点击保存
|
||||
function handleOk() {
|
||||
const { maxNumber } = props;
|
||||
|
||||
if (fileItemList.value.length > maxNumber) {
|
||||
return createMessage.warning(t('component.upload.maxNumber', [maxNumber]));
|
||||
}
|
||||
if (uploading.value) {
|
||||
return createMessage.warning(t('component.upload.saveWarn'));
|
||||
}
|
||||
const fileList: FileUpload[] = [];
|
||||
|
||||
for (const item of fileItemList.value) {
|
||||
const { status, responseData } = item;
|
||||
if (status === UploadResultStatus.SUCCESS && responseData) {
|
||||
fileList.push(responseData.fileUpload);
|
||||
}
|
||||
}
|
||||
// 存在一个上传成功的即可保存
|
||||
if (fileList.length <= 0) {
|
||||
return createMessage.warning(t('component.upload.saveError'));
|
||||
}
|
||||
fileItemList.value = [];
|
||||
closeModal();
|
||||
emit('change', fileList);
|
||||
}
|
||||
|
||||
// 点击关闭:则所有操作不保存,包括上传的
|
||||
async function handleCloseFunc() {
|
||||
if (!uploading.value) {
|
||||
fileItemList.value = [];
|
||||
return true;
|
||||
} else {
|
||||
createMessage.warning(t('component.upload.uploadWait'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const columns = createTableColumns() as any[];
|
||||
|
||||
const actionColumn = createActionColumn(handleRemove) as any;
|
||||
</script>
|
||||
<style lang="less">
|
||||
.upload-modal {
|
||||
.ant-upload-list {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ant-table-wrapper .ant-spin-nested-loading {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
&__btn {
|
||||
margin-left: 8px;
|
||||
text-align: right;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<BasicModal
|
||||
v-if="!props.showPreviewList"
|
||||
width="80%"
|
||||
:title="t('component.upload.view')"
|
||||
:cancelText="t('component.modal.okText')"
|
||||
wrapClassName="upload-preview-modal"
|
||||
v-bind="$attrs"
|
||||
@register="register"
|
||||
:showOkBtn="false"
|
||||
>
|
||||
<div class="overflow-x-auto">
|
||||
<FileList
|
||||
:dataSource="fileList"
|
||||
:columns="columns"
|
||||
:actionColumn="actionColumn"
|
||||
:readonly="readonly"
|
||||
:emptyText="t('component.upload.fileListEmpty')"
|
||||
/>
|
||||
</div>
|
||||
</BasicModal>
|
||||
<div v-else-if="props.emptyHidePreview ? fileList.length > 0 : true" class="overflow-x-auto mt-3">
|
||||
<FileList
|
||||
:dataSource="fileList"
|
||||
:columns="columns"
|
||||
:actionColumn="actionColumn"
|
||||
:readonly="readonly"
|
||||
:emptyText="t('component.upload.fileListEmpty')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { watch, ref, useAttrs } from 'vue';
|
||||
import { BasicModal, useModalInner } from '@jeesite/core/components/Modal';
|
||||
// import { BasicTable, useTable } from '@jeesite/core/components/Table';
|
||||
import FileList from './FileList.vue';
|
||||
import { previewProps } from './props';
|
||||
import { downloadByUrl } from '@jeesite/core/utils/file/download';
|
||||
import { createPreviewColumns, createPreviewActionColumn } from './data';
|
||||
import { useI18n } from '@jeesite/core/hooks/web/useI18n';
|
||||
import { useGlobSetting } from '@jeesite/core/hooks/setting';
|
||||
import { isArray } from '@jeesite/core/utils/is';
|
||||
import { FileUpload } from '@jeesite/core/api/sys/upload';
|
||||
|
||||
const props = defineProps(previewProps);
|
||||
const emit = defineEmits(['change', 'register', 'delete']);
|
||||
|
||||
const [register] = useModalInner();
|
||||
const { t } = useI18n();
|
||||
const fileList = ref<FileUpload[]>([]);
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(value) => {
|
||||
if (!isArray(value)) value = [];
|
||||
fileList.value = value;
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
// 删除
|
||||
function handleRemove(record: FileUpload) {
|
||||
const index = fileList.value.findIndex((item) => item.id === record.id);
|
||||
if (index !== -1) {
|
||||
const removed = fileList.value.splice(index, 1);
|
||||
emit('delete', removed[0]);
|
||||
emit('change', fileList.value);
|
||||
}
|
||||
}
|
||||
|
||||
// 下载
|
||||
function handleDownload(record: FileUpload) {
|
||||
downloadByUrl({ url: props.apiDownloadUrl + '/' + record.id });
|
||||
}
|
||||
|
||||
const columns = createPreviewColumns(props) as any[];
|
||||
|
||||
const actionColumn = createPreviewActionColumn(
|
||||
{
|
||||
handleRemove,
|
||||
handleDownload,
|
||||
},
|
||||
props.readonly,
|
||||
) as any;
|
||||
</script>
|
||||
<style lang="less">
|
||||
.upload-preview-modal {
|
||||
.ant-upload-list {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ant-table-wrapper .ant-spin-nested-loading {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
198
web-vue/packages/core/components/Upload/src/data.tsx
Normal file
198
web-vue/packages/core/components/Upload/src/data.tsx
Normal file
@@ -0,0 +1,198 @@
|
||||
import type { BasicColumn, ActionItem } from '@jeesite/core/components/Table';
|
||||
import { FileItem, UploadResultStatus } from './typing';
|
||||
import { formatSize, isImgTypeByName } from './helper';
|
||||
import { Avatar, Progress, Tag } from 'ant-design-vue';
|
||||
import { Icon } from '@jeesite/core/components/Icon';
|
||||
import TableAction from '@jeesite/core/components/Table/src/components/TableAction.vue';
|
||||
import ThumbUrl from './ThumbUrl.vue';
|
||||
import { useI18n } from '@jeesite/core/hooks/web/useI18n';
|
||||
import { FileUpload } from '@jeesite/core/api/sys/upload';
|
||||
import { useGlobSetting } from '@jeesite/core/hooks/setting';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { ctxPath } = useGlobSetting();
|
||||
|
||||
// 文件上传列表
|
||||
export function createTableColumns(): BasicColumn[] {
|
||||
return [
|
||||
{
|
||||
dataIndex: 'fileUrl',
|
||||
title: t('component.upload.legend'),
|
||||
width: 100,
|
||||
customRender: ({ record, index }) => {
|
||||
const { fileUrl, type, fileEntity } = (record as FileUpload) || {};
|
||||
let url = fileUrl || '';
|
||||
if (!url.startsWith('data:image/') && url.indexOf('://') == -1) {
|
||||
url = ctxPath + url;
|
||||
}
|
||||
if (isImgTypeByName(url)) {
|
||||
return <ThumbUrl fileUrl={url} />;
|
||||
}
|
||||
const ext = type || fileEntity?.fileExtension || <Icon icon="i-ant-design:file-outlined" />;
|
||||
const color = ['#f56a00', '#7265e6', '#ffbf00', '#00a2ae'][index % 4];
|
||||
return <Avatar style={{ backgroundColor: color, verticalAlign: 'middle' }}>{ext}</Avatar>;
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'fileName',
|
||||
title: t('component.upload.fileName'),
|
||||
align: 'left',
|
||||
customRender: ({ text, record }) => {
|
||||
const { percent, status: uploadStatus } = (record as FileItem) || {};
|
||||
let status: 'normal' | 'exception' | 'active' | 'success' = '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="mb-1" title={text}>
|
||||
{text}
|
||||
</p>
|
||||
<Progress percent={percent} size="small" status={status} />
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'size',
|
||||
title: t('component.upload.fileSize'),
|
||||
width: 100,
|
||||
customRender: ({ text = 0 }) => {
|
||||
return text && formatSize(text);
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'status',
|
||||
title: t('component.upload.fileStatue'),
|
||||
width: 100,
|
||||
customRender: ({ text, record }) => {
|
||||
const { responseData } = (record as FileItem) || {};
|
||||
if (text === UploadResultStatus.SUCCESS) {
|
||||
return <Tag color="green">{() => responseData?.message || t('component.upload.uploadSuccess')}</Tag>;
|
||||
} else if (text === UploadResultStatus.ERROR) {
|
||||
return <Tag color="red">{() => responseData?.message || t('component.upload.uploadError')}</Tag>;
|
||||
} else if (text === UploadResultStatus.UPLOADING) {
|
||||
return <Tag color="blue">{() => responseData?.message || t('component.upload.uploading')}</Tag>;
|
||||
}
|
||||
return text;
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
export function createActionColumn(handleRemove: Function): BasicColumn {
|
||||
return {
|
||||
width: 120,
|
||||
title: t('component.upload.operating'),
|
||||
dataIndex: 'actions',
|
||||
align: 'center',
|
||||
fixed: false,
|
||||
customRender: ({ record }) => {
|
||||
const actions: ActionItem[] = [
|
||||
{
|
||||
label: t('component.upload.del'),
|
||||
color: 'error',
|
||||
popConfirm: {
|
||||
title: t('component.upload.delConfirm'),
|
||||
confirm: handleRemove.bind(null, record),
|
||||
},
|
||||
},
|
||||
];
|
||||
return <TableAction actions={actions} outside={true} />;
|
||||
},
|
||||
};
|
||||
}
|
||||
// 文件预览列表
|
||||
export function createPreviewColumns(props: any): BasicColumn[] {
|
||||
return [
|
||||
{
|
||||
dataIndex: 'fileUrl',
|
||||
title: t('component.upload.legend'),
|
||||
width: 100,
|
||||
customRender: ({ record, index }) => {
|
||||
const { fileUrl, type, fileEntity } = (record as FileUpload) || {};
|
||||
let url = fileUrl || '';
|
||||
let previewUrl;
|
||||
if (!url.startsWith('data:image/') && url.indexOf('://') == -1) {
|
||||
url = ctxPath + url;
|
||||
if (props.imageThumbName) {
|
||||
previewUrl = url;
|
||||
if (url.indexOf('?') == -1) {
|
||||
url += '.' + props.imageThumbName;
|
||||
} else {
|
||||
url = url.replace('?', '.' + props.imageThumbName + '?');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isImgTypeByName(url)) {
|
||||
return <ThumbUrl fileUrl={url} previewUrl={previewUrl} />;
|
||||
}
|
||||
const ext = type || fileEntity?.fileExtension || <Icon icon="i-ant-design:file-outlined" />;
|
||||
const color = ['#f56a00', '#7265e6', '#ffbf00', '#00a2ae'][index % 4];
|
||||
return <Avatar style={{ backgroundColor: color, verticalAlign: 'middle' }}>{ext}</Avatar>;
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'fileName',
|
||||
title: t('component.upload.fileName'),
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
dataIndex: 'fileEntity.fileSize',
|
||||
title: t('component.upload.fileSize'),
|
||||
width: 100,
|
||||
customRender: ({ text = 0 }) => {
|
||||
return text && formatSize(text);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('上传时间'),
|
||||
dataIndex: 'createDate',
|
||||
width: 130,
|
||||
align: 'center',
|
||||
customRender: ({ text = 0 }) => {
|
||||
return <span class="truncate">{text}</span>;
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function createPreviewActionColumn(
|
||||
{
|
||||
handleRemove,
|
||||
handleDownload,
|
||||
}: {
|
||||
handleRemove: Fn;
|
||||
handleDownload: Fn;
|
||||
},
|
||||
readonly = false,
|
||||
): BasicColumn {
|
||||
return {
|
||||
width: 160,
|
||||
title: t('component.upload.operating'),
|
||||
dataIndex: 'actions',
|
||||
align: 'center',
|
||||
fixed: false,
|
||||
customRender: ({ record }) => {
|
||||
const actions: ActionItem[] = [];
|
||||
if (!readonly) {
|
||||
actions.push({
|
||||
label: t('component.upload.del'),
|
||||
color: 'error',
|
||||
popConfirm: {
|
||||
title: t('component.upload.delConfirm'),
|
||||
confirm: handleRemove.bind(null, record),
|
||||
},
|
||||
});
|
||||
}
|
||||
actions.push({
|
||||
label: t('component.upload.download'),
|
||||
onClick: handleDownload.bind(null, record),
|
||||
});
|
||||
return <TableAction actions={actions} outside={true} />;
|
||||
},
|
||||
};
|
||||
}
|
||||
42
web-vue/packages/core/components/Upload/src/helper.ts
Normal file
42
web-vue/packages/core/components/Upload/src/helper.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
* @author ThinkGem
|
||||
*/
|
||||
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');
|
||||
|
||||
return reg.test(file.name);
|
||||
}
|
||||
|
||||
export function checkImgType(file: File) {
|
||||
return isImgTypeByName(file.name);
|
||||
}
|
||||
|
||||
export function isImgTypeByName(name: string) {
|
||||
return name.startsWith('data:image/') || /\.(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);
|
||||
});
|
||||
}
|
||||
|
||||
// 格式化文件大小, 输出成带单位的字符串 think gem
|
||||
export function formatSize(size: number, pointLength = 2, units = ['B', 'K', 'M', 'G', 'TB']) {
|
||||
if (!size) return '';
|
||||
let unit;
|
||||
while ((unit = units.shift()) && size > 1024) {
|
||||
size = size / 1024;
|
||||
}
|
||||
return (unit === 'B' ? size : size.toFixed(pointLength)) + unit;
|
||||
}
|
||||
206
web-vue/packages/core/components/Upload/src/props.ts
Normal file
206
web-vue/packages/core/components/Upload/src/props.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
import type { PropType } from 'vue';
|
||||
import { FileBasicColumn } from './typing';
|
||||
import { FileUpload, uploadFile } from '@jeesite/core/api/sys/upload';
|
||||
import { useGlobSetting } from '@jeesite/core/hooks/setting';
|
||||
import type { SizeType } from '@jeesite/core/components/Table';
|
||||
import { DEFAULT_SIZE } from '@jeesite/core/components/Table/src/const';
|
||||
|
||||
type UploadType = 'image' | 'media' | 'file' | 'all';
|
||||
|
||||
/**
|
||||
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
* @author ThinkGem
|
||||
*/
|
||||
const { ctxAdminPath } = useGlobSetting();
|
||||
|
||||
export const basicProps = {
|
||||
uploadText: {
|
||||
type: String as PropType<string>,
|
||||
default: '',
|
||||
},
|
||||
helpText: {
|
||||
type: String as PropType<string>,
|
||||
default: '',
|
||||
},
|
||||
// 文件最大多少MB
|
||||
maxSize: {
|
||||
type: Number as PropType<number>,
|
||||
default: null, // 默认从后台获取
|
||||
},
|
||||
// 最大数量的文件,Infinity不限制
|
||||
maxNumber: {
|
||||
type: Number as PropType<number>,
|
||||
default: Infinity,
|
||||
},
|
||||
// 根据后缀,或者其他
|
||||
accept: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
uploadParams: {
|
||||
type: Object as PropType<any>,
|
||||
default: {},
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
api: {
|
||||
type: Function as PropType<PromiseFn>,
|
||||
default: uploadFile,
|
||||
},
|
||||
apiUploadUrl: {
|
||||
type: String as PropType<string>,
|
||||
default: ctxAdminPath + '/file/upload',
|
||||
},
|
||||
apiDownloadUrl: {
|
||||
type: String as PropType<string>,
|
||||
default: ctxAdminPath + '/file/download',
|
||||
},
|
||||
apiFileListUrl: {
|
||||
type: String as PropType<string>,
|
||||
default: ctxAdminPath + '/file/fileList',
|
||||
},
|
||||
// 选择文件后,是否需要点击上传按钮再上传文件
|
||||
isLazy: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
// 业务主键
|
||||
bizKey: {
|
||||
type: [String, Number] as PropType<string | number>,
|
||||
default: '',
|
||||
},
|
||||
// 业务类型
|
||||
bizType: {
|
||||
type: String as PropType<string>,
|
||||
default: '',
|
||||
},
|
||||
// 上传类型
|
||||
uploadType: {
|
||||
type: String as PropType<UploadType>,
|
||||
default: 'all',
|
||||
},
|
||||
// 图片压缩最大宽度
|
||||
imageMaxWidth: {
|
||||
type: Number as PropType<number>,
|
||||
default: null, // 默认从后台获取
|
||||
},
|
||||
// 图片压缩最大高度
|
||||
imageMaxHeight: {
|
||||
type: Number as PropType<number>,
|
||||
default: null, // 默认从后台获取
|
||||
},
|
||||
// 如果开启了图片缩略图,这里可以指定缩略图名称,例如:150x150.jpg v5.4.2
|
||||
imageThumbName: {
|
||||
type: String as PropType<string>,
|
||||
default: '',
|
||||
},
|
||||
// 是否启用秒传(标准版/专业版)
|
||||
checkmd5: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: null, // 默认从后台获取
|
||||
},
|
||||
// 是否开启分片上传(标准版/专业版)
|
||||
chunked: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: null, // 默认从后台获取
|
||||
},
|
||||
// 分片大小(字节)(标准版/专业版)
|
||||
chunkSize: {
|
||||
type: Number as PropType<number>,
|
||||
default: null, // 默认从后台获取
|
||||
},
|
||||
// 最大上传线程数(标准版/专业版)
|
||||
threads: {
|
||||
type: Number as PropType<number>,
|
||||
default: null, // 默认从后台获取
|
||||
},
|
||||
// 是否文件夹上传(caniuse)
|
||||
directory: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
// 预览按钮大小
|
||||
size: {
|
||||
type: String as PropType<SizeType>,
|
||||
default: DEFAULT_SIZE,
|
||||
},
|
||||
};
|
||||
|
||||
export const uploadContainerProps = {
|
||||
...basicProps,
|
||||
value: {
|
||||
type: Object as PropType<any>,
|
||||
default: {},
|
||||
},
|
||||
// 是否显示预览按钮
|
||||
showPreview: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
// 是否显示已上传的文件个数
|
||||
showPreviewNumber: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
// 直接在表单里显示预览文件列表
|
||||
showPreviewList: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
// 预览表格为空的时候,是否不显示预览框
|
||||
emptyHidePreview: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
// 加载时间戳,此为监听属性,方便刷新文件列表数据
|
||||
loadTime: {
|
||||
type: Number as PropType<number>,
|
||||
default: 0,
|
||||
},
|
||||
};
|
||||
|
||||
export const uploadProps = {
|
||||
...basicProps,
|
||||
previewFileList: {
|
||||
type: Array as PropType<FileUpload[]>,
|
||||
default: () => [],
|
||||
},
|
||||
};
|
||||
|
||||
export const previewProps = {
|
||||
...uploadContainerProps,
|
||||
value: {
|
||||
type: Array as PropType<FileUpload[]>,
|
||||
default: () => [],
|
||||
},
|
||||
};
|
||||
|
||||
export const fileListProps = {
|
||||
columns: {
|
||||
type: [Array] as PropType<FileBasicColumn[]>,
|
||||
default: null,
|
||||
},
|
||||
actionColumn: {
|
||||
type: Object as PropType<FileBasicColumn>,
|
||||
default: null,
|
||||
},
|
||||
dataSource: {
|
||||
type: Array as PropType<any[]>,
|
||||
default: null,
|
||||
},
|
||||
emptyText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
};
|
||||
70
web-vue/packages/core/components/Upload/src/typing.ts
Normal file
70
web-vue/packages/core/components/Upload/src/typing.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
* @author ThinkGem
|
||||
*/
|
||||
import { UploadApiResult } from '@jeesite/core/api/sys/upload';
|
||||
|
||||
export enum UploadResultStatus {
|
||||
SUCCESS = 'success',
|
||||
ERROR = 'error',
|
||||
UPLOADING = 'uploading',
|
||||
}
|
||||
|
||||
export interface FileItem {
|
||||
file: File;
|
||||
size: string | number;
|
||||
type?: string;
|
||||
percent: number;
|
||||
status?: UploadResultStatus;
|
||||
responseData?: UploadApiResult;
|
||||
// jeesite
|
||||
id: string;
|
||||
fileMd5: string;
|
||||
fileName: string;
|
||||
fileUploadId: string;
|
||||
fileEntityId: string;
|
||||
bizKey: string;
|
||||
bizType: string;
|
||||
uploadType: string;
|
||||
imageMaxWidth?: string | number;
|
||||
imageMaxHeight?: string | number;
|
||||
fileUrl?: string;
|
||||
uploadInfo?: string;
|
||||
}
|
||||
|
||||
// export interface PreviewFileItem extends FileItem {
|
||||
// url: string;
|
||||
// name: string;
|
||||
// type: string;
|
||||
// }
|
||||
|
||||
export interface FileBasicColumn {
|
||||
/**
|
||||
* Renderer of the table cell. The return value should be a VNode, or an object for colSpan/rowSpan config
|
||||
* @type Function | ScopedSlot
|
||||
*/
|
||||
customRender?: Function;
|
||||
/**
|
||||
* Title of this column
|
||||
* @type any (string | slot)
|
||||
*/
|
||||
title: string;
|
||||
|
||||
/**
|
||||
* Width of this column
|
||||
* @type string | number
|
||||
*/
|
||||
width?: number;
|
||||
/**
|
||||
* Display field of the data record, could be set like a.b.c
|
||||
* @type string
|
||||
*/
|
||||
dataIndex: string;
|
||||
/**
|
||||
* specify how content is aligned
|
||||
* @default 'left'
|
||||
* @type string
|
||||
*/
|
||||
align?: 'left' | 'right' | 'center';
|
||||
}
|
||||
100
web-vue/packages/core/components/Upload/src/useUpload.ts
Normal file
100
web-vue/packages/core/components/Upload/src/useUpload.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { computed, ComputedRef, onMounted, Ref, ref, ToRefs, unref } from 'vue';
|
||||
import { UploadParams, uploadParams } from '@jeesite/core/api/sys/upload';
|
||||
import { useI18n } from '@jeesite/core/hooks/web/useI18n';
|
||||
|
||||
export function useUpload(props: any) {
|
||||
const { t } = useI18n();
|
||||
|
||||
const uploadParamsRef = ref<UploadParams>({} as any);
|
||||
|
||||
onMounted(async () => {
|
||||
uploadParamsRef.value = await uploadParams();
|
||||
});
|
||||
|
||||
const getUploadParams = computed(() => {
|
||||
const params = unref(uploadParamsRef);
|
||||
if (props.checkmd5 != null) {
|
||||
params.checkmd5 = props.checkmd5;
|
||||
}
|
||||
if (props.imageMaxWidth) {
|
||||
params.imageMaxWidth = props.imageMaxWidth;
|
||||
}
|
||||
if (props.imageMaxHeight) {
|
||||
params.imageMaxHeight = props.imageMaxHeight;
|
||||
}
|
||||
return params;
|
||||
});
|
||||
|
||||
// 上传最大文件大小(字节)
|
||||
const getMaxFileSize = computed(() => {
|
||||
const maxSize = props.maxSize;
|
||||
if (maxSize) {
|
||||
return maxSize * 1024 * 1024;
|
||||
} else {
|
||||
const { maxFileSize } = unref(uploadParamsRef);
|
||||
if (maxFileSize) {
|
||||
return maxFileSize;
|
||||
}
|
||||
}
|
||||
return 500 * 1024 * 1024;
|
||||
});
|
||||
|
||||
// 文件类型限制
|
||||
const getAccept = computed(() => {
|
||||
const accept = props.accept;
|
||||
if (accept && accept.length > 0) {
|
||||
return accept;
|
||||
}
|
||||
const { imageAllowSuffixes, mediaAllowSuffixes, fileAllowSuffixes } = unref(uploadParamsRef);
|
||||
const uploadType = props.uploadType;
|
||||
if (uploadType == 'image') {
|
||||
return imageAllowSuffixes.split(',');
|
||||
} else if (uploadType == 'media') {
|
||||
return mediaAllowSuffixes.split(',');
|
||||
} else if (uploadType == 'file') {
|
||||
return fileAllowSuffixes.split(',');
|
||||
} else {
|
||||
return [...imageAllowSuffixes.split(','), ...mediaAllowSuffixes.split(','), ...fileAllowSuffixes.split(',')];
|
||||
}
|
||||
});
|
||||
|
||||
// 文件类型限制(文件选择框类型)
|
||||
const getStringAccept = computed(() => {
|
||||
return unref(getAccept)
|
||||
.map((item: any) => {
|
||||
if (item.indexOf('/') > 0 || item.startsWith('.')) {
|
||||
return item;
|
||||
} else {
|
||||
return `.${item}`;
|
||||
}
|
||||
})
|
||||
.join(',');
|
||||
});
|
||||
|
||||
// 支持jpg、jpeg、png格式,不超过2M,最多可选择10张图片
|
||||
const getHelpText = computed(() => {
|
||||
const helpText = props.helpText;
|
||||
if (helpText) {
|
||||
return helpText;
|
||||
}
|
||||
const helpTexts: string[] = [];
|
||||
|
||||
const accept = props.accept;
|
||||
if (accept.length > 0) {
|
||||
helpTexts.push(t('component.upload.accept', [accept.join(',')]));
|
||||
}
|
||||
|
||||
const maxSize = unref(getMaxFileSize);
|
||||
if (maxSize) {
|
||||
helpTexts.push(t('component.upload.maxSize', [maxSize / 1024 / 1024]));
|
||||
}
|
||||
|
||||
const maxNumber = props.maxNumber;
|
||||
if (maxNumber && maxNumber !== Infinity) {
|
||||
helpTexts.push(t('component.upload.maxNumber', [maxNumber]));
|
||||
}
|
||||
return helpTexts.join(',');
|
||||
});
|
||||
|
||||
return { getUploadParams, getMaxFileSize, getAccept, getStringAccept, getHelpText };
|
||||
}
|
||||
Reference in New Issue
Block a user