nocobase/packages/core/client/src/block-provider/TableBlockProvider.tsx
被雨水过滤的空气-Rairn 53d0c2dd23
feat: support file collection (#1636)
* feat: support to add File collection

* feat: support to upload files

* refactor: rename 'ReadPretty.Attachment' to 'ReadPretty.File'

* feat: support to associate the File collection

* refactor: add Preview and replace Upload.Selector

* fix(Preview): fix some problems in ReadPretty mode

* feat: use 'preview' as a default title field

* feat: support only local storage now

* fix: should not show 'Add new' button

* chore: add default value for file storage

* fix: fix preview field of file collection cannot be displayed normally

* fix: only Table and Details can display File collection

* chore: translate

* refactor: migration to plugin from core

* refactor: change 'preview' to 'url'

* fix: only 'belongsTo' and 'belongsToMany' can linked file collection

* fix: fix storage and add a field called storage in file collection

* feat: add 'deletable' to configure the visibility of the delete button

* fix: fix can't upload attachment problem

* fix: remove more option

* fix: can't use preview to filter

* fix: remove Import action option

* refactor: remove useless code

* chore: optimize condition

* chore: remove comment

* test: windows compatible

* refactor: optimize upload

* fix: upload action

* fix: createAction

* fix: uploads

* fix: file collection cannot be inherited by other collections

* fix: url should be editable

* fix: url is filterable

* fix: use input interface for url field

* fix: fix error

* fix: remove subform

* Revert "chore: translate"

This reverts commit 53cd346dab8cbee0c52a9da3cf83a99dff2def34.

* refactor: move translation to plugin

* fix: title is editable

* fix: collection?.template === 'file'

* fix: fix order of URL

* fix(collection-manager): allow collectionCategories:list

* chore: add translation

* fix(upload): should enable to use drawer

* refactor: move code to plugin

---------

Co-authored-by: chenos <chenlinxh@gmail.com>
2023-04-06 12:43:40 +08:00

249 lines
8.4 KiB
TypeScript

import { ArrayField, createForm } from '@formily/core';
import { FormContext, Schema, useField, useFieldSchema } from '@formily/react';
import uniq from 'lodash/uniq';
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { useCollectionManager } from '../collection-manager';
import { useFilterBlock } from '../filter-provider/FilterProvider';
import { FixedBlockWrapper, SchemaComponentOptions, removeNullCondition } from '../schema-component';
import { BlockProvider, RenderChildrenWithAssociationFilter, useBlockRequestContext } from './BlockProvider';
import { mergeFilter } from './SharedFilterProvider';
import { findFilterTargets } from './hooks';
export const TableBlockContext = createContext<any>({});
interface Props {
params?: any;
showIndex?: boolean;
dragSort?: boolean;
rowKey?: string;
childrenColumnName: any;
}
const InternalTableBlockProvider = (props: Props) => {
const { params, showIndex, dragSort, rowKey, childrenColumnName } = props;
const field = useField();
const { resource, service } = useBlockRequestContext();
const [expandFlag, setExpandFlag] = useState(false);
return (
<FixedBlockWrapper>
<TableBlockContext.Provider
value={{
field,
service,
resource,
params,
showIndex,
dragSort,
rowKey,
expandFlag,
childrenColumnName,
setExpandFlag: () => setExpandFlag(!expandFlag),
}}
>
<RenderChildrenWithAssociationFilter {...props} />
</TableBlockContext.Provider>
</FixedBlockWrapper>
);
};
export const useAssociationNames = (collection) => {
const { getCollectionFields } = useCollectionManager();
const collectionFields = getCollectionFields(collection);
const associationFields = new Set();
for (const collectionField of collectionFields) {
if (collectionField.target) {
associationFields.add(collectionField.name);
const fields = getCollectionFields(collectionField.target);
for (const field of fields) {
if (field.target) {
associationFields.add(`${collectionField.name}.${field.name}`);
}
}
}
}
const fieldSchema = useFieldSchema();
const tableSchema = fieldSchema.reduceProperties((buf, schema) => {
if (schema['x-component'] === 'TableV2') {
return schema;
}
return buf;
}, new Schema({}));
return uniq(
tableSchema.reduceProperties((buf, schema) => {
if (schema['x-component'] === 'TableV2.Column') {
const s = schema.reduceProperties((buf, s) => {
const [name] = (s.name as string).split('.');
if (s['x-collection-field'] && associationFields.has(name)) {
return s;
}
return buf;
}, null);
if (s) {
// 关联字段和关联的关联字段
const [firstName] = s.name.split('.');
if (associationFields.has(s.name)) {
buf.push(s.name);
} else if (associationFields.has(firstName)) {
buf.push(firstName);
}
}
}
return buf;
}, []),
);
};
export const TableBlockProvider = (props) => {
const resourceName = props.resource;
const params = { ...props.params };
const appends = useAssociationNames(props.collection);
const fieldSchema = useFieldSchema();
const { getCollection, getCollectionField } = useCollectionManager();
const collection = getCollection(props.collection);
const { treeTable } = fieldSchema?.['x-decorator-props'] || {};
if (props.dragSort) {
params['sort'] = ['sort'];
}
let childrenColumnName = 'children';
if (collection?.tree && treeTable !== false) {
if (resourceName.includes('.')) {
const f = getCollectionField(resourceName);
if (f?.treeChildren) {
childrenColumnName = f.name;
params['tree'] = true;
}
} else {
const f = collection.fields.find((f) => f.treeChildren);
if (f) {
childrenColumnName = f.name;
}
params['tree'] = true;
}
}
if (!Object.keys(params).includes('appends')) {
params['appends'] = appends;
}
const form = useMemo(() => createForm(), [treeTable]);
return (
<SchemaComponentOptions scope={{ treeTable }}>
<FormContext.Provider value={form}>
<BlockProvider {...props} params={params}>
<InternalTableBlockProvider {...props} childrenColumnName={childrenColumnName} params={params} />
</BlockProvider>
</FormContext.Provider>
</SchemaComponentOptions>
);
};
export const useTableBlockContext = () => {
return useContext(TableBlockContext);
};
export const useTableBlockProps = () => {
const field = useField<ArrayField>();
const fieldSchema = useFieldSchema();
const ctx = useTableBlockContext();
const globalSort = fieldSchema.parent?.['x-decorator-props']?.['params']?.['sort'];
const { getDataBlocks } = useFilterBlock();
useEffect(() => {
if (!ctx?.service?.loading) {
field.value = ctx?.service?.data?.data;
field.data = field.data || {};
field.data.selectedRowKeys = ctx?.field?.data?.selectedRowKeys;
field.componentProps.pagination = field.componentProps.pagination || {};
field.componentProps.pagination.pageSize = ctx?.service?.data?.meta?.pageSize;
field.componentProps.pagination.total = ctx?.service?.data?.meta?.count;
field.componentProps.pagination.current = ctx?.service?.data?.meta?.page;
}
}, [ctx?.service?.loading]);
return {
childrenColumnName: ctx.childrenColumnName,
loading: ctx?.service?.loading,
showIndex: ctx.showIndex,
dragSort: ctx.dragSort,
rowKey: ctx.rowKey || 'id',
pagination:
ctx?.params?.paginate !== false
? {
defaultCurrent: ctx?.params?.page || 1,
defaultPageSize: ctx?.params?.pageSize,
}
: false,
onRowSelectionChange(selectedRowKeys) {
console.log(selectedRowKeys);
ctx.field.data = ctx?.field?.data || {};
ctx.field.data.selectedRowKeys = selectedRowKeys;
},
async onRowDragEnd({ from, to }) {
await ctx.resource.move({
sourceId: from[ctx.rowKey || 'id'],
targetId: to[ctx.rowKey || 'id'],
});
ctx.service.refresh();
},
onChange({ current, pageSize }, filters, sorter) {
let sort = sorter.order
? sorter.order === `ascend`
? [sorter.field]
: [`-${sorter.field}`]
: globalSort || ctx.service.params?.[0]?.sort;
ctx.service.run({ ...ctx.service.params?.[0], page: current, pageSize, sort });
},
onClickRow(record, setSelectedRow, selectedRow) {
const { targets, uid } = findFilterTargets(fieldSchema);
const dataBlocks = getDataBlocks();
// 如果是之前创建的区块是没有 x-filter-targets 属性的,所以这里需要判断一下避免报错
if (!targets || !targets.some((target) => dataBlocks.some((dataBlock) => dataBlock.uid === target.uid))) {
// 当用户已经点击过某一行,如果此时再把相连接的区块给删除的话,行的高亮状态就会一直保留。
// 这里暂时没有什么比较好的方法,只是在用户再次点击的时候,把高亮状态给清除掉。
setSelectedRow((prev) => (prev.length ? [] : prev));
return;
}
const value = [record[ctx.rowKey]];
dataBlocks.forEach((block) => {
const target = targets.find((target) => target.uid === block.uid);
if (!target) return;
const param = block.service.params?.[0] || {};
// 保留原有的 filter
const storedFilter = block.service.params?.[1]?.filters || {};
if (selectedRow.includes(record[ctx.rowKey])) {
delete storedFilter[uid];
} else {
storedFilter[uid] = {
$and: [
{
[target.field || ctx.rowKey]: {
[target.field ? '$in' : '$eq']: value,
},
},
],
};
}
const mergedFilter = mergeFilter([
...Object.values(storedFilter).map((filter) => removeNullCondition(filter)),
block.defaultFilter,
]);
return block.doFilter(
{
...param,
page: 1,
filter: mergedFilter,
},
{ filters: storedFilter },
);
});
// 更新表格的选中状态
setSelectedRow((prev) => (prev?.includes(record[ctx.rowKey]) ? [] : [...value]));
},
};
};