初始版本提交

This commit is contained in:
yaoyn
2024-02-05 09:15:37 +08:00
parent b52d4414be
commit 445292105f
1848 changed files with 236859 additions and 75 deletions

View File

@ -0,0 +1,4 @@
import { withInstall } from '/@/utils';
import basicUpload from './src/BasicUpload.vue';
export const BasicUpload = withInstall(basicUpload);

View File

@ -0,0 +1,146 @@
<template>
<div>
<Space>
<a-button
type="primary"
@click="openUploadModal"
:disabled="bindValue.disabled"
preIcon="carbon:cloud-upload"
>
上传
</a-button>
<Tooltip placement="bottom" v-if="showPreview">
<template #title>
{{ t('component.upload.uploaded') }}
<template v-if="fileList.length">
{{ fileList.length }}
</template>
</template>
<a-button :disabled="bindValue.disabled" @click="openPreviewModal">
<Icon icon="bi:eye" />
<template v-if="fileList.length && showPreviewNumber">
{{ fileList.length }}
</template>
</a-button>
</Tooltip>
</Space>
<UploadModal
v-bind="bindValue"
:previewFileList="fileList"
:folderId="folderId"
@register="registerUploadModal"
@change="handleChange"
@delete="handleDelete"
/>
<UploadPreviewModal
:value="fileList"
:file-names="fileNameList"
@register="registerPreviewModal"
@list-change="handlePreviewChange"
@delete="handlePreviewDelete"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch, unref, computed } from 'vue';
import { Icon } from '/@/components/Icon';
import { Tooltip, Space } from 'ant-design-vue';
import { useModal } from '/@/components/Modal';
import { uploadContainerProps } from './props';
import { omit } from 'lodash-es';
import { useI18n } from '/@/hooks/web/useI18n';
import UploadModal from './UploadModal.vue';
import UploadPreviewModal from './UploadPreviewModal.vue';
import { getFileList } from '/@/api/system/file';
export default defineComponent({
name: 'BasicUpload',
components: { UploadModal, Space, UploadPreviewModal, Icon, Tooltip },
props: uploadContainerProps,
emits: ['change', 'delete', 'preview-delete', 'update:value'],
setup(props, { emit, attrs }) {
console.log('props.value1111', props.api);
const { t } = useI18n();
// 上传modal
const [registerUploadModal, { openModal: openUploadModal }] = useModal();
// 预览modal
const [registerPreviewModal, { openModal: openPreviewModal }] = useModal();
const fileList = ref<string[]>([]);
const fileNameList = ref<string[]>([]);
const folderId = computed(() => props.value);
const showPreview = computed(() => {
const { emptyHidePreview } = props;
if (!emptyHidePreview) return true;
return emptyHidePreview ? fileList.value.length > 0 : true;
});
const bindValue = computed(() => {
const value = { ...attrs, ...props };
return omit(value, 'onChange');
});
watch(
() => props.value,
async (value) => {
//如果没有传入参数 默认不再请求文件列表
if (value && value.length > 0) {
const list = await getFileList({ folderId: value });
fileList.value = list.map((item) => item.fileUrl);
fileNameList.value = list.map((item) => item.fileName);
} else {
fileList.value = [];
}
emit('update:value', value);
emit('change', value);
},
{ immediate: true },
);
// 上传modal保存操作
function handleChange(urls: string[], folderId: string, fileNames: string[]) {
fileList.value = [...unref(fileList), ...(urls || [])];
fileNameList.value = [...unref(fileNameList), ...(fileNames || [])];
emit('update:value', folderId);
emit('change', folderId);
}
// 预览modal保存操作
function handlePreviewChange(urls: string[], folderId: string, fileNames: string[]) {
fileList.value = [...(urls || [])];
fileNameList.value = [...(fileNames || [])];
emit('update:value', folderId);
emit('change', folderId);
}
function handleDelete(record: Recordable) {
emit('delete', record);
}
function handlePreviewDelete(url: string) {
emit('preview-delete', url);
}
return {
registerUploadModal,
openUploadModal,
handleChange,
handlePreviewChange,
registerPreviewModal,
openPreviewModal,
fileList,
fileNameList,
showPreview,
bindValue,
handleDelete,
handlePreviewDelete,
folderId,
t,
};
},
});
</script>

View File

