feat: optimize file collection (#1666)
* feat(Upload.DraggerV2): add loading * feat: auto close on uploading over * feat: auto selected when upload is complete * feat: filter selected options * feat: add quick-upload and select-file * fix: should not appear on readPretty mode * fix: radio * fix: compatible with older version * feat: improving the interaction of radio selections * fix: show two butons by default
This commit is contained in:
parent
f35da4a2a0
commit
2050eb5317
@ -45,12 +45,14 @@ export const linkTo: IField = {
|
||||
},
|
||||
},
|
||||
},
|
||||
availableTypes:['belongsToMany'],
|
||||
availableTypes: ['belongsToMany'],
|
||||
schemaInitialize(schema: ISchema, { readPretty, block, targetCollection }) {
|
||||
if (targetCollection?.template === 'file') {
|
||||
const fieldNames = schema['x-component-props']['fieldNames'] || { label: 'preview', value: 'id' };
|
||||
fieldNames.label = 'preview';
|
||||
schema['x-component-props']['fieldNames'] = fieldNames;
|
||||
schema['x-component-props'].quickUpload = true;
|
||||
schema['x-component-props'].selectFile = true;
|
||||
}
|
||||
|
||||
if (block === 'Form') {
|
||||
|
@ -57,6 +57,8 @@ export const m2m: IField = {
|
||||
const fieldNames = schema['x-component-props']['fieldNames'] || { label: 'preview', value: 'id' };
|
||||
fieldNames.label = 'preview';
|
||||
schema['x-component-props']['fieldNames'] = fieldNames;
|
||||
schema['x-component-props'].quickUpload = true;
|
||||
schema['x-component-props'].selectFile = true;
|
||||
}
|
||||
|
||||
if (block === 'Form') {
|
||||
|
@ -56,6 +56,8 @@ export const m2o: IField = {
|
||||
const fieldNames = schema['x-component-props']['fieldNames'] || { label: 'preview', value: 'id' };
|
||||
fieldNames.label = 'preview';
|
||||
schema['x-component-props']['fieldNames'] = fieldNames;
|
||||
schema['x-component-props'].quickUpload = true;
|
||||
schema['x-component-props'].selectFile = true;
|
||||
}
|
||||
|
||||
if (block === 'Form') {
|
||||
|
@ -51,13 +51,7 @@ export const o2m: IField = {
|
||||
},
|
||||
},
|
||||
availableTypes: ['hasMany'],
|
||||
schemaInitialize(schema: ISchema, { field, block, readPretty, targetCollection }) {
|
||||
if (targetCollection?.template === 'file') {
|
||||
const fieldNames = schema['x-component-props']['fieldNames'] || { label: 'preview', value: 'id' };
|
||||
fieldNames.label = 'preview';
|
||||
schema['x-component-props']['fieldNames'] = fieldNames;
|
||||
}
|
||||
|
||||
schemaInitialize(schema: ISchema, { field, block, readPretty }) {
|
||||
if (block === 'Form') {
|
||||
if (schema['x-component'] === 'TableField') {
|
||||
const association = `${field.collectionName}.${field.name}`;
|
||||
|
@ -118,7 +118,7 @@ export const o2o: IField = {
|
||||
},
|
||||
},
|
||||
availableTypes: ['hasOne'],
|
||||
schemaInitialize(schema: ISchema, { field, block, readPretty, action, targetCollection }) {
|
||||
schemaInitialize(schema: ISchema, { field, block, readPretty, action }) {
|
||||
internalSchameInitialize(schema, { field, block, readPretty, action });
|
||||
if (['Table', 'Kanban'].includes(block)) {
|
||||
schema['x-component-props'] = schema['x-component-props'] || {};
|
||||
@ -127,12 +127,6 @@ export const o2o: IField = {
|
||||
// 预览文件时需要的参数
|
||||
schema['x-component-props']['size'] = 'small';
|
||||
}
|
||||
|
||||
if (targetCollection?.template === 'file') {
|
||||
const fieldNames = schema['x-component-props']['fieldNames'] || { label: 'preview', value: 'id' };
|
||||
fieldNames.label = 'preview';
|
||||
schema['x-component-props']['fieldNames'] = fieldNames;
|
||||
}
|
||||
},
|
||||
properties: {
|
||||
'uiSchema.title': {
|
||||
@ -302,7 +296,7 @@ export const oho: IField = {
|
||||
},
|
||||
},
|
||||
},
|
||||
schemaInitialize(schema: ISchema, { field, block, readPretty, action, targetCollection }) {
|
||||
schemaInitialize(schema: ISchema, { field, block, readPretty, action }) {
|
||||
internalSchameInitialize(schema, { field, block, readPretty, action });
|
||||
if (['Table', 'Kanban'].includes(block)) {
|
||||
schema['x-component-props'] = schema['x-component-props'] || {};
|
||||
@ -311,12 +305,6 @@ export const oho: IField = {
|
||||
// 预览文件时需要的参数
|
||||
schema['x-component-props']['size'] = 'small';
|
||||
}
|
||||
|
||||
if (targetCollection?.template === 'file') {
|
||||
const fieldNames = schema['x-component-props']['fieldNames'] || { label: 'preview', value: 'id' };
|
||||
fieldNames.label = 'preview';
|
||||
schema['x-component-props']['fieldNames'] = fieldNames;
|
||||
}
|
||||
},
|
||||
properties: {
|
||||
'uiSchema.title': {
|
||||
@ -491,6 +479,8 @@ export const obo: IField = {
|
||||
const fieldNames = schema['x-component-props']['fieldNames'] || { label: 'preview', value: 'id' };
|
||||
fieldNames.label = 'preview';
|
||||
schema['x-component-props']['fieldNames'] = fieldNames;
|
||||
schema['x-component-props'].quickUpload = true;
|
||||
schema['x-component-props'].selectFile = true;
|
||||
}
|
||||
},
|
||||
properties: {
|
||||
|
@ -8,7 +8,7 @@ import React, { useContext, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ACLCollectionFieldProvider } from '../../../acl/ACLProvider';
|
||||
import { BlockRequestContext, useFilterByTk, useFormBlockContext } from '../../../block-provider';
|
||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
|
||||
import { Collection, useCollection, useCollectionManager } from '../../../collection-manager';
|
||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
|
||||
import { useCompile, useDesignable, useFieldComponentOptions } from '../../hooks';
|
||||
import { BlockItem } from '../block-item';
|
||||
@ -81,6 +81,7 @@ FormItem.Designer = () => {
|
||||
const { dn, refresh, insertAdjacent } = useDesignable();
|
||||
const compile = useCompile();
|
||||
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
||||
const targetCollection = getCollection(collectionField.target);
|
||||
const interfaceConfig = getInterface(collectionField?.interface);
|
||||
const validateSchema = interfaceConfig?.['validateSchema']?.(fieldSchema);
|
||||
const originalTitle = collectionField?.uiSchema?.title;
|
||||
@ -229,6 +230,46 @@ FormItem.Designer = () => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!form?.readPretty && isFileCollection(targetCollection) ? (
|
||||
<SchemaSettings.SwitchItem
|
||||
key="quick-upload"
|
||||
title={t('Quick upload')}
|
||||
checked={fieldSchema['x-component-props']?.quickUpload as boolean}
|
||||
onChange={(value) => {
|
||||
const schema = {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
};
|
||||
field.componentProps.quickUpload = value;
|
||||
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
||||
fieldSchema['x-component-props'].quickUpload = value;
|
||||
schema['x-component-props'] = fieldSchema['x-component-props'];
|
||||
dn.emit('patch', {
|
||||
schema,
|
||||
});
|
||||
refresh();
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{!form?.readPretty && isFileCollection(targetCollection) ? (
|
||||
<SchemaSettings.SwitchItem
|
||||
key="select-file"
|
||||
title={t('Select file')}
|
||||
checked={fieldSchema['x-component-props']?.selectFile as boolean}
|
||||
onChange={(value) => {
|
||||
const schema = {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
};
|
||||
field.componentProps.selectFile = value;
|
||||
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
||||
fieldSchema['x-component-props'].selectFile = value;
|
||||
schema['x-component-props'] = fieldSchema['x-component-props'];
|
||||
dn.emit('patch', {
|
||||
schema,
|
||||
});
|
||||
refresh();
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{form && !form?.readPretty && validateSchema && (
|
||||
<SchemaSettings.ModalItem
|
||||
title={t('Set validation rules')}
|
||||
@ -415,7 +456,7 @@ FormItem.Designer = () => {
|
||||
block: 'Form',
|
||||
readPretty: field.readPretty,
|
||||
action: tk ? 'get' : null,
|
||||
targetCollection: getCollection(collectionField.target),
|
||||
targetCollection,
|
||||
});
|
||||
|
||||
insertAdjacent('beforeBegin', divWrap(schema), {
|
||||
@ -547,4 +588,8 @@ FormItem.Designer = () => {
|
||||
);
|
||||
};
|
||||
|
||||
function isFileCollection(collection: Collection) {
|
||||
return collection?.template === 'file';
|
||||
}
|
||||
|
||||
FormItem.FilterFormDesigner = FilterFormDesigner;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { DeleteOutlined, DownloadOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import { connect, mapReadPretty } from '@formily/react';
|
||||
import { Upload as AntdUpload, Button, Space } from 'antd';
|
||||
import { Upload as AntdUpload, Button, Progress, Space } from 'antd';
|
||||
import cls from 'classnames';
|
||||
import { saveAs } from 'file-saver';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
@ -8,25 +8,34 @@ import { useTranslation } from 'react-i18next';
|
||||
import Lightbox from 'react-image-lightbox';
|
||||
import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
|
||||
import { ReadPretty } from '../upload/ReadPretty';
|
||||
import { isImage, toArr, toFileList } from '../upload/shared';
|
||||
import { isImage, toFileList, useUploadProps } from '../upload/shared';
|
||||
import '../upload/style.less';
|
||||
import { UploadProps } from '../upload/type';
|
||||
|
||||
interface TPreview {
|
||||
(props: any): JSX.Element;
|
||||
Selector: React.FC<any>;
|
||||
}
|
||||
type Props = UploadProps & {
|
||||
/** 是否显示 Upload 按钮 */
|
||||
quickUpload: boolean;
|
||||
/** 是否显示 Select 按钮 */
|
||||
selectFile: boolean;
|
||||
onRemove?: (file) => void;
|
||||
onSelect?: () => void;
|
||||
};
|
||||
|
||||
export const Preview = connect((props) => {
|
||||
return <ReadPretty.File {...props} />;
|
||||
}, mapReadPretty(ReadPretty.File));
|
||||
|
||||
export const FileSelector = (props: any) => {
|
||||
const { disabled, multiple, value, onRemove, onSelect } = props;
|
||||
export const FileSelector = (props: Props) => {
|
||||
const { disabled, multiple, value, quickUpload, selectFile, onRemove, onSelect } = props;
|
||||
const uploadProps = useUploadProps({ ...props });
|
||||
const [fileList, setFileList] = useState([]);
|
||||
const [photoIndex, setPhotoIndex] = useState(0);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
// 兼容旧版本
|
||||
const showSelectButton = selectFile === undefined && quickUpload === undefined;
|
||||
|
||||
useEffect(() => {
|
||||
setFileList(toFileList(value));
|
||||
}, [value]);
|
||||
@ -42,11 +51,13 @@ export const FileSelector = (props: any) => {
|
||||
onSelect?.();
|
||||
};
|
||||
|
||||
const list = fileList.length ? (multiple ? fileList : [fileList[fileList.length - 1]]) : [];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={cls('ant-upload-picture-card-wrapper nb-upload')}>
|
||||
<div className={'ant-upload-list ant-upload-list-picture-card'}>
|
||||
{fileList.map((file) => {
|
||||
{list.map((file) => {
|
||||
const handleClick = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@ -59,7 +70,7 @@ export const FileSelector = (props: any) => {
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className={'ant-upload-list-picture-card-container'}>
|
||||
<div key={file.id} className={'ant-upload-list-picture-card-container'}>
|
||||
<div className="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture-card">
|
||||
<div className={'ant-upload-list-item-info'}>
|
||||
<span className="ant-upload-span">
|
||||
@ -108,21 +119,25 @@ export const FileSelector = (props: any) => {
|
||||
)}
|
||||
</Space>
|
||||
</span>
|
||||
{file.status === 'uploading' && (
|
||||
<div className={'ant-upload-list-item-progress'}>
|
||||
<Progress strokeWidth={2} type={'line'} showInfo={false} percent={file.percent} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{!disabled && (multiple || toArr(value).length < 1) && (
|
||||
<>
|
||||
{showSelectButton ? (
|
||||
<div className={'ant-upload-list-picture-card-container'}>
|
||||
<AntdUpload
|
||||
disabled={disabled}
|
||||
multiple={multiple}
|
||||
listType={'picture-card'}
|
||||
fileList={fileList}
|
||||
showUploadList={false}
|
||||
onRemove={handleRemove}
|
||||
>
|
||||
{!disabled && (multiple || toArr(value).length < 1) && (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
@ -137,10 +152,68 @@ export const FileSelector = (props: any) => {
|
||||
<PlusOutlined />
|
||||
{t('Select')}
|
||||
</div>
|
||||
)}
|
||||
</AntdUpload>
|
||||
</div>
|
||||
)}
|
||||
) : null}
|
||||
{quickUpload ? (
|
||||
<div className={'ant-upload-list-picture-card-container'}>
|
||||
<AntdUpload
|
||||
{...uploadProps}
|
||||
disabled={disabled}
|
||||
multiple={multiple}
|
||||
listType={'picture-card'}
|
||||
fileList={fileList}
|
||||
showUploadList={false}
|
||||
onRemove={handleRemove}
|
||||
onChange={(info) => {
|
||||
// 如果不在这里 setFileList 的话,会导致 onChange 只会执行一次
|
||||
setFileList([...info.fileList]);
|
||||
uploadProps.onChange?.(info);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<PlusOutlined />
|
||||
{t('Upload')}
|
||||
</div>
|
||||
</AntdUpload>
|
||||
</div>
|
||||
) : null}
|
||||
{selectFile ? (
|
||||
<div className={'ant-upload-list-picture-card-container'}>
|
||||
<AntdUpload
|
||||
disabled={disabled}
|
||||
multiple={multiple}
|
||||
listType={'picture-card'}
|
||||
showUploadList={false}
|
||||
onRemove={handleRemove}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
onClick={handleSelect}
|
||||
>
|
||||
<PlusOutlined />
|
||||
{t('Select')}
|
||||
</div>
|
||||
</AntdUpload>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
{/* 预览图片的弹框 */}
|
||||
|
@ -12,7 +12,8 @@ import { FileSelector } from '../preview';
|
||||
import { useFieldNames } from './useFieldNames';
|
||||
import { getLabelFormatValue, useLabelUiSchema } from './util';
|
||||
|
||||
const RecordPickerContext = createContext(null);
|
||||
export const RecordPickerContext = createContext(null);
|
||||
|
||||
function flatData(data) {
|
||||
let newArr = [];
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
@ -27,28 +28,40 @@ function flatData(data) {
|
||||
|
||||
const useTableSelectorProps = () => {
|
||||
const field = useField<ArrayField>();
|
||||
const { multiple, value, setSelectedRows, selectedRows: rcSelectRows } = useContext(RecordPickerContext);
|
||||
const { onRowSelectionChange, rowKey, ...others } = useTsp();
|
||||
const {
|
||||
multiple,
|
||||
options = [],
|
||||
setSelectedRows,
|
||||
selectedRows: rcSelectRows = [],
|
||||
onChange,
|
||||
} = useContext(RecordPickerContext);
|
||||
const { onRowSelectionChange, rowKey = 'id', ...others } = useTsp();
|
||||
const { setVisible } = useActionContext();
|
||||
|
||||
return {
|
||||
...others,
|
||||
rowKey,
|
||||
rowSelection: {
|
||||
type: multiple ? 'checkbox' : 'radio',
|
||||
// defaultSelectedRowKeys: rcSelectRows?.map((item) => item[rowKey||'id']),
|
||||
selectedRowKeys: rcSelectRows?.map((item) => item[rowKey || 'id']),
|
||||
selectedRowKeys: rcSelectRows
|
||||
.filter((item) => options.every((row) => row[rowKey] !== item[rowKey]))
|
||||
.map((item) => item[rowKey]),
|
||||
},
|
||||
dataSource: field.value?.filter((item) => options.every((row) => row[rowKey] !== item[rowKey])),
|
||||
onRowSelectionChange(selectedRowKeys, selectedRows) {
|
||||
if (multiple) {
|
||||
const scopeRows = flatData(field.value) || [];
|
||||
const allSelectedRows = rcSelectRows || [];
|
||||
const otherRows = differenceBy(allSelectedRows, scopeRows, rowKey || 'id');
|
||||
const unionSelectedRows = unionBy(otherRows, selectedRows, rowKey || 'id');
|
||||
const unionSelectedRowKeys = unionSelectedRows.map((item) => item[rowKey || 'id']);
|
||||
const otherRows = differenceBy(allSelectedRows, scopeRows, rowKey);
|
||||
const unionSelectedRows = unionBy(otherRows, selectedRows, rowKey);
|
||||
const unionSelectedRowKeys = unionSelectedRows.map((item) => item[rowKey]);
|
||||
setSelectedRows?.(unionSelectedRows);
|
||||
onRowSelectionChange?.(unionSelectedRowKeys, unionSelectedRows);
|
||||
} else {
|
||||
setSelectedRows?.(selectedRows);
|
||||
onRowSelectionChange?.(selectedRowKeys, selectedRows);
|
||||
onChange(selectedRows?.[0] || null);
|
||||
setVisible(false);
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -56,11 +69,11 @@ const useTableSelectorProps = () => {
|
||||
|
||||
const usePickActionProps = () => {
|
||||
const { setVisible } = useActionContext();
|
||||
const { multiple, selectedRows, onChange } = useContext(RecordPickerContext);
|
||||
const { multiple, selectedRows, onChange, options } = useContext(RecordPickerContext);
|
||||
return {
|
||||
onClick() {
|
||||
if (multiple) {
|
||||
onChange(selectedRows);
|
||||
onChange([...options, ...selectedRows]);
|
||||
} else {
|
||||
onChange(selectedRows?.[0] || null);
|
||||
}
|
||||
@ -79,8 +92,20 @@ const useAssociation = (props) => {
|
||||
return getField(fieldSchema.name);
|
||||
};
|
||||
|
||||
export const InputRecordPicker: React.FC<any> = (props) => {
|
||||
const { value, multiple, onChange, ...others } = props;
|
||||
interface IRecordPickerProps {
|
||||
multiple?: boolean;
|
||||
association?: string;
|
||||
value?: any;
|
||||
/** 是否显示 Upload 按钮(仅适用于文件场景下) */
|
||||
quickUpload?: boolean;
|
||||
/** 是否显示 Select 按钮(仅适用于文件场景下) */
|
||||
selectFile?: boolean;
|
||||
onChange?: (value: any) => void;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export const InputRecordPicker: React.FC<any> = (props: IRecordPickerProps) => {
|
||||
const { value, multiple, onChange, quickUpload, selectFile, ...others } = props;
|
||||
const fieldNames = useFieldNames(props);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const fieldSchema = useFieldSchema();
|
||||
@ -101,7 +126,6 @@ export const InputRecordPicker: React.FC<any> = (props) => {
|
||||
};
|
||||
});
|
||||
setOptions(opts);
|
||||
setSelectedRows(opts);
|
||||
}
|
||||
}, [value, fieldNames?.label]);
|
||||
|
||||
@ -113,13 +137,12 @@ export const InputRecordPicker: React.FC<any> = (props) => {
|
||||
|
||||
const handleSelect = () => {
|
||||
setVisible(true);
|
||||
setSelectedRows(options);
|
||||
setSelectedRows([]);
|
||||
};
|
||||
|
||||
const handleRemove = (file) => {
|
||||
const newOptions = options.filter((option) => option.id !== file.id);
|
||||
setOptions(newOptions);
|
||||
setSelectedRows(newOptions);
|
||||
if (newOptions.length === 0) {
|
||||
return onChange(null);
|
||||
}
|
||||
@ -129,7 +152,25 @@ export const InputRecordPicker: React.FC<any> = (props) => {
|
||||
return (
|
||||
<div>
|
||||
{showFilePicker ? (
|
||||
<FileSelector value={options} multiple onSelect={handleSelect} onRemove={handleRemove} />
|
||||
<FileSelector
|
||||
value={options}
|
||||
multiple={multiple}
|
||||
quickUpload={quickUpload}
|
||||
selectFile={selectFile}
|
||||
action={`${collectionField.target}:create`}
|
||||
onSelect={handleSelect}
|
||||
onRemove={handleRemove}
|
||||
onChange={(changed) => {
|
||||
if (changed.every((file) => file.status !== 'uploading')) {
|
||||
changed = changed.filter((file) => file.status === 'done').map((file) => file.response.data);
|
||||
if (multiple) {
|
||||
onChange([...options, ...changed]);
|
||||
} else {
|
||||
onChange(changed[0]);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Select
|
||||
{...others}
|
||||
@ -157,7 +198,17 @@ export const InputRecordPicker: React.FC<any> = (props) => {
|
||||
open={false}
|
||||
/>
|
||||
)}
|
||||
{Drawer({ multiple, onChange, selectedRows, setSelectedRows, collectionField, visible, setVisible, fieldSchema })}
|
||||
{Drawer({
|
||||
multiple,
|
||||
onChange,
|
||||
selectedRows,
|
||||
setSelectedRows,
|
||||
collectionField,
|
||||
visible,
|
||||
setVisible,
|
||||
fieldSchema,
|
||||
options,
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -171,9 +222,20 @@ const Drawer: React.FunctionComponent<{
|
||||
visible: boolean;
|
||||
setVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
fieldSchema;
|
||||
}> = ({ multiple, onChange, selectedRows, setSelectedRows, collectionField, visible, setVisible, fieldSchema }) => {
|
||||
options: any[];
|
||||
}> = ({
|
||||
multiple,
|
||||
onChange,
|
||||
selectedRows,
|
||||
setSelectedRows,
|
||||
collectionField,
|
||||
visible,
|
||||
setVisible,
|
||||
fieldSchema,
|
||||
options,
|
||||
}) => {
|
||||
return (
|
||||
<RecordPickerContext.Provider value={{ multiple, onChange, selectedRows, setSelectedRows }}>
|
||||
<RecordPickerContext.Provider value={{ multiple, onChange, selectedRows, setSelectedRows, options }}>
|
||||
<CollectionProvider allowNull name={collectionField?.target}>
|
||||
<ActionContext.Provider value={{ openMode: 'drawer', visible, setVisible }}>
|
||||
<FormProvider>
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
RecordProvider,
|
||||
useSchemaInitializer,
|
||||
useTableBlockContext,
|
||||
useTableSelectorContext
|
||||
useTableSelectorContext,
|
||||
} from '../../../';
|
||||
import { useACLFieldWhitelist } from '../../../acl/ACLProvider';
|
||||
import { extractIndex, getIdsWithChildren, isCollectionFieldComponent, isColumnComponent } from './utils';
|
||||
@ -473,6 +473,7 @@ export const Table: any = observer((props: any) => {
|
||||
}
|
||||
}}
|
||||
rowKey={rowKey ?? defaultRowKey}
|
||||
dataSource={field?.value?.slice?.()}
|
||||
{...others}
|
||||
{...restProps}
|
||||
pagination={paginationProps}
|
||||
@ -492,7 +493,6 @@ export const Table: any = observer((props: any) => {
|
||||
},
|
||||
expandedRowKeys: expandedKeys,
|
||||
}}
|
||||
dataSource={field?.value?.slice?.()}
|
||||
/>
|
||||
</SortableWrapper>
|
||||
{field.errors.length > 0 && (
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { DeleteOutlined, DownloadOutlined, InboxOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import { DeleteOutlined, DownloadOutlined, InboxOutlined, LoadingOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import { usePrefixCls } from '@formily/antd/lib/__builtins__';
|
||||
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
||||
import { Upload as AntdUpload, Button, Progress, Space } from 'antd';
|
||||
@ -24,7 +24,7 @@ export const Upload: ComposedUpload = connect(
|
||||
);
|
||||
|
||||
Upload.Attachment = connect((props: UploadProps) => {
|
||||
const { disabled, multiple, value, onChange, onRemove } = props;
|
||||
const { disabled, multiple, value, onChange } = props;
|
||||
const [fileList, setFileList] = useState([]);
|
||||
const [sync, setSync] = useState(true);
|
||||
const images = fileList;
|
||||
@ -216,12 +216,24 @@ Upload.DraggerV2 = connect(
|
||||
const defaultSubTitle = t('Support for a single or bulk upload, file size should not exceed') + ` 10MB`;
|
||||
const { title = defaultTitle, subTitle = defaultSubTitle, useProps } = props;
|
||||
const extraProps: Record<string, any> = useProps?.() || {};
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleChange = (fileList = []) => {
|
||||
const { onChange } = extraProps;
|
||||
onChange?.(fileList);
|
||||
|
||||
if (fileList.some((file) => file.status === 'uploading')) {
|
||||
setLoading(true);
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={usePrefixCls('upload-dragger')}>
|
||||
<AntdUpload.Dragger {...useUploadProps({ ...props, ...extraProps })}>
|
||||
<AntdUpload.Dragger {...useUploadProps({ ...props, ...extraProps, onChange: handleChange })}>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<InboxOutlined />
|
||||
{loading ? <LoadingOutlined style={{ fontSize: 36 }} spin /> : <InboxOutlined />}
|
||||
</p>
|
||||
<p className="ant-upload-text">{title}</p>
|
||||
<p className="ant-upload-hint">{subTitle}</p>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Field } from '@formily/core';
|
||||
import { useField, useForm } from '@formily/react';
|
||||
import { useField } from '@formily/react';
|
||||
import { reaction } from '@formily/reactive';
|
||||
import { isArr, isValid, toArr as toArray } from '@formily/shared';
|
||||
import { UploadChangeParam } from 'antd/lib/upload';
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useBlockRequestContext } from '@nocobase/client';
|
||||
import { RecordPickerContext, useActionContext, useBlockRequestContext } from '@nocobase/client';
|
||||
import { notification } from 'antd';
|
||||
import { useContext } from 'react';
|
||||
import { useFmTranslation } from '../locale';
|
||||
|
||||
// 限制上传文件大小为 10M
|
||||
@ -8,6 +9,8 @@ export const FILE_LIMIT_SIZE = 10 * 1024 * 1024;
|
||||
export const useUploadFiles = () => {
|
||||
const { service } = useBlockRequestContext();
|
||||
const { t } = useFmTranslation();
|
||||
const { setVisible } = useActionContext();
|
||||
const { setSelectedRows } = useContext(RecordPickerContext) || {};
|
||||
const uploadingFiles = {};
|
||||
|
||||
let pendingNumber = 0;
|
||||
@ -32,13 +35,21 @@ export const useUploadFiles = () => {
|
||||
pendingNumber++;
|
||||
uploadingFiles[file.uid] = true;
|
||||
}
|
||||
if (file.status === 'done' && uploadingFiles[file.uid]) {
|
||||
if (file.status !== 'uploading' && uploadingFiles[file.uid]) {
|
||||
delete uploadingFiles[file.uid];
|
||||
if (--pendingNumber === 0) {
|
||||
service?.refresh?.();
|
||||
setSelectedRows?.((preRows) => [
|
||||
...preRows,
|
||||
...fileList.filter((file) => file.status === 'done').map((file) => file.response.data),
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (fileList.every((file) => file.status === 'done')) {
|
||||
setVisible(false);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -12,7 +12,7 @@ export const UploadActionInitializer = (props) => {
|
||||
'x-component': 'Action',
|
||||
'x-decorator': 'ACLActionProvider',
|
||||
'x-component-props': {
|
||||
openMode: 'modal',
|
||||
openMode: 'drawer',
|
||||
type: 'primary',
|
||||
icon: 'UploadOutlined',
|
||||
},
|
||||
@ -27,10 +27,11 @@ export const UploadActionInitializer = (props) => {
|
||||
title: '{{ t("Upload files") }}',
|
||||
'x-component': 'Upload.DraggerV2',
|
||||
'x-component-props': {
|
||||
height: '60vh',
|
||||
height: '50vh',
|
||||
action: `${collection.name}:create`,
|
||||
useProps: '{{useUploadFiles}}',
|
||||
multiple: true,
|
||||
listType: 'picture',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user