@ -0,0 +1,104 @@
<script lang="tsx">
import { defineComponent, CSSProperties, watch, nextTick } from 'vue';
import { fileListProps } from './props';
import { isFunction } from '/@/utils/is';
import { useModalContext } from '/@/components/Modal/src/hooks/useModalContext';
export default defineComponent({
name: 'FileList',
props: fileListProps,
setup(props) {
const modalFn = useModalContext();
watch(
() => props.dataSource,
() => {
nextTick(() => {
modalFn?.redoModalHeight?.();
});
},
);
return () => {
const { columns, actionColumn, dataSource } = props;
const columnList = [...columns, actionColumn];
return (
<table class="file-table">
<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 (
<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 (
<td class={['file-table-td', align]} key={dataIndex}>
{render
? customRender?.({ text: record[dataIndex], record })
: record[dataIndex]}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
);
};
},
});
</script>
<style lang="less">
.file-table {
width: 100%;
border-collapse: collapse;
.center {
text-align: center;
}
.left {
text-align: left;
}
.right {
text-align: right;
}
&-th,
&-td {
padding: 12px 8px;
}
thead {
background-color: @background-color-light;
}
table,
td,
th {
border: 1px solid @border-color-base;
}
}
</style>

View File

@ -0,0 +1,29 @@
<template>
<span class="thumb">
<Image v-if="fileUrl" :src="fileUrl" :width="104" />
</span>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { propTypes } from '/@/utils/propTypes';
import { Image } from 'ant-design-vue';
export default defineComponent({
components: { Image },
props: {
fileUrl: propTypes.string.def(''),
fileName: propTypes.string.def(''),
},
});
</script>
<style lang="less">
.thumb {
img {
position: static;
display: block;
cursor: zoom-in;
border-radius: 4px;
object-fit: cover;
}
}
</style>

View File

@ -0,0 +1,342 @@
<template>
<BasicModal
width="800px"
:title="t('component.upload.upload')"
:okText="t('component.upload.save')"
v-bind="$attrs"
@register="register"
@ok="handleOk"
:closeFunc="handleCloseFunc"
:maskClosable="false"
:keyboard="false"
class="upload-modal"
:okButtonProps="getOkButtonProps"
:cancelButtonProps="{ disabled: isUploadingRef }"
>
<template #centerFooter>
<a-button
@click="handleStartUpload"
color="success"
:disabled="!getIsSelectFile"
:loading="isUploadingRef"
>
{{ 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"
:show-upload-list="false"
class="upload-modal-toolbar__btn"
>
<a-button type="primary"> 选择文件 </a-button>
</Upload>
</div>
<FileList :dataSource="fileListRef" :columns="columns" :actionColumn="actionColumn" />
</BasicModal>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, toRefs, unref, computed, PropType, watch } from 'vue';
import { Upload, Alert } 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 './typing';
import { basicProps } from './props';
import { createTableColumns, createActionColumn } from './data';
// utils
import { checkImgType, getBase64WithFile } from './helper';
import { buildSnowFlakeId, buildUUID } from '/@/utils/uuid';
import { isFunction } from '/@/utils/is';
import { warn } from '/@/utils/log';
import FileList from './FileList.vue';
import { useI18n } from '/@/hooks/web/useI18n';
export default defineComponent({
components: { BasicModal, Upload, Alert, FileList },
props: {
...basicProps,
previewFileList: {
type: Array as PropType<string[]>,
default: () => [],
},
folderId: {
type: String as PropType<string>,
default: () => '',
},
},
emits: ['change', 'register', 'delete'],
setup(props, { emit }) {
const state = reactive<{ fileList: FileItem[] }>({
fileList: [],
});
// 是否正在上传
const isUploadingRef = ref(false);
const fileListRef = ref<FileItem[]>([]);
const { accept, helpText, maxNumber, maxSize } = toRefs(props);
const folderIdRef = ref<string>(props.folderId);
watch(
() => props.folderId,
() => {
folderIdRef.value = props.folderId;
},
);
const { t } = useI18n();
const [register, { closeModal }] = useModalInner();
const { getStringAccept, getHelpText } = useUploadType({
acceptRef: accept,
helpTextRef: helpText,
maxNumberRef: maxNumber,
maxSizeRef: maxSize,
});
const { createMessage } = useMessage();
const getIsSelectFile = computed(() => {
return (
fileListRef.value.length > 0 &&
!fileListRef.value.every((item) => item.status === UploadResultStatus.SUCCESS)
);
});
const getOkButtonProps = computed(() => {
const someSuccess = fileListRef.value.some(
(item) => item.status === UploadResultStatus.SUCCESS,
);
return {
disabled: isUploadingRef.value || fileListRef.value.length === 0 || !someSuccess,
};
});
const getUploadBtnText = computed(() => {
const someError = fileListRef.value.some(
(item) => item.status === UploadResultStatus.ERROR,
);
return isUploadingRef.value
? t('component.upload.uploading')
: someError
? t('component.upload.reUploadFailed')
: t('component.upload.startUpload');
});
// 上传前校验
function beforeUpload(file: File) {
const { size, name } = file;
const { maxSize } = props;
// 设置最大值,则判断
if (maxSize && file.size / 1024 / 1024 >= maxSize) {
createMessage.error(t('component.upload.maxSizeMultiple', [maxSize]));
return false;
}
const commonItem = {
uuid: buildUUID(),
file,
size,
name,
percent: 0,
type: name.split('.').pop(),
};
// 生成图片缩略图
if (checkImgType(file)) {
// beforeUpload如果异步会调用自带上传方法
// file.thumbUrl = await getBase64(file);
getBase64WithFile(file).then(({ result: thumbUrl }) => {
fileListRef.value = [
...unref(fileListRef),
{
thumbUrl,
...commonItem,
},
];
});
} else {
fileListRef.value = [...unref(fileListRef), commonItem];
}
return false;
}
// 删除
function handleRemove(record: FileItem) {
const index = fileListRef.value.findIndex((item) => item.uuid === record.uuid);
index !== -1 && fileListRef.value.splice(index, 1);
emit('delete', record);
}
// 预览
// function handlePreview(record: FileItem) {
// const { thumbUrl = '' } = record;
// createImgPreview({
// imageList: [thumbUrl],
// });
// }
async function uploadApiByItem(item: FileItem, folderId: string) {
const { api } = props;
if (!api || !isFunction(api)) {
return warn('upload api must exist and be a function');
}
try {
item.status = UploadResultStatus.UPLOADING;
const { data } = await props.api?.(
{
data: {
...(props.uploadParams || {}),
folderId,
},
file: item.file,
name: props.name,
filename: props.filename,
},
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() {
const { maxNumber } = props;
if ((fileListRef.value.length + props.previewFileList?.length ?? 0) > maxNumber) {
return createMessage.warning(t('component.upload.maxNumber', [maxNumber]));
}
try {
isUploadingRef.value = true;
// 只上传不是成功状态的
const uploadFileList =
fileListRef.value.filter((item) => item.status !== UploadResultStatus.SUCCESS) || [];
//判断表单是否传入folderId 如果没有 默认生成雪花id 用于后端文件存储
if (!unref(folderIdRef)) {
folderIdRef.value = buildSnowFlakeId();
}
const data = await Promise.all(
uploadFileList.map((item) => {
return uploadApiByItem(item, folderIdRef.value);
}),
);
isUploadingRef.value = false;
// 生产环境:抛出错误
const errorList = data.filter((item: any) => !item.success);
if (errorList.length > 0) throw errorList;
} catch (e) {
isUploadingRef.value = false;
throw e;
}
}
// 点击保存
function handleOk() {
const { maxNumber } = props;
if (fileListRef.value.length > maxNumber) {
return createMessage.warning(t('component.upload.maxNumber', [maxNumber]));
}
if (isUploadingRef.value) {
return createMessage.warning(t('component.upload.saveWarn'));
}
const fileList: string[] = []; //文件地址
const fileNameList: string[] = []; //文件名
for (const item of fileListRef.value) {
const { status, responseData, name } = item;
if (status === UploadResultStatus.SUCCESS && responseData) {
fileList.push(responseData.data);
fileNameList.push(name);
}
}
// 存在一个上传成功的即可保存
if (fileList.length <= 0) {
return createMessage.warning(t('component.upload.saveError'));
}
fileListRef.value = [];
closeModal();
emit('change', fileList, folderIdRef.value, fileNameList);
}
// 点击关闭:则所有操作不保存,包括上传的
async function handleCloseFunc() {
if (!isUploadingRef.value) {
fileListRef.value = [];
return true;
} else {
createMessage.warning(t('component.upload.uploadWait'));
return false;
}
}
return {
columns: createTableColumns() as any[],
actionColumn: createActionColumn(handleRemove) as any,
register,
closeModal,
getHelpText,
getStringAccept,
getOkButtonProps,
beforeUpload,
// registerTable,
fileListRef,
state,
isUploadingRef,
handleStartUpload,
handleOk,
handleCloseFunc,
getIsSelectFile,
getUploadBtnText,
t,
};
},
});
</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>

View File

@ -0,0 +1,99 @@
<template>
<BasicModal
width="800px"
:title="t('component.upload.preview')"
class="upload-preview-modal"
v-bind="$attrs"
@register="register"
:showOkBtn="false"
>
<FileList :dataSource="fileListRef" :columns="columns" :actionColumn="actionColumn" />
</BasicModal>
</template>
<script lang="ts">
import { defineComponent, watch, ref } from 'vue';
// import { BasicTable, useTable } from '/@/components/Table';
import FileList from './FileList.vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { previewProps } from './props';
import { PreviewFileItem } from './typing';
import { downloadByUrl } from '/@/utils/file/download';
import { createPreviewColumns, createPreviewActionColumn } from './data';
import { useI18n } from '/@/hooks/web/useI18n';
import { isArray } from '/@/utils/is';
export default defineComponent({
components: { BasicModal, FileList },
props: previewProps,
emits: ['list-change', 'register', 'delete'],
setup(props, { emit }) {
const [register, { closeModal }] = useModalInner();
const { t } = useI18n();
const fileListRef = ref<PreviewFileItem[]>([]);
watch(
() => props.value,
(value) => {
if (!isArray(value)) value = [];
fileListRef.value = value
.filter((item) => !!item)
.map((item, index) => {
return {
url: item,
type: item.split('.').pop() || '',
name: props.fileNames[index],
};
});
},
{ immediate: true },
);
// 删除
function handleRemove(record: PreviewFileItem) {
const index = fileListRef.value.findIndex((item) => item.url === record.url);
if (index !== -1) {
const removed = fileListRef.value.splice(index, 1);
emit('delete', removed[0].url);
emit(
'list-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 });
}
return {
t,
register,
closeModal,
fileListRef,
columns: createPreviewColumns() as any[],
actionColumn: createPreviewActionColumn({ handleRemove, handleDownload }) as any,
};
},
});
</script>
<style lang="less">
.upload-preview-modal {
.ant-upload-list {
display: none;
}
.ant-table-wrapper .ant-spin-nested-loading {
padding: 0;
}
}
</style>

View File

@ -0,0 +1,153 @@
import type { BasicColumn, ActionItem } from '/@/components/Table';
import { FileItem, PreviewFileItem, UploadResultStatus } from './typing';
import {
// checkImgType,
isImgTypeByName,
} from './helper';
import { Progress, Tag } from 'ant-design-vue';
import TableAction from '/@/components/Table/src/components/TableAction.vue';
import ThumbUrl from './ThumbUrl.vue';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
// 文件上传列表
export function createTableColumns(): BasicColumn[] {
return [
{
dataIndex: 'thumbUrl',
title: t('略缩图'),
width: 100,
customRender: ({ record }) => {
const { thumbUrl } = (record as FileItem) || {};
return thumbUrl && <ThumbUrl fileUrl={thumbUrl} />;
},
},
{
dataIndex: 'name',
title: t('文件名'),
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="truncate mb-1" title={text}>
{text}
</p>
<Progress percent={percent} size="small" status={status} />
</span>
);
},
},
{
dataIndex: 'size',
title: t('文件大小'),
width: 100,
customRender: ({ text = 0 }) => {
return text && (text / 1024).toFixed(2) + 'KB';
},
},
// {
// dataIndex: 'type',
// title: '文件类型',
// width: 100,
// },
{
dataIndex: 'status',
title: t('状态'),
width: 100,
customRender: ({ text }) => {
if (text === UploadResultStatus.SUCCESS) {
return <Tag color="green">{() => t('上传成功')}</Tag>;
} else if (text === UploadResultStatus.ERROR) {
return <Tag color="red">{() => t('上传失败')}</Tag>;
} else if (text === UploadResultStatus.UPLOADING) {
return <Tag color="blue">{() => t('上传中')}</Tag>;
}
return text;
},
},
];
}
export function createActionColumn(handleRemove: Function): BasicColumn {
return {
width: 120,
title: t('操作'),
dataIndex: 'action',
fixed: false,
customRender: ({ record }) => {
const actions: ActionItem[] = [
{
label: t('删除'),
color: 'error',
onClick: handleRemove.bind(null, record),
},
];
// if (checkImgType(record)) {
// actions.unshift({
// label: t('component.upload.preview'),
// onClick: handlePreview.bind(null, record),
// });
// }
return <TableAction actions={actions} outside={true} />;
},
};
}
// 文件预览列表
export function createPreviewColumns(): BasicColumn[] {
return [
{
dataIndex: 'url',
title: t('略缩图'),
width: 100,
customRender: ({ record }) => {
const { url } = (record as PreviewFileItem) || {};
return isImgTypeByName(url) && <ThumbUrl fileUrl={url} />;
},
},
{
dataIndex: 'name',
title: t('文件名'),
align: 'left',
},
];
}
export function createPreviewActionColumn({
handleRemove,
handleDownload,
}: {
handleRemove: Fn;
handleDownload: Fn;
}): BasicColumn {
return {
width: 160,
title: t('操作'),
dataIndex: 'action',
fixed: false,
customRender: ({ record }) => {
const actions: ActionItem[] = [
{
label: t('删除'),
color: 'error',
onClick: handleRemove.bind(null, record),
},
{
label: t('下载'),
onClick: handleDownload.bind(null, record),
},
];
return <TableAction actions={actions} outside={true} />;
},
};
}

View File

@ -0,0 +1,31 @@
import { testRegLength } from '/@/utils/event/design';
export function checkFileType(file: File, accepts: string[]) {
const newTypes = accepts.join('|');
testRegLength(newTypes);
// 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 /\.(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);
});
}

View File

@ -0,0 +1,87 @@
import type { PropType } from 'vue';
import { FileBasicColumn } from './typing';
export const basicProps = {
helpText: {
type: String as PropType<string>,
default: '',
},
// 文件最大多少MB
maxSize: {
type: Number as PropType<number>,
default: 2,
},
// 最大数量的文件Infinity不限制
maxNumber: {
type: Number as PropType<number>,
default: Infinity,
},
// 根据后缀,或者其他
accept: {
type: [Array as PropType<string[]>, String as PropType<string>],
default: () => [] || '',
},
multiple: {
type: Boolean as PropType<boolean>,
default: true,
},
uploadParams: {
type: Object as PropType<any>,
default: {},
},
api: {
type: Function as PropType<PromiseFn>,
default: null,
required: true,
},
name: {
type: String as PropType<string>,
default: 'file',
},
filename: {
type: String as PropType<string>,
default: null,
},
};
export const uploadContainerProps = {
value: {
type: String as PropType<string>,
default: () => '',
},
...basicProps,
showPreviewNumber: {
type: Boolean as PropType<boolean>,
default: true,
},
emptyHidePreview: {
type: Boolean as PropType<boolean>,
default: false,
},
};
export const previewProps = {
value: {
type: Array as PropType<string[]>,
default: () => [],
},
fileNames: {
type: Array as PropType<string[]>,
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,
},
};

View File

@ -0,0 +1,55 @@
import { UploadApiResult } from '/@/api/sys/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;
}
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';
}

View File

@ -0,0 +1,62 @@
import { Ref, unref, computed } from 'vue';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
export function useUploadType({
acceptRef,
helpTextRef,
maxNumberRef,
maxSizeRef,
}: {
acceptRef: Ref<string[] | string>;
helpTextRef: Ref<string>;
maxNumberRef: Ref<number>;
maxSizeRef: Ref<number>;
}) {
// 文件类型限制
const getAccept = computed(() => {
const accept = unref(acceptRef);
if (accept && accept.length > 0) {
return Array.isArray(accept) ? accept : accept.split(',');
}
return [];
});
const getStringAccept = computed(() => {
return unref(getAccept)
.map((item) => {
if (item.indexOf('/') > 0 || item.startsWith('.')) {
return item;
} else {
return `.${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(
t('component.upload.accept', Array.isArray(accept) ? [accept.join(',')] : [accept]),
);
}
const maxSize = unref(maxSizeRef);
if (maxSize) {
helpTexts.push(t('component.upload.maxSize', [maxSize]));
}
const maxNumber = unref(maxNumberRef);
if (maxNumber && maxNumber !== Infinity) {
helpTexts.push(t('component.upload.maxNumber', [maxNumber]));
}
return helpTexts.join('');
});
return { getAccept, getStringAccept, getHelpText };
}