diff --git a/packages/app/src/pages/index.tsx b/packages/app/src/pages/index.tsx index a72eb11b8..6507e8644 100644 --- a/packages/app/src/pages/index.tsx +++ b/packages/app/src/pages/index.tsx @@ -5,6 +5,7 @@ import { AntdSchemaComponentProvider, APIClientProvider, AuthLayout, + BlockSchemaComponentProvider, BlockTemplateDetails, BlockTemplatePage, ChinaRegionProvider, @@ -95,6 +96,7 @@ const providers = [ }, }, ], + BlockSchemaComponentProvider, AntdSchemaComponentProvider, ChinaRegionProvider, WorkflowRouteProvider, diff --git a/packages/client/src/acl/ACLShortcut.tsx b/packages/client/src/acl/ACLShortcut.tsx index 58cc24c55..25d795ec5 100644 --- a/packages/client/src/acl/ACLShortcut.tsx +++ b/packages/client/src/acl/ACLShortcut.tsx @@ -1,25 +1,12 @@ import { LockOutlined } from '@ant-design/icons'; -import { ISchema, useForm } from '@formily/react'; +import { ISchema } from '@formily/react'; import { uid } from '@formily/shared'; import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { PluginManager } from '../plugin-manager'; -import { ActionContext, SchemaComponent, useActionContext } from '../schema-component'; +import { ActionContext, SchemaComponent } from '../schema-component'; import * as components from './Configuration'; -const useCloseAction = () => { - const { setVisible } = useActionContext(); - const form = useForm(); - return { - async run() { - setVisible(false); - form.submit((values) => { - console.log(values); - }); - }, - }; -}; - const schema: ISchema = { type: 'object', properties: { @@ -49,7 +36,7 @@ export const ACLShortcut = () => { setVisible(true); }} /> - + ); }; diff --git a/packages/client/src/acl/RolePermissionManager.tsx b/packages/client/src/acl/RolePermissionManager.tsx deleted file mode 100644 index b7b7c15d4..000000000 --- a/packages/client/src/acl/RolePermissionManager.tsx +++ /dev/null @@ -1,233 +0,0 @@ -import { Button, Checkbox, Divider, Drawer, Space, Table, Tabs, Tag, Typography } from 'antd'; -import React, { useState } from 'react'; -import { ScopeRecordPicker } from './ScopeRecordPicker'; - -export function RoleManager() {} - -export const RoleTable = () => { - return ( -
- - - - - value && '是', - }, - { - title: '操作', - dataIndex: 'actions', - key: 'actions', - render: () => ( - }> - - Edit role - Delete - - ), - }, - ]} - dataSource={[ - { - name: 'root', - title: '管理员', - description: '描述', - }, - { - name: 'member', - title: '普通成员', - description: '描述', - default: true, - }, - ]} - /> - - ); -}; - -const ConfigurePermissions = () => { - const [visible, setVisible] = useState(false); - return ( - <> - setVisible(true)}>Configure - setVisible(false)}> - - - - ); -}; - -export const RolePermissions = () => { - return ( - - - -
(value ? 单独配置 : 通用配置), - }, - { - title: '操作', - dataIndex: 'actions', - key: 'actions', - render: () => , - }, - ]} - dataSource={[ - { - title: '用户', - name: 'users', - }, - ]} - /> - - -
- 允许访问 - - ), - dataIndex: 'accessible', - key: 'accessible', - render: () => , - }, - ]} - dataSource={[ - { - title: '页面1', - }, - { - title: '页面2', - accessible: true, - }, - ]} - /> - - - ); -}; - -const ResourceActionsConfigure = () => { - const [visible, setVisible] = useState(false); - return ( - <> - setVisible(true)}>Configure - setVisible(false)} - footer={ - - - - - } - > - - - - ); -}; - -export const ResourceActionsForm = () => { - return ( -
-
- 允许操作 - - ), - dataIndex: 'enable', - key: 'enable', - render: () => , - }, - { - title: '可操作的数据范围', - dataIndex: 'scope', - key: 'scope', - render: () => , - }, - ]} - dataSource={[ - { - title: '添加', - }, - { - title: '导入', - enable: true, - }, - ]} - /> - - ); -}; diff --git a/packages/client/src/acl/ScopeRecordPicker.tsx b/packages/client/src/acl/ScopeRecordPicker.tsx deleted file mode 100644 index 41c3571c0..000000000 --- a/packages/client/src/acl/ScopeRecordPicker.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { createForm } from '@formily/core'; -import { FormContext, ISchema } from '@formily/react'; -import { Button, Space } from 'antd'; -import React, { useMemo } from 'react'; -import { SchemaComponent } from '../'; -import { AntdSchemaComponentProvider } from '../schema-component'; - -const schema: ISchema = { - type: 'object', - properties: { - scope: { - type: 'array', - default: [ - { id: 1, name: 'name1' }, - { id: 2, name: 'name2' }, - ], - 'x-component': 'RecordPicker', - properties: { - actions: { - 'x-component': 'ScopeActions', - }, - rowSelection: { - 'x-component': 'RowSelection', - 'x-component-props': { - rowKey: 'id', - objectValue: true, - rowSelection: { - type: 'radio', - }, - dataSource: [ - { id: 1, title: '全部数据' }, - { id: 2, title: '用户自己创建的数据' }, - { id: 3, title: '待审核的文章' }, - ], - }, - properties: { - column1: { - type: 'void', - title: '数据范围', - 'x-component': 'RowSelection.Column', - 'x-read-pretty': true, - properties: { - title: { - type: 'string', - 'x-component': 'Input', - 'x-read-pretty': true, - }, - }, - }, - }, - }, - }, - }, - }, -}; - -const ScopeActions = () => { - return ( - - - - - ); -}; - -export const ScopeRecordPicker = () => { - const form = useMemo(() => createForm({}), []); - return ( - - - - - - ); -}; diff --git a/packages/client/src/acl/demos/demo1.tsx b/packages/client/src/acl/demos/demo1.tsx deleted file mode 100644 index e48a39da3..000000000 --- a/packages/client/src/acl/demos/demo1.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { RoleTable } from '@nocobase/client'; -import React from 'react'; - -export default () => { - return ; -}; diff --git a/packages/client/src/acl/demos/demo2.tsx b/packages/client/src/acl/demos/demo2.tsx deleted file mode 100644 index 5571f3838..000000000 --- a/packages/client/src/acl/demos/demo2.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { RolePermissions } from '@nocobase/client'; -import React from 'react'; - -export default () => { - return ; -}; diff --git a/packages/client/src/acl/demos/demo3.tsx b/packages/client/src/acl/demos/demo3.tsx deleted file mode 100644 index 3e0778be5..000000000 --- a/packages/client/src/acl/demos/demo3.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { ResourceActionsForm } from '@nocobase/client'; -import React from 'react'; - -export default () => { - return ; -}; diff --git a/packages/client/src/acl/index.md b/packages/client/src/acl/index.md index 4a8bb558f..fa0ae96ff 100644 --- a/packages/client/src/acl/index.md +++ b/packages/client/src/acl/index.md @@ -7,10 +7,6 @@ group: path: /client --- -# ACL 待定 +# ACL -访问控制列表,plugin-acl 的前端模块 - - - - +访问控制列表,plugin-acl 的前端模块,详细文档待补充。 diff --git a/packages/client/src/acl/index.tsx b/packages/client/src/acl/index.tsx index eb4cb16c4..7a540754a 100644 --- a/packages/client/src/acl/index.tsx +++ b/packages/client/src/acl/index.tsx @@ -1,3 +1,3 @@ export * from './ACLProvider'; -export * from './RolePermissionManager'; export * from './ACLShortcut'; + diff --git a/packages/client/src/action-logs/ActionLogProvider.tsx b/packages/client/src/action-logs/ActionLogProvider.tsx index 544f5baa0..7ba24a051 100644 --- a/packages/client/src/action-logs/ActionLogProvider.tsx +++ b/packages/client/src/action-logs/ActionLogProvider.tsx @@ -1,19 +1,20 @@ -import React from 'react'; -import { SchemaInitializerProvider } from '../schema-initializer'; -import { initializes } from '../schema-initializer/Initializers'; +import React, { useContext } from 'react'; +import { SchemaInitializerContext, SchemaInitializerProvider } from '../schema-initializer'; + +const actionLogsItemGroup = { + type: 'itemGroup', + title: '{{t("Others")}}', + children: [ + { + type: 'item', + title: '{{t("Action logs")}}', + component: 'ActionLogBlockInitializer', + }, + ], +}; export const ActionLogProvider = (props: any) => { - const actionLogsItemGroup = { - type: 'itemGroup', - title: '{{t("Others")}}', - children: [ - { - type: 'item', - title: '{{t("Action logs")}}', - component: 'ActionLogBlockInitializer', - }, - ], - }; + const initializes = useContext(SchemaInitializerContext); initializes.BlockInitializers.items.push({ ...actionLogsItemGroup }); return {props.children}; }; diff --git a/packages/client/src/application/index.md b/packages/client/src/application/index.md index b60e014ff..40131982e 100644 --- a/packages/client/src/application/index.md +++ b/packages/client/src/application/index.md @@ -12,5 +12,3 @@ group: ## compose - - diff --git a/packages/client/src/block-provider/BlockProvider.tsx b/packages/client/src/block-provider/BlockProvider.tsx new file mode 100644 index 000000000..24ffbd9f9 --- /dev/null +++ b/packages/client/src/block-provider/BlockProvider.tsx @@ -0,0 +1,177 @@ +import { Field } from '@formily/core'; +import { useField } from '@formily/react'; +import { useRequest } from 'ahooks'; +import React, { createContext, useContext } from 'react'; +import { TableFieldResource, useAPIClient, useRecord } from '../'; +import { CollectionProvider, useCollection, useCollectionManager } from '../collection-manager'; +import { useRecordIndex } from '../record-provider'; + +export const BlockResourceContext = createContext(null); +export const BlockAssociationContext = createContext(null); +const BlockRequestContext = createContext(null); + +export const useBlockResource = () => { + return useContext(BlockResourceContext); +}; + +interface UseReousrceProps { + resource: any; + association?: any; + useSourceId?: any; + block?: any; +} + +const useAssociation = (props) => { + const { association } = props; + const { getCollectionField } = useCollectionManager(); + if (typeof association === 'string') { + return getCollectionField(association); + } else if (association?.collectionName && association?.name) { + return getCollectionField(`${association?.collectionName}.${association?.name}`); + } +}; + +const useReousrce = (props: UseReousrceProps) => { + const { block, resource, useSourceId } = props; + const record = useRecord(); + const api = useAPIClient(); + const association = useAssociation(props); + const sourceId = useSourceId?.(); + const field = useField(); + if (block === 'TableField') { + const options = { + field, + api, + resource, + sourceId: sourceId || record[association?.sourceKey || 'id'], + }; + return new TableFieldResource(options); + } + const __parent = useContext(BlockRequestContext); + if (__parent?.block === 'TableField' && __parent?.resource instanceof TableFieldResource) { + return __parent.resource; + } + if (!association) { + return api.resource(resource); + } + if (sourceId) { + return api.resource(resource, sourceId); + } + return api.resource(resource, record[association?.sourceKey || 'id']); +}; + +const useActionParams = (props) => { + const { useParams } = props; + const params = useParams?.() || {}; + return { ...props.params, ...params }; +}; + +export const useResourceAction = (props, opts = {}) => { + const { resource, action } = props; + const { fields } = useCollection(); + const appends = fields?.filter((field) => field.target).map((field) => field.name); + const params = useActionParams(props); + if (appends?.length) { + params['appends'] = appends; + } + const result = useRequest( + (params) => (action ? resource[action](params).then((res) => res.data) : Promise.resolve({})), + { + ...opts, + defaultParams: [params], + }, + ); + return result; +}; + +const MaybeCollectionProvider = (props) => { + const { collection } = props; + return collection ? ( + {props.children} + ) : ( + <>{props.children} + ); +}; + +const BlockRequestProvider = (props) => { + const field = useField(); + const resource = useBlockResource(); + const service = useResourceAction( + { ...props, resource }, + { + ...props.requestOptions, + }, + ); + const __parent = useContext(BlockRequestContext); + return ( + + {props.children} + + ); +}; + +export const useBlockRequestContext = () => { + return useContext(BlockRequestContext); +}; + +export const BlockProvider = (props) => { + const { collection, association } = props; + const resource = useReousrce(props); + return ( + + + + {props.children} + + + + ); +}; + +export const useBlockAssociationContext = () => { + return useContext(BlockAssociationContext); +}; + +export const useFilterByTk = () => { + const { resource, __parent } = useContext(BlockRequestContext); + const recordIndex = useRecordIndex(); + const record = useRecord(); + const collection = useCollection(); + const { getCollectionField } = useCollectionManager(); + const assoc = useContext(BlockAssociationContext); + if (resource instanceof TableFieldResource || __parent?.block === 'TableField') { + return recordIndex; + } + if (assoc) { + const association = getCollectionField(assoc); + return record?.[association.targetKey || 'id']; + } + return record?.[collection.filterTargetKey || 'id']; +}; + +export const useSourceIdFromRecord = () => { + const record = useRecord(); + const { getCollectionField } = useCollectionManager(); + const assoc = useContext(BlockAssociationContext); + if (assoc) { + const association = getCollectionField(assoc); + return record?.[association.sourceKey || 'id']; + } +}; + +export const useSourceIdFromParentRecord = () => { + const record = useRecord(); + const { getCollectionField } = useCollectionManager(); + const assoc = useContext(BlockAssociationContext); + if (assoc) { + const association = getCollectionField(assoc); + return record?.__parent?.[association.sourceKey || 'id']; + } +}; + +export const useParamsFromRecord = () => { + const filterByTk = useFilterByTk(); + return { + filterByTk: filterByTk, + }; +}; diff --git a/packages/client/src/block-provider/BlockSchemaComponentProvider.tsx b/packages/client/src/block-provider/BlockSchemaComponentProvider.tsx new file mode 100644 index 000000000..83f087a14 --- /dev/null +++ b/packages/client/src/block-provider/BlockSchemaComponentProvider.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { SchemaComponentOptions } from '../schema-component/core/SchemaComponentOptions'; +import { useParamsFromRecord, useSourceIdFromParentRecord, useSourceIdFromRecord } from './BlockProvider'; +import { CalendarBlockProvider, useCalendarBlockProps } from './CalendarBlockProvider'; +import { FormBlockProvider, useFormBlockProps } from './FormBlockProvider'; +import * as bp from './hooks'; +import { KanbanBlockProvider, useKanbanBlockProps } from './KanbanBlockProvider'; +import { TableBlockProvider, useTableBlockProps } from './TableBlockProvider'; +import { TableFieldProvider, useTableFieldProps } from './TableFieldProvider'; +import { TableSelectorProvider, useTableSelectorProps } from './TableSelectorProvider'; +export const BlockSchemaComponentProvider: React.FC = (props) => { + return ( + + {props.children} + + ); +}; diff --git a/packages/client/src/block-provider/CalendarBlockProvider.tsx b/packages/client/src/block-provider/CalendarBlockProvider.tsx new file mode 100644 index 000000000..bb3376135 --- /dev/null +++ b/packages/client/src/block-provider/CalendarBlockProvider.tsx @@ -0,0 +1,53 @@ +import { ArrayField } from '@formily/core'; +import { useField } from '@formily/react'; +import { Spin } from 'antd'; +import React, { createContext, useContext, useEffect } from 'react'; +import { BlockProvider, useBlockRequestContext } from './BlockProvider'; + +export const CalendarBlockContext = createContext({}); + +const InternalCalendarBlockProvider = (props) => { + const { fieldNames } = props; + const field = useField(); + const { resource, service } = useBlockRequestContext(); + if (service.loading) { + return ; + } + return ( + + {props.children} + + ); +}; + +export const CalendarBlockProvider = (props) => { + return ( + + + + ); +}; + +export const useCalendarBlockContext = () => { + return useContext(CalendarBlockContext); +}; + +export const useCalendarBlockProps = () => { + const ctx = useCalendarBlockContext(); + const field = useField(); + useEffect(() => { + if (!ctx?.service?.loading) { + field.componentProps.dataSource = ctx?.service?.data?.data; + } + }, [ctx?.service?.loading]); + return { + fieldNames: ctx.fieldNames, + }; +}; diff --git a/packages/client/src/block-provider/FormBlockProvider.tsx b/packages/client/src/block-provider/FormBlockProvider.tsx new file mode 100644 index 000000000..7d87bd548 --- /dev/null +++ b/packages/client/src/block-provider/FormBlockProvider.tsx @@ -0,0 +1,58 @@ +import { createForm } from '@formily/core'; +import { useField } from '@formily/react'; +import { Spin } from 'antd'; +import React, { createContext, useContext, useEffect, useMemo } from 'react'; +import { BlockProvider, useBlockRequestContext } from './BlockProvider'; + +export const FormBlockContext = createContext({}); + +const InternalFormBlockProvider = (props) => { + const { action, readPretty } = props; + const field = useField(); + const form = useMemo( + () => + createForm({ + readPretty, + }), + [], + ); + const { resource, service } = useBlockRequestContext(); + if (service.loading) { + return ; + } + return ( + + {props.children} + + ); +}; + +export const FormBlockProvider = (props) => { + return ( + + + + ); +}; + +export const useFormBlockContext = () => { + return useContext(FormBlockContext); +}; + +export const useFormBlockProps = () => { + const ctx = useFormBlockContext(); + useEffect(() => { + ctx.form.setInitialValues(ctx.service?.data?.data); + }, []); + return { + form: ctx.form, + }; +}; diff --git a/packages/client/src/block-provider/KanbanBlockProvider.tsx b/packages/client/src/block-provider/KanbanBlockProvider.tsx new file mode 100644 index 000000000..7c33536be --- /dev/null +++ b/packages/client/src/block-provider/KanbanBlockProvider.tsx @@ -0,0 +1,88 @@ +import { ArrayField } from '@formily/core'; +import { useField } from '@formily/react'; +import { Spin } from 'antd'; +import React, { createContext, useContext, useEffect } from 'react'; +import { useCollection } from '../collection-manager'; +import { toColumns } from '../schema-component/antd/kanban-v2/Kanban'; +import { BlockProvider, useBlockRequestContext } from './BlockProvider'; + +export const KanbanBlockContext = createContext({}); + +const useGroupField = (props) => { + const { getField } = useCollection(); + const { groupField } = props; + if (typeof groupField === 'string') { + return getField(groupField); + } + if (groupField?.name) { + return getField(groupField?.name); + } +}; + +const InternalKanbanBlockProvider = (props) => { + const field = useField(); + const { resource, service } = useBlockRequestContext(); + const groupField = useGroupField(props); + if (!groupField) { + return null; + } + if (service.loading) { + return ; + } + return ( + + {props.children} + + ); +}; + +export const KanbanBlockProvider = (props) => { + return ( + + + + ); +}; + +export const useKanbanBlockContext = () => { + return useContext(KanbanBlockContext); +}; + +export const useKanbanBlockProps = () => { + const field = useField(); + const ctx = useKanbanBlockContext(); + useEffect(() => { + if (!ctx?.service?.loading) { + field.value = toColumns(ctx.groupField, ctx?.service?.data?.data); + } + // field.loading = ctx?.service?.loading; + }, [ctx?.service?.loading]); + return { + groupField: ctx.groupField, + async onCardDragEnd({ columns, groupField }, { fromColumnId, fromPosition }, { toColumnId, toPosition }) { + const sourceColumn = columns.find((column) => column.id === fromColumnId); + const destinationColumn = columns.find((column) => column.id === toColumnId); + const sourceCard = sourceColumn?.cards?.[fromPosition]; + const targetCard = destinationColumn?.cards?.[toPosition]; + const values = { + sourceId: sourceCard.id, + sortField: `${groupField.name}_sort`, + }; + if (targetCard) { + values['targetId'] = targetCard.id; + } else { + values['targetScope'] = { + [groupField.name]: toColumnId, + }; + } + await ctx.resource.move(values); + }, + }; +}; diff --git a/packages/client/src/block-provider/TableBlockProvider.tsx b/packages/client/src/block-provider/TableBlockProvider.tsx new file mode 100644 index 000000000..7b21fc957 --- /dev/null +++ b/packages/client/src/block-provider/TableBlockProvider.tsx @@ -0,0 +1,123 @@ +import { ArrayField } from '@formily/core'; +import { Schema, useField, useFieldSchema } from '@formily/react'; +import React, { createContext, useContext, useEffect } from 'react'; +import { useCollectionManager } from '../collection-manager'; +import { BlockProvider, useBlockRequestContext } from './BlockProvider'; + +export const TableBlockContext = createContext({}); + +const InternalTableBlockProvider = (props) => { + const { params, showIndex, dragSort, rowKey } = props; + const field = useField(); + const { resource, service } = useBlockRequestContext(); + // if (service.loading) { + // return ; + // } + return ( + + {props.children} + + ); +}; + +const useAssociationNames = (collection) => { + const { getCollectionFields } = useCollectionManager(); + const names = getCollectionFields(collection) + ?.filter((field) => field.target) + .map((field) => field.name); + return names; + const fieldSchema = useFieldSchema(); + const tableSchema = fieldSchema.reduceProperties((buf, schema) => { + if (schema['x-component'] === 'TableV2') { + return schema; + } + return buf; + }, null) as Schema; + return tableSchema.reduceProperties((buf, schema) => { + if (schema['x-component'] === 'TableV2.Column') { + const s = schema.reduceProperties((buf, s) => { + if (s['x-collection-field'] && names.includes(s.name)) { + return s; + } + return buf; + }, null); + if (s) { + buf.push(s.name); + } + } + return buf; + }, []); +}; + +export const TableBlockProvider = (props) => { + const params = { ...props.params }; + const appends = useAssociationNames(props.collection); + if (props.dragSort) { + params['sort'] = ['sort']; + } + if (appends?.length) { + params['appends'] = appends; + } + return ( + + + + ); +}; + +export const useTableBlockContext = () => { + return useContext(TableBlockContext); +}; + +export const useTableBlockProps = () => { + const field = useField(); + const ctx = useTableBlockContext(); + 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 { + 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) { + 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 }) { + ctx.service.run({ ...ctx.service.params?.[0], page: current, pageSize }); + }, + }; +}; diff --git a/packages/client/src/block-provider/TableFieldProvider.tsx b/packages/client/src/block-provider/TableFieldProvider.tsx new file mode 100644 index 000000000..680585a75 --- /dev/null +++ b/packages/client/src/block-provider/TableFieldProvider.tsx @@ -0,0 +1,147 @@ +import { ArrayField, Field } from '@formily/core'; +import { useField } from '@formily/react'; +import React, { createContext, useContext, useEffect } from 'react'; +import { APIClient } from '../api-client'; +import { BlockProvider, useBlockRequestContext } from './BlockProvider'; + +export const TableFieldContext = createContext({}); + +const InternalTableFieldProvider = (props) => { + const { params = {}, showIndex, dragSort } = props; + const field = useField(); + const { resource, service } = useBlockRequestContext(); + // if (service.loading) { + // return ; + // } + return ( + + {props.children} + + ); +}; + +export class TableFieldResource { + field: Field; + api: APIClient; + sourceId: any; + resource?: any; + + constructor(options) { + this.field = options.field; + this.api = options.api; + this.sourceId = options.sourceId; + this.resource = this.api.resource(options.resource, this.sourceId); + } + + async list(options) { + this.field.data = this.field.data || {}; + if (this.field?.data?.dataSource?.length) { + console.log('list', this.field.data.dataSource); + return { + data: { + data: this.field.data.dataSource, + }, + }; + } + if (!this.sourceId) { + console.log('list.sourceId', this.field.data.dataSource); + this.field.data.dataSource = []; + return { + data: { + data: [], + }, + }; + } + const response = await this.resource.list(options); + console.log('list', response); + this.field.data.dataSource = response.data.data; + return { + data: { + data: response.data.data, + }, + }; + } + + async get(options) { + console.log('get', options); + const { filterByTk } = options; + return { + data: { + data: this.field.data.dataSource[filterByTk], + }, + }; + } + + async create(options) { + console.log('create', options); + const { values } = options; + this.field.data.dataSource.push(values); + } + + async update(options) { + console.log('update', options); + const { filterByTk, values } = options; + this.field.data.dataSource[filterByTk] = values; + } + + async destroy(options) { + console.log('destroy', options); + let { filterByTk } = options; + if (!Array.isArray(filterByTk)) { + filterByTk = [filterByTk]; + } + this.field.data.dataSource = this.field.data.dataSource.filter((item, index) => { + return !filterByTk.includes(index); + }); + } +} + +export const TableFieldProvider = (props) => { + return ( + + + + ); +}; + +export const useTableFieldContext = () => { + return useContext(TableFieldContext); +}; + +export const useTableFieldProps = () => { + const field = useField(); + const ctx = useTableFieldContext(); + useEffect(() => { + if (!ctx?.service?.loading) { + field.value = ctx?.service?.data?.data; + field.data = field.data || {}; + field.data.selectedRowKeys = ctx?.field?.data?.selectedRowKeys; + } + }, [ctx?.service?.loading]); + return { + size: 'middle', + loading: ctx?.service?.loading, + showIndex: ctx.showIndex, + dragSort: ctx.dragSort, + pagination: false, + rowKey: (record: any) => { + return field.value?.indexOf?.(record); + }, + onRowSelectionChange(selectedRowKeys) { + ctx.field.data = ctx?.field?.data || {}; + ctx.field.data.selectedRowKeys = selectedRowKeys; + }, + onChange({ current, pageSize }) { + ctx.service.run({ page: current, pageSize }); + }, + }; +}; diff --git a/packages/client/src/block-provider/TableSelectorProvider.tsx b/packages/client/src/block-provider/TableSelectorProvider.tsx new file mode 100644 index 000000000..86a5fc601 --- /dev/null +++ b/packages/client/src/block-provider/TableSelectorProvider.tsx @@ -0,0 +1,100 @@ +import { ArrayField } from '@formily/core'; +import { useField } from '@formily/react'; +import React, { createContext, useContext, useEffect } from 'react'; +import { useCollectionManager } from '../collection-manager'; +import { BlockProvider, useBlockRequestContext } from './BlockProvider'; + +export const TableSelectorContext = createContext({}); + +const InternalTableSelectorProvider = (props) => { + const { params, rowKey } = props; + const field = useField(); + const { resource, service } = useBlockRequestContext(); + // if (service.loading) { + // return ; + // } + return ( + + {props.children} + + ); +}; + +const useAssociationNames = (collection) => { + const { getCollectionFields } = useCollectionManager(); + const names = getCollectionFields(collection) + ?.filter((field) => field.target) + .map((field) => field.name); + return names; +}; + +export const TableSelectorProvider = (props) => { + const params = { ...props.params }; + const appends = useAssociationNames(props.collection); + if (props.dragSort) { + params['sort'] = ['sort']; + } + if (appends?.length) { + params['appends'] = appends; + } + return ( + + + + ); +}; + +export const useTableSelectorContext = () => { + return useContext(TableSelectorContext); +}; + +export const useTableSelectorProps = () => { + const field = useField(); + const ctx = useTableSelectorContext(); + 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 { + loading: ctx?.service?.loading, + showIndex: false, + dragSort: false, + rowKey: ctx.rowKey || 'id', + pagination: + ctx?.params?.paginate !== false + ? { + defaultCurrent: ctx?.params?.page || 1, + defaultPageSize: ctx?.params?.pageSize, + } + : false, + onRowSelectionChange(selectedRowKeys, selectedRows) { + 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 }) { + ctx.service.run({ ...ctx.service.params?.[0], page: current, pageSize }); + }, + }; +}; diff --git a/packages/client/src/block-provider/hooks/index.ts b/packages/client/src/block-provider/hooks/index.ts new file mode 100644 index 000000000..17ba92400 --- /dev/null +++ b/packages/client/src/block-provider/hooks/index.ts @@ -0,0 +1,76 @@ +import { useForm } from '@formily/react'; +import { useActionContext } from '../../schema-component'; +import { useBlockRequestContext, useFilterByTk } from '../BlockProvider'; + +export const usePickActionProps = () => { + const form = useForm(); + return { + onClick() { + console.log('usePickActionProps', form.values); + }, + }; +}; + +export const useCreateActionProps = () => { + const form = useForm(); + const { resource, __parent } = useBlockRequestContext(); + const { setVisible } = useActionContext(); + return { + async onClick() { + await form.submit(); + await resource.create({ + values: form.values, + }); + __parent?.service?.refresh?.(); + setVisible?.(false); + }, + }; +}; + +export const useUpdateActionProps = () => { + const form = useForm(); + const filterByTk = useFilterByTk(); + const { resource, __parent } = useBlockRequestContext(); + const { setVisible } = useActionContext(); + return { + async onClick() { + await form.submit(); + + await resource.update({ + filterByTk, + values: form.values, + }); + __parent?.service?.refresh?.(); + setVisible?.(false); + }, + }; +}; + +export const useDestroyActionProps = () => { + const filterByTk = useFilterByTk(); + const { resource, service } = useBlockRequestContext(); + return { + async onClick() { + await resource.destroy({ + filterByTk, + }); + service?.refresh?.(); + }, + }; +}; + +export const useBulkDestroyActionProps = () => { + const { field } = useBlockRequestContext(); + const { resource, service } = useBlockRequestContext(); + return { + async onClick() { + if (!field?.data?.selectedRowKeys?.length) { + return; + } + await resource.destroy({ + filterByTk: field.data?.selectedRowKeys, + }); + service?.refresh?.(); + }, + }; +}; diff --git a/packages/client/src/block-provider/index.tsx b/packages/client/src/block-provider/index.tsx new file mode 100644 index 000000000..e9d77ea6b --- /dev/null +++ b/packages/client/src/block-provider/index.tsx @@ -0,0 +1,9 @@ + +export * from './BlockProvider'; +export * from './BlockSchemaComponentProvider'; +export * from './CalendarBlockProvider'; +export * from './FormBlockProvider'; +export * from './KanbanBlockProvider'; +export * from './TableBlockProvider'; +export * from './TableFieldProvider'; + diff --git a/packages/client/src/board/demos/demo2.tsx b/packages/client/src/board/demos/demo2.tsx index fe52882e5..76c5c15a8 100644 --- a/packages/client/src/board/demos/demo2.tsx +++ b/packages/client/src/board/demos/demo2.tsx @@ -95,7 +95,7 @@ export default function App() { onNewCardConfirm={(draftCard) => draftCard} onCardNew={console.log} renderCard={(card, { column, dragging }) => { - return {card.title}; + return {card.title}; }} renderCardAdder={({ column }) => { return ( diff --git a/packages/client/src/collection-manager/CollectionField.tsx b/packages/client/src/collection-manager/CollectionField.tsx index 48c2910a8..8da074bed 100644 --- a/packages/client/src/collection-manager/CollectionField.tsx +++ b/packages/client/src/collection-manager/CollectionField.tsx @@ -10,7 +10,7 @@ import { useCollectionField } from './hooks'; const InternalField: React.FC = (props) => { const field = useField(); const fieldSchema = useFieldSchema(); - const { name, uiSchema } = useCollectionField(); + const { name, interface: interfaceType, uiSchema } = useCollectionField(); const component = useComponent(uiSchema?.['x-component']); const compile = useCompile(); const setFieldProps = (key, value) => { @@ -29,6 +29,9 @@ const InternalField: React.FC = (props) => { setFieldProps('title', uiSchema.title); setFieldProps('description', uiSchema.description); setFieldProps('initialValue', uiSchema.default); + if (!field.validator && uiSchema['x-validator']) { + field.validator = uiSchema['x-validator']; + } field.readPretty = uiSchema['x-read-pretty']; setRequired(); // @ts-ignore @@ -36,8 +39,16 @@ const InternalField: React.FC = (props) => { const originalProps = compile(uiSchema['x-component-props']) || {}; const componentProps = merge(originalProps, field.componentProps || {}); field.component = [component, componentProps]; - // console.log('componentProps', uiSchema?.title, field.componentProps); - }, [uiSchema?.title, uiSchema?.description, uiSchema?.required]); + // if (interfaceType === 'input') { + // field.componentProps.ellipsis = true; + // } else if (interfaceType === 'textarea') { + // field.componentProps.ellipsis = true; + // } else if (interfaceType === 'markdown') { + // field.componentProps.ellipsis = true; + // } else if (interfaceType === 'attachment') { + // field.componentProps.size = 'small'; + // } + }, [JSON.stringify(uiSchema)]); if (!uiSchema) { return null; } diff --git a/packages/client/src/collection-manager/CollectionProvider.tsx b/packages/client/src/collection-manager/CollectionProvider.tsx index 2ecf82d61..7dce442ca 100644 --- a/packages/client/src/collection-manager/CollectionProvider.tsx +++ b/packages/client/src/collection-manager/CollectionProvider.tsx @@ -5,10 +5,6 @@ import { CollectionOptions } from './types'; export const CollectionProvider: React.FC<{ name?: string; collection?: CollectionOptions }> = (props) => { const { name, collection, children } = props; - const { get } = useCollectionManager(); - return ( - - {children} - - ); + const { getCollection } = useCollectionManager(); + return {children}; }; diff --git a/packages/client/src/collection-manager/hooks/useCollectionManager.ts b/packages/client/src/collection-manager/hooks/useCollectionManager.ts index 2c759ed85..2326b2be8 100644 --- a/packages/client/src/collection-manager/hooks/useCollectionManager.ts +++ b/packages/client/src/collection-manager/hooks/useCollectionManager.ts @@ -12,13 +12,27 @@ export const useCollectionManager = () => { get(name: string) { return collections?.find((collection) => collection.name === name); }, - getCollection(name: string) { + getCollection(name: any) { + if (typeof name !== 'string') { + return name; + } return collections?.find((collection) => collection.name === name); }, getCollectionFields(name: string) { const collection = collections?.find((collection) => collection.name === name); return collection?.fields || []; }, + getCollectionField(name: string) { + const [collectionName, fieldName] = name.split('.'); + if (!fieldName) { + return; + } + const collection = collections?.find((collection) => collection.name === collectionName); + if (!collection) { + return; + } + return collection?.fields?.find((field) => field.name === fieldName); + }, getInterface(name: string) { return interfaces[name] ? clone(interfaces[name]) : null; }, diff --git a/packages/client/src/collection-manager/interfaces/attachment.ts b/packages/client/src/collection-manager/interfaces/attachment.ts index 28225f9e3..a61de8663 100644 --- a/packages/client/src/collection-manager/interfaces/attachment.ts +++ b/packages/client/src/collection-manager/interfaces/attachment.ts @@ -1,3 +1,4 @@ +import { ISchema } from '@formily/react'; import { uid } from '@formily/shared'; import { defaultProps, operators } from './properties'; import { IField } from './types'; @@ -21,6 +22,12 @@ export const attachment: IField = { }, }, }, + schemaInitialize(schema: ISchema, { block }) { + if (['Table', 'Kanban'].includes(block)) { + schema['x-component-props'] = schema['x-component-props'] || {}; + schema['x-component-props']['size'] = 'small'; + } + }, initialize: (values: any) => { if (!values.through) { values.through = `t_${uid()}`; diff --git a/packages/client/src/collection-manager/interfaces/createdBy.ts b/packages/client/src/collection-manager/interfaces/createdBy.ts index 55fdd592d..e1ede4e61 100644 --- a/packages/client/src/collection-manager/interfaces/createdBy.ts +++ b/packages/client/src/collection-manager/interfaces/createdBy.ts @@ -1,4 +1,5 @@ -import { defaultProps, operators } from './properties'; +import { ISchema } from '@formily/react'; +import { defaultProps, operators, recordPickerViewer } from './properties'; import { IField } from './types'; export const createdBy: IField = { @@ -43,4 +44,9 @@ export const createdBy: IField = { }, ], }, + schemaInitialize(schema: ISchema, { readPretty }) { + schema['properties'] = { + viewer: recordPickerViewer, + }; + }, }; diff --git a/packages/client/src/collection-manager/interfaces/datetime.ts b/packages/client/src/collection-manager/interfaces/datetime.ts index 8cc3d8b1a..aeefb0379 100644 --- a/packages/client/src/collection-manager/interfaces/datetime.ts +++ b/packages/client/src/collection-manager/interfaces/datetime.ts @@ -12,7 +12,7 @@ export const datetime: IField = { type: 'date', // name, uiSchema: { - type: 'datetime', + type: 'string', // title, 'x-component': 'DatePicker', 'x-component-props': { diff --git a/packages/client/src/collection-manager/interfaces/input.ts b/packages/client/src/collection-manager/interfaces/input.ts index b43999dd1..0f973f1de 100644 --- a/packages/client/src/collection-manager/interfaces/input.ts +++ b/packages/client/src/collection-manager/interfaces/input.ts @@ -1,3 +1,4 @@ +import { ISchema } from '@formily/react'; import { defaultProps, operators } from './properties'; import { IField } from './types'; @@ -22,4 +23,10 @@ export const input: IField = { filterable: { operators: operators.string, }, + schemaInitialize(schema: ISchema, { block }) { + if (['Table', 'Kanban'].includes(block)) { + schema['x-component-props'] = schema['x-component-props'] || {}; + schema['x-component-props']['ellipsis'] = true; + } + }, }; diff --git a/packages/client/src/collection-manager/interfaces/linkTo.ts b/packages/client/src/collection-manager/interfaces/linkTo.ts index 000a87dfa..9b7c6b0b6 100644 --- a/packages/client/src/collection-manager/interfaces/linkTo.ts +++ b/packages/client/src/collection-manager/interfaces/linkTo.ts @@ -1,5 +1,7 @@ +import { ISchema } from '@formily/react'; import { uid } from '@formily/shared'; -import { defaultProps } from './properties'; +import { cloneDeep } from 'lodash'; +import { defaultProps, recordPickerSelector, recordPickerViewer } from './properties'; import { IField } from './types'; export const linkTo: IField = { @@ -42,6 +44,17 @@ export const linkTo: IField = { }, }, }, + schemaInitialize(schema: ISchema, { readPretty }) { + if (readPretty) { + schema['properties'] = { + viewer: cloneDeep(recordPickerViewer), + }; + } else { + schema['properties'] = { + selector: cloneDeep(recordPickerSelector), + }; + } + }, initialize: (values: any) => { if (values.type === 'belongsToMany') { if (!values.through) { diff --git a/packages/client/src/collection-manager/interfaces/markdown.ts b/packages/client/src/collection-manager/interfaces/markdown.ts index fe9a1eec0..687987d28 100644 --- a/packages/client/src/collection-manager/interfaces/markdown.ts +++ b/packages/client/src/collection-manager/interfaces/markdown.ts @@ -1,3 +1,4 @@ +import { ISchema } from '@formily/react'; import { defaultProps } from './properties'; import { IField } from './types'; @@ -18,4 +19,10 @@ export const markdown: IField = { properties: { ...defaultProps, }, + schemaInitialize(schema: ISchema, { block }) { + if (['Table', 'Kanban'].includes(block)) { + schema['x-component-props'] = schema['x-component-props'] || {}; + schema['x-component-props']['ellipsis'] = true; + } + }, }; diff --git a/packages/client/src/collection-manager/interfaces/properties/index.ts b/packages/client/src/collection-manager/interfaces/properties/index.ts index 0afcc9666..632f908ca 100644 --- a/packages/client/src/collection-manager/interfaces/properties/index.ts +++ b/packages/client/src/collection-manager/interfaces/properties/index.ts @@ -1,5 +1,7 @@ import { ISchema } from '@formily/react'; +export * as operators from './operators'; + export const type: ISchema = { type: 'string', title: '{{t("Storage type")}}', @@ -199,5 +201,77 @@ export const defaultProps = { type, }; +export const recordPickerSelector: ISchema = { + type: 'void', + title: '{{ t("Select record") }}', + 'x-component': 'RecordPicker.Selector', + 'x-component-props': { + className: 'nb-record-picker-selector' + }, + properties: { + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'TableSelectorInitializers', + properties: {}, + }, + footer: { + 'x-component': 'Action.Container.Footer', + 'x-component-props': {}, + properties: { + actions: { + type: 'void', + 'x-component': 'ActionBar', + 'x-component-props': {}, + properties: { + submit: { + title: '{{ t("Submit") }}', + 'x-action': 'submit', + 'x-component': 'Action', + 'x-designer': 'Action.Designer', + 'x-component-props': { + type: 'primary', + htmlType: 'submit', + useProps: '{{ usePickActionProps }}', + }, + }, + }, + }, + }, + }, + }, +}; -export * as operators from './operators'; +export const recordPickerViewer = { + type: 'void', + title: '{{ t("View record") }}', + 'x-component': 'RecordPicker.Viewer', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + tabs: { + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'TabPaneInitializers', + properties: { + tab1: { + type: 'void', + title: '{{t("Details")}}', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + properties: { + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'RecordBlockInitializers', + properties: {}, + }, + }, + }, + }, + }, + }, +}; diff --git a/packages/client/src/collection-manager/interfaces/subTable.ts b/packages/client/src/collection-manager/interfaces/subTable.ts index 6bc8813e3..da3bfe95b 100644 --- a/packages/client/src/collection-manager/interfaces/subTable.ts +++ b/packages/client/src/collection-manager/interfaces/subTable.ts @@ -1,3 +1,4 @@ +import { ISchema } from '@formily/react'; import { uid } from '@formily/shared'; import { defaultProps } from './properties'; import { IField } from './types'; @@ -13,13 +14,53 @@ export const subTable: IField = { type: 'hasMany', // name, uiSchema: { - type: 'array', + type: 'void', // title, - 'x-component': 'Table', + 'x-component': 'TableField', 'x-component-props': {}, - enum: [], }, }, + schemaInitialize(schema: ISchema, { field, readPretty }) { + const association = `${field.collectionName}.${field.name}`; + schema['type'] = 'void'; + schema['x-component'] = 'TableField'; + schema['properties'] = { + block: { + type: 'void', + 'x-decorator': 'TableFieldProvider', + 'x-decorator-props': { + collection: field.target, + association: association, + resource: association, + action: 'list', + params: { + paginate: false, + }, + showIndex: true, + dragSort: false, + }, + properties: { + actions: { + type: 'void', + 'x-initializer': 'SubTableActionInitializers', + 'x-component': 'TableField.ActionBar', + 'x-component-props': {}, + }, + [field.name]: { + type: 'array', + 'x-initializer': 'TableColumnInitializers', + 'x-component': 'TableV2', + 'x-component-props': { + rowSelection: { + type: 'checkbox', + }, + useProps: '{{ useTableFieldProps }}', + }, + }, + }, + }, + }; + }, initialize: (values: any) => { if (!values.target) { values.target = `t_${uid()}`; diff --git a/packages/client/src/collection-manager/interfaces/textarea.ts b/packages/client/src/collection-manager/interfaces/textarea.ts index 9a5cbde3b..3561d51d6 100644 --- a/packages/client/src/collection-manager/interfaces/textarea.ts +++ b/packages/client/src/collection-manager/interfaces/textarea.ts @@ -1,3 +1,4 @@ +import { ISchema } from '@formily/react'; import { defaultProps } from './properties'; import { IField } from './types'; @@ -19,4 +20,10 @@ export const textarea: IField = { properties: { ...defaultProps, }, + schemaInitialize(schema: ISchema, { block }) { + if (['Table', 'Kanban'].includes(block)) { + schema['x-component-props'] = schema['x-component-props'] || {}; + schema['x-component-props']['ellipsis'] = true; + } + }, }; diff --git a/packages/client/src/collection-manager/interfaces/updatedAt.ts b/packages/client/src/collection-manager/interfaces/updatedAt.ts index 5eb008706..4498c5ee3 100644 --- a/packages/client/src/collection-manager/interfaces/updatedAt.ts +++ b/packages/client/src/collection-manager/interfaces/updatedAt.ts @@ -13,7 +13,7 @@ export const updatedAt: IField = { field: 'updatedAt', // name, uiSchema: { - type: 'datetime', + type: 'string', title: '{{t("Last updated at")}}', 'x-component': 'DatePicker', 'x-component-props': {}, diff --git a/packages/client/src/collection-manager/interfaces/updatedBy.ts b/packages/client/src/collection-manager/interfaces/updatedBy.ts index 282103a26..d809232e1 100644 --- a/packages/client/src/collection-manager/interfaces/updatedBy.ts +++ b/packages/client/src/collection-manager/interfaces/updatedBy.ts @@ -1,4 +1,5 @@ -import { defaultProps, operators } from './properties'; +import { ISchema } from '@formily/react'; +import { defaultProps, operators, recordPickerViewer } from './properties'; import { IField } from './types'; export const updatedBy: IField = { @@ -42,4 +43,9 @@ export const updatedBy: IField = { }, ], }, + schemaInitialize(schema: ISchema, { readPretty }) { + schema['properties'] = { + viewer: recordPickerViewer, + }; + }, }; diff --git a/packages/client/src/i18n/i18n.ts b/packages/client/src/i18n/i18n.ts index 16afc9b3f..791fb1720 100644 --- a/packages/client/src/i18n/i18n.ts +++ b/packages/client/src/i18n/i18n.ts @@ -1,34 +1,21 @@ import i18next from 'i18next'; import moment from 'moment'; import { initReactI18next } from 'react-i18next'; - -const zhCN = require('../locale/zh_CN'); -const enUS = require('../locale/en_US'); +import { resources } from '../locale'; const log = require('debug')('i18next'); export const i18n = i18next.createInstance(); i18n.use(initReactI18next).init({ lng: localStorage.getItem('locale') || 'en-US', - debug: false, + // debug: true, defaultNS: 'client', // parseMissingKeyHandler: (key) => { // console.log('parseMissingKeyHandler', `'${key}': '${key}',`); // return key; // }, // ns: ['client'], - resources: { - 'en-US': { - client: { - ...enUS, - }, - }, - 'zh-CN': { - client: { - ...zhCN, - }, - }, - }, + resources, }); const momentLngs = { diff --git a/packages/client/src/index.tsx b/packages/client/src/index.tsx index f6c861875..5a27f5422 100644 --- a/packages/client/src/index.tsx +++ b/packages/client/src/index.tsx @@ -6,6 +6,7 @@ export * from './antd-config-provider'; export * from './api-client'; export * from './application'; export * from './async-data-provider'; +export * from './block-provider'; export * from './board'; export * from './china-region'; export * from './collection-manager'; diff --git a/packages/client/src/locale/en_US.json b/packages/client/src/locale/en_US.ts similarity index 98% rename from packages/client/src/locale/en_US.json rename to packages/client/src/locale/en_US.ts index f2db3d52c..150f3eb9c 100644 --- a/packages/client/src/locale/en_US.json +++ b/packages/client/src/locale/en_US.ts @@ -1,8 +1,10 @@ -{ +export default { "{{count}} filter items_one": "{{count}} filter item", "{{count}} filter items_other": "{{count}} filter items", "{{count}} more items_one": "{{count}} more item", "{{count}} more items_other": "{{count}} more items", + "Total {{count}} items_one": "Total {{count}} item", + "Total {{count}} items_other": "Total {{count}} items", "Delete menu item": "Delete menu item", "Today": "Today", "Month": "Month", diff --git a/packages/client/src/locale/index.ts b/packages/client/src/locale/index.ts new file mode 100644 index 000000000..66fca384b --- /dev/null +++ b/packages/client/src/locale/index.ts @@ -0,0 +1,15 @@ +import enUS from './en_US'; +import zhCN from './zh_CN'; + +export const resources = { + 'en-US': { + client: { + ...enUS, + }, + }, + 'zh-CN': { + client: { + ...zhCN, + }, + }, +}; diff --git a/packages/client/src/locale/zh_CN.json b/packages/client/src/locale/zh_CN.ts similarity index 98% rename from packages/client/src/locale/zh_CN.json rename to packages/client/src/locale/zh_CN.ts index 5faad51aa..ef5d571ec 100644 --- a/packages/client/src/locale/zh_CN.json +++ b/packages/client/src/locale/zh_CN.ts @@ -1,10 +1,10 @@ -{ +export default { "Display <1><0>10<1>20<2>50<3>100 items per page": "每页显示 <1><0>10<1>20<2>50<3>100 条", "Meet <1><0>All<1>Any conditions in the group": "满足组内 <1><0>全部<1>任意 条件", "Open in<1><0>Modal<1>Drawer<2>Window": "在 <1><0>对话框<1>抽屉<2>窗口 内打开", "{{count}} filter items": "{{count}} 个筛选项", "{{count}} more items": "还有 {{count}} 项", - + "Total {{count}} items": "总共 {{count}} 条", "Today": "今天", "Month": "月", "Week": "周", @@ -285,5 +285,7 @@ "Block templates": "区块模板", "Convert reference to duplicate": "模板引用转为复制", "Template name": "模板名称", - "Block type": "区块类型" + "Block type": "区块类型", + "Action column": "操作列", + "Records per page": "每页显示数量" } diff --git a/packages/client/src/record-provider/index.tsx b/packages/client/src/record-provider/index.tsx index f2f758d5f..f5bab9775 100644 --- a/packages/client/src/record-provider/index.tsx +++ b/packages/client/src/record-provider/index.tsx @@ -5,7 +5,8 @@ export const RecordIndexContext = createContext(null); export const RecordProvider: React.FC<{ record: any }> = (props) => { const { record, children } = props; - return {children}; + const __parent = useContext(RecordContext); + return {children}; }; export const RecordIndexProvider: React.FC<{ index: any }> = (props) => { diff --git a/packages/client/src/schema-component/antd/AntdSchemaComponentProvider.tsx b/packages/client/src/schema-component/antd/AntdSchemaComponentProvider.tsx index 4e7d0c035..15ed7644a 100644 --- a/packages/client/src/schema-component/antd/AntdSchemaComponentProvider.tsx +++ b/packages/client/src/schema-component/antd/AntdSchemaComponentProvider.tsx @@ -2,8 +2,9 @@ import React from 'react'; import * as components from '.'; import * as common from '../common'; import { SchemaComponentOptions } from '../core/SchemaComponentOptions'; +import { useFilterActionProps } from './filter/useFilterActionProps'; export const AntdSchemaComponentProvider = (props) => { const { children } = props; - return {children}; + return {children}; }; diff --git a/packages/client/src/schema-component/antd/action/Action.Designer.tsx b/packages/client/src/schema-component/antd/action/Action.Designer.tsx index 0e20b016c..1ea3fa2c0 100644 --- a/packages/client/src/schema-component/antd/action/Action.Designer.tsx +++ b/packages/client/src/schema-component/antd/action/Action.Designer.tsx @@ -4,7 +4,6 @@ import { useDesignable } from '../..'; import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings'; export const ActionDesigner = (props) => { - const initialValue = {}; const field = useField(); const fieldSchema = useFieldSchema(); const { dn } = useDesignable(); @@ -22,21 +21,35 @@ export const ActionDesigner = (props) => { 'x-decorator': 'FormItem', 'x-component': 'Input', title: '按钮标题', + default: fieldSchema.title, + 'x-component-props': {}, + // description: `原字段标题:${collectionField?.uiSchema?.title}`, + }, + icon: { + 'x-decorator': 'FormItem', + 'x-component': 'IconPicker', + title: '按钮图标', + default: fieldSchema?.['x-component-props']?.icon, 'x-component-props': {}, // description: `原字段标题:${collectionField?.uiSchema?.title}`, }, }, } as ISchema } - initialValues={{ title: field.title }} - onSubmit={({ title }) => { + onSubmit={({ title, icon }) => { if (title) { fieldSchema.title = title; field.title = title; + field.componentProps.icon = icon; + fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {}; + fieldSchema['x-component-props'].icon = icon; dn.emit('patch', { schema: { ['x-uid']: fieldSchema['x-uid'], title, + 'x-component-props': { + ...fieldSchema['x-component-props'], + }, }, }); dn.refresh(); diff --git a/packages/client/src/schema-component/antd/action/Action.Drawer.tsx b/packages/client/src/schema-component/antd/action/Action.Drawer.tsx index 95f7e702d..c55451e14 100644 --- a/packages/client/src/schema-component/antd/action/Action.Drawer.tsx +++ b/packages/client/src/schema-component/antd/action/Action.Drawer.tsx @@ -36,6 +36,17 @@ export const ActionDrawer: ComposedActionDrawer = observer((props) => { background: #f0f2f5; } } + &.nb-record-picker-selector { + .nb-block-item { + margin-bottom: 24px; + .general-schema-designer { + top: -8px; + bottom: -8px; + left: -8px; + right: -8px; + } + } + } `, )} footer={ diff --git a/packages/client/src/schema-component/antd/action/Action.tsx b/packages/client/src/schema-component/antd/action/Action.tsx index e49714912..ad0cdea79 100644 --- a/packages/client/src/schema-component/antd/action/Action.tsx +++ b/packages/client/src/schema-component/antd/action/Action.tsx @@ -4,8 +4,10 @@ import { Button, Modal, Popover } from 'antd'; import classnames from 'classnames'; import React, { useState } from 'react'; import { useActionContext } from '../..'; +import { Icon } from '../../../icon'; import { SortableItem } from '../../common'; import { useDesigner } from '../../hooks'; +import { useProps } from '../../hooks/useProps'; import ActionContainer from './Action.Container'; import { ActionDesigner } from './Action.Designer'; import { ActionDrawer } from './Action.Drawer'; @@ -71,10 +73,12 @@ export const Action: ComposedAction = observer((props: any) => { containerRefKey, component, useAction = useA, - onClick, className, + disabled, + icon, ...others } = props; + const { onClick } = useProps(props); const [visible, setVisible] = useState(false); const Designer = useDesigner(); const field = useField(); @@ -84,6 +88,7 @@ export const Action: ComposedAction = observer((props: any) => { const renderButton = () => ( } onClick={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); @@ -104,8 +109,8 @@ export const Action: ComposedAction = observer((props: any) => { component={component || Button} className={classnames(className, actionDesignerCss)} > - {field.title} + ); return ( diff --git a/packages/client/src/schema-component/antd/calendar-v2/Calendar.Designer.tsx b/packages/client/src/schema-component/antd/calendar-v2/Calendar.Designer.tsx new file mode 100644 index 000000000..a1219566f --- /dev/null +++ b/packages/client/src/schema-component/antd/calendar-v2/Calendar.Designer.tsx @@ -0,0 +1,142 @@ +import { ISchema, useField, useFieldSchema } from '@formily/react'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useCompile, useDesignable } from '../..'; +import { useCalendarBlockContext } from '../../../block-provider'; +import { useCollection } from '../../../collection-manager'; +import { useCollectionFilterOptions } from '../../../collection-manager/action-hooks'; +import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings'; +import { useSchemaTemplate } from '../../../schema-templates'; + +const useOptions = () => { + const compile = useCompile(); + const { fields } = useCollection(); + const options = fields?.map((field) => { + return { + value: field.name, + label: compile(field?.uiSchema?.title), + }; + }); + return options; +} + +export const CalendarDesigner = () => { + const field = useField(); + const fieldSchema = useFieldSchema(); + const { name, title, fields } = useCollection(); + const dataSource = useCollectionFilterOptions(name); + const { service } = useCalendarBlockContext(); + const { dn } = useDesignable(); + const compile = useCompile(); + const { t } = useTranslation(); + const template = useSchemaTemplate(); + const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {}; + const options = useOptions(); + const fieldNames = fieldSchema?.['x-decorator-props']?.['fieldNames'] || {}; + return ( + + { + const fieldNames = field.decoratorProps.fieldNames || {}; + fieldNames['title'] = title; + field.decoratorProps.params = fieldNames; + fieldSchema['x-decorator-props']['params'] = fieldNames; + service.refresh(); + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': field.decoratorProps, + }, + }); + dn.refresh(); + }} + /> + { + const fieldNames = field.decoratorProps.fieldNames || {}; + fieldNames['start'] = start; + field.decoratorProps.params = fieldNames; + fieldSchema['x-decorator-props']['params'] = fieldNames; + service.refresh(); + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': field.decoratorProps, + }, + }); + dn.refresh(); + }} + /> + { + const fieldNames = field.decoratorProps.fieldNames || {}; + fieldNames['end'] = end; + field.decoratorProps.params = fieldNames; + fieldSchema['x-decorator-props']['params'] = fieldNames; + service.refresh(); + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': field.decoratorProps, + }, + }); + dn.refresh(); + }} + /> + { + const params = field.decoratorProps.params || {}; + params.filter = filter; + field.decoratorProps.params = params; + fieldSchema['x-decorator-props']['params'] = params; + service.run({ ...service?.params?.[0], filter }); + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': field.decoratorProps, + }, + }); + }} + /> + + + + + + ); +}; diff --git a/packages/client/src/schema-component/antd/calendar-v2/Calendar.tsx b/packages/client/src/schema-component/antd/calendar-v2/Calendar.tsx new file mode 100644 index 000000000..6d7a7181f --- /dev/null +++ b/packages/client/src/schema-component/antd/calendar-v2/Calendar.tsx @@ -0,0 +1,156 @@ +import { LeftOutlined, RightOutlined } from '@ant-design/icons'; +import { createForm } from '@formily/core'; +import { observer, RecursionField, Schema, useFieldSchema } from '@formily/react'; +import get from 'lodash/get'; +import moment from 'moment'; +import React, { useMemo, useState } from 'react'; +import { Calendar as BigCalendar, momentLocalizer } from 'react-big-calendar'; +import * as dates from 'react-big-calendar/lib/utils/dates'; +import { useTranslation } from 'react-i18next'; +import { RecordProvider } from '../../../'; +import { i18n } from '../../../i18n'; +import { useProps } from '../../hooks/useProps'; +import { ActionContext } from '../action'; +import { CalendarToolbarContext } from './context'; +import './style.less'; +import type { ToolbarProps } from './types'; + +const localizer = momentLocalizer(moment); + +function Toolbar(props: ToolbarProps) { + const fieldSchema = useFieldSchema(); + const toolBarSchema: Schema = useMemo( + () => + fieldSchema.reduceProperties((buf, current) => { + if (current['x-component'].endsWith('.ActionBar')) { + return current; + } + return buf; + }, null), + [], + ); + return ( + + + + ); +} + +const messages: any = { + allDay: '', + previous: ( +
+ +
+ ), + next: ( +
+ +
+ ), + today: i18n.t('Today'), + month: i18n.t('Month'), + week: i18n.t('Week'), + work_week: i18n.t('Work week'), + day: i18n.t('Day'), + agenda: i18n.t('Agenda'), + date: i18n.t('Date'), + time: i18n.t('Time'), + event: i18n.t('Event'), + noEventsInRange: i18n.t('None'), + showMore: (count) => i18n.t('{{count}} more items', { count }), +}; + +const useEvents = (dataSource: any, fieldNames: any) => { + const { t } = useTranslation(); + return useMemo( + () => + Array.isArray(dataSource) + ? dataSource?.map((item) => { + return { + id: get(item, fieldNames.id || 'id'), + title: get(item, fieldNames.title) || t('Untitle'), + start: new Date(get(item, fieldNames.start)), + end: new Date(get(item, fieldNames.end || fieldNames.start)), + }; + }) + : [], + [dataSource, fieldNames], + ); +}; + +const CalendarRecordViewer = (props) => { + const { visible, setVisible, record } = props; + const form = useMemo(() => createForm(), [record]); + const fieldSchema = useFieldSchema(); + const eventSchema: Schema = useMemo( + () => + fieldSchema.reduceProperties((buf, current) => { + if (current['x-component'].endsWith('.Event')) { + return current; + } + return buf; + }, null), + [], + ); + return ( + eventSchema && ( + + + + + + ) + ); +}; + +export const Calendar: any = observer((props: any) => { + const { dataSource, fieldNames } = useProps(props); + const events = useEvents(dataSource, fieldNames); + const [visible, setVisible] = useState(false); + const [record, setRecord] = useState({}); + return ( +
+ + { + console.log('onSelectSlot', slotInfo); + }} + onDoubleClickEvent={(event) => { + console.log('onDoubleClickEvent'); + }} + onSelectEvent={(event) => { + const record = dataSource?.find((item) => item[fieldNames.id] === event.id); + if (!record) { + return; + } + setRecord(record); + setVisible(true); + }} + formats={{ + monthHeaderFormat: 'Y-M', + agendaDateFormat: 'M-DD', + dayHeaderFormat: 'Y-M-DD', + dayRangeHeaderFormat: ({ start, end }, culture, local) => { + if (dates.eq(start, end, 'month')) { + return local.format(start, 'Y-M', culture); + } + return `${local.format(start, 'Y-M', culture)} - ${local.format(end, 'Y-M', culture)}`; + }, + }} + defaultDate={new Date()} + components={{ + toolbar: Toolbar, + }} + localizer={localizer} + /> +
+ ); +}); diff --git a/packages/client/src/schema-component/antd/calendar-v2/Event.tsx b/packages/client/src/schema-component/antd/calendar-v2/Event.tsx new file mode 100644 index 000000000..73a9f26f6 --- /dev/null +++ b/packages/client/src/schema-component/antd/calendar-v2/Event.tsx @@ -0,0 +1,6 @@ +import { observer } from '@formily/react'; +import React from 'react'; + +export const Event = observer((props) => { + return <>{props.children}; +}); diff --git a/packages/client/src/schema-component/antd/calendar-v2/Nav.tsx b/packages/client/src/schema-component/antd/calendar-v2/Nav.tsx new file mode 100644 index 000000000..4a8bd3ca3 --- /dev/null +++ b/packages/client/src/schema-component/antd/calendar-v2/Nav.tsx @@ -0,0 +1,19 @@ +import { LeftOutlined, RightOutlined } from '@ant-design/icons'; +import { observer } from '@formily/react'; +import { Button } from 'antd'; +import React, { useContext } from 'react'; +import { navigate } from 'react-big-calendar/lib/utils/constants'; +import { useDesignable } from '../../hooks'; +import { CalendarToolbarContext } from './context'; + +export const Nav = observer((props) => { + const { DesignableBar } = useDesignable(); + const { onNavigate } = useContext(CalendarToolbarContext); + return ( +
+ + + +
+ ); +}); diff --git a/packages/client/src/schema-component/antd/calendar-v2/Title.tsx b/packages/client/src/schema-component/antd/calendar-v2/Title.tsx new file mode 100644 index 000000000..379a6a70f --- /dev/null +++ b/packages/client/src/schema-component/antd/calendar-v2/Title.tsx @@ -0,0 +1,15 @@ +import { observer } from '@formily/react'; +import React, { useContext } from 'react'; +import { useDesignable } from '../../hooks'; +import { CalendarToolbarContext } from './context'; + +export const Title = observer(() => { + const { DesignableBar } = useDesignable(); + const { label } = useContext(CalendarToolbarContext); + return ( +
+ {label} + +
+ ); +}); diff --git a/packages/client/src/schema-component/antd/calendar-v2/Today.tsx b/packages/client/src/schema-component/antd/calendar-v2/Today.tsx new file mode 100644 index 000000000..dd8804f7b --- /dev/null +++ b/packages/client/src/schema-component/antd/calendar-v2/Today.tsx @@ -0,0 +1,23 @@ +import { observer } from '@formily/react'; +import { Button } from 'antd'; +import React, { useContext } from 'react'; +import { navigate } from 'react-big-calendar/lib/utils/constants'; +import { useTranslation } from 'react-i18next'; +import { useDesignable } from '../../hooks'; +import { CalendarToolbarContext } from './context'; + +export const Today = observer((props) => { + const { DesignableBar } = useDesignable(); + const { onNavigate } = useContext(CalendarToolbarContext); + const { t } = useTranslation(); + return ( + + ); +}); diff --git a/packages/client/src/schema-component/antd/calendar-v2/ViewSelect.tsx b/packages/client/src/schema-component/antd/calendar-v2/ViewSelect.tsx new file mode 100644 index 000000000..027ea36e3 --- /dev/null +++ b/packages/client/src/schema-component/antd/calendar-v2/ViewSelect.tsx @@ -0,0 +1,25 @@ +import { observer } from '@formily/react'; +import { Select } from 'antd'; +import React, { useContext } from 'react'; +import { useDesignable } from '../../hooks'; +import { CalendarToolbarContext } from './context'; + +export const ViewSelect = observer((props) => { + const { DesignableBar } = useDesignable(); + const { + views, + view, + onView, + localizer: { messages }, + } = useContext(CalendarToolbarContext); + return ( +
+ + +
+ ); +}); diff --git a/packages/client/src/schema-component/antd/calendar-v2/context.ts b/packages/client/src/schema-component/antd/calendar-v2/context.ts new file mode 100644 index 000000000..31dc497ba --- /dev/null +++ b/packages/client/src/schema-component/antd/calendar-v2/context.ts @@ -0,0 +1,5 @@ +import { createContext } from 'react'; +import type { ToolbarProps } from './types'; + +export const CalendarToolbarContext = createContext(null); +export const CalendarContext = createContext(null); diff --git a/packages/client/src/schema-component/antd/calendar-v2/demos/collections.ts b/packages/client/src/schema-component/antd/calendar-v2/demos/collections.ts new file mode 100644 index 000000000..2eed0197b --- /dev/null +++ b/packages/client/src/schema-component/antd/calendar-v2/demos/collections.ts @@ -0,0 +1,1321 @@ +export default { + data: [ + { + key: '17zb9wfq0vt', + name: 'product', + title: 's', + inherit: false, + fields: [], + createdBy: true, + updatedBy: true, + sortable: true, + }, + { + key: '1s4aq7jbxdh', + name: 'roles', + title: '{{t("Roles")}}', + inherit: false, + fields: [ + { + key: 'gs6190e27z5', + name: 'name', + type: 'uid', + interface: 'input', + collectionName: 'roles', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'vy6i30jm9jm', + uiSchema: { + 'x-uid': 'vy6i30jm9jm', + name: 'vlldvknkn3t', + type: 'string', + title: '{{t("Role UID")}}', + 'x-component': 'Input', + }, + prefix: 'r_', + primaryKey: true, + }, + { + key: 'w0hvovoxnjh', + name: 'title', + type: 'string', + interface: 'input', + collectionName: 'roles', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'pyq24tp0zkj', + uiSchema: { + 'x-uid': 'pyq24tp0zkj', + name: 'wtku9z07h69', + type: 'string', + title: '{{t("Role name")}}', + 'x-component': 'Input', + }, + unique: true, + }, + { + key: 'lxysmomsdhy', + name: 'description', + type: 'string', + interface: null, + collectionName: 'roles', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + }, + { + key: 'ynyhe4kgxku', + name: 'strategy', + type: 'json', + interface: null, + collectionName: 'roles', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + }, + { + key: 'qr9949ps9dv', + name: 'default', + type: 'boolean', + interface: null, + collectionName: 'roles', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + defaultValue: false, + }, + { + key: '11ts4nwrm8c', + name: 'allowConfigure', + type: 'boolean', + interface: null, + collectionName: 'roles', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + }, + { + key: 'nxj4s7xev9g', + name: 'allowNewMenu', + type: 'boolean', + interface: null, + collectionName: 'roles', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + }, + { + key: 'ibwisd53xeg', + name: 'menuUiSchemas', + type: 'belongsToMany', + interface: null, + collectionName: 'roles', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + target: 'uiSchemas', + targetKey: 'x-uid', + foreignKey: 'roleName', + sourceKey: 'name', + otherKey: 'uiSchemaXUid', + through: 'rolesUischemas', + }, + { + key: '0vt1uoy8f47', + name: 'resources', + type: 'hasMany', + interface: null, + collectionName: 'roles', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + target: 'rolesResources', + sourceKey: 'name', + targetKey: 'id', + foreignKey: 'roleName', + }, + ], + autoGenId: false, + model: 'RoleModel', + filterTargetKey: 'name', + }, + { + key: 'nhng5sgypw8', + name: 't_j6omof6tza8', + title: '任务', + inherit: false, + fields: [ + { + key: 'aabs1ya9aux', + name: 'f_g8j5jvalqh0', + type: 'string', + interface: 'input', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'tzb6h0tsbis', + uiSchema: { + 'x-uid': 'tzb6h0tsbis', + name: 'y94c05v2hp2', + type: 'string', + 'x-component': 'Input', + title: '名称', + enum: [], + }, + }, + { + key: 'kidn54kisne', + name: 'f_tegyd222bcc', + type: 'text', + interface: 'textarea', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'p4uucjm3rrx', + uiSchema: { + 'x-uid': 'p4uucjm3rrx', + name: 'ri9ulk87owm', + type: 'string', + 'x-component': 'Input.TextArea', + title: '描述', + enum: [], + }, + }, + { + key: 'kwdiruosgsb', + name: 'f_u007sq2jg93', + type: 'belongsToMany', + interface: 'linkTo', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: 's5w3l6pl1hn', + uiSchemaUid: 'iea79k3kmao', + uiSchema: { + 'x-uid': 'iea79k3kmao', + name: 'njked4wch3i', + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'id', value: 'id' } }, + title: '负责人', + enum: [], + }, + target: 'users', + targetKey: 'id', + sourceKey: 'id', + through: 't_7476m1wwsfe', + foreignKey: 'f_9zwkbo7dd18', + otherKey: 'f_q84aphtx7eb', + }, + { + key: 'w7civak6gp3', + name: 'f_hpmvdltzs6m', + type: 'string', + interface: 'radioGroup', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: '481uv9zj072', + uiSchema: { + 'x-uid': '481uv9zj072', + name: '8fyelcfuzfu', + enum: [ + { value: '67snt3e6yld', label: '未开始', color: 'default' }, + { value: 'ht963n365al', label: '进行中', color: 'green' }, + { value: '2s2s8wzcnm0', label: '测试中', color: 'volcano' }, + { value: 'irepvdzp1ac', label: '已完成', color: 'blue' }, + ], + type: 'string', + 'x-component': 'Radio.Group', + title: '状态', + }, + }, + { + key: '9x87w5z0ea6', + name: 'f_jj9cyhron1d', + type: 'hasMany', + interface: 'subTable', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'pndvx008mmn', + uiSchema: { + 'x-uid': 'pndvx008mmn', + name: 'l8xqzpkxj3p', + type: 'array', + 'x-component': 'Table', + 'x-component-props': {}, + enum: [], + title: '子任务', + }, + targetKey: 'id', + sourceKey: 'id', + foreignKey: 'f_yzivojrp6l8', + target: 't_ab12qiwruwk', + }, + { + key: '3f4y1iq16ux', + name: 'f_ooar0pto2ko', + type: 'belongsToMany', + interface: 'attachment', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: '5xf7izxjny5', + uiSchema: { + 'x-uid': '5xf7izxjny5', + name: '9az9003ijcm', + 'x-component-props': { multiple: true, action: 'attachments:upload' }, + type: 'array', + 'x-component': 'Upload.Attachment', + title: '附件', + enum: [], + }, + target: 'attachments', + targetKey: 'id', + sourceKey: 'id', + through: 't_5a7wh2qhk4l', + foreignKey: 'f_tqfsxclrexu', + otherKey: 'f_j2krlyq6oka', + }, + { + key: '4um5urxskg0', + name: 'f_f7txg1oc3nt', + type: 'date', + interface: 'createdAt', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'jjax6r7c84m', + uiSchema: { + 'x-uid': 'jjax6r7c84m', + name: 'clidfmw96u3', + 'x-component-props': { dateFormat: 'YYYY-MM-DD', showTime: true, timeFormat: 'HH:mm:ss' }, + type: 'datetime', + title: '创建时间', + 'x-component': 'DatePicker', + 'x-read-pretty': true, + enum: [], + }, + field: 'createdAt', + }, + { + key: 'dnwqg2s3oph', + name: 'f_2dpc76bszit', + type: 'belongsTo', + interface: 'createdBy', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'po3y2pc112z', + uiSchema: { + 'x-uid': 'po3y2pc112z', + name: 'ke2rte2zikm', + type: 'object', + title: '创建人', + 'x-component': 'RecordPicker', + 'x-component-props': { fieldNames: { value: 'id', label: 'nickname' } }, + 'x-read-pretty': true, + enum: [], + }, + target: 'users', + foreignKey: 'createdById', + targetKey: 'id', + }, + { + key: '1sayjktagk3', + name: 'f_z27302tl2bf', + type: 'string', + interface: 'select', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: '0a28rxnov8o', + uiSchema: { + 'x-uid': '0a28rxnov8o', + name: '2vg44ojdq4j', + enum: [ + { value: 'mkvgt7ndsqd', label: '缺陷', color: 'volcano' }, + { value: '9yyv2tawmry', label: '交互', color: 'green' }, + { value: 'vx4bhmtsuus', label: '需求', color: 'cyan' }, + ], + type: 'string', + 'x-component': 'Select', + title: '类型', + }, + }, + { + key: 'zhvtzwta7bz', + name: 'f_yc8jbfiqfvh', + type: 'string', + interface: 'radioGroup', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'jijqs397op9', + uiSchema: { + 'x-uid': 'jijqs397op9', + name: '99v9hnujiuh', + enum: [ + { value: 's7hocw6twfk', label: '高', color: 'red' }, + { value: 'v25527dxseu', label: '中', color: 'blue' }, + { value: 'c4hobfb5k07', label: '低', color: 'lime' }, + ], + type: 'string', + 'x-component': 'Radio.Group', + title: '优先级', + }, + }, + { + key: 'nobsmkp6ypg', + name: 'f_ksgzy9vmgce', + type: 'belongsToMany', + interface: 'linkTo', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: 'z70jtfabrm7', + uiSchemaUid: '3gbviwfd0cv', + uiSchema: { + 'x-uid': '3gbviwfd0cv', + name: '6sabm3win42', + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: false, fieldNames: { label: 'id', value: 'id' } }, + title: '迭代', + enum: [], + }, + target: 't_94rsj6kbzvn', + targetKey: 'id', + sourceKey: 'id', + through: 't_ewdf2dsjlu3', + foreignKey: 'f_72tnpdz6kfg', + otherKey: 'f_8do8o1wsavk', + }, + { + key: 'rwtqeecnbst', + name: 'f_zek99qhv0vc', + type: 'date', + interface: 'updatedAt', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'df0kmwka9ra', + uiSchema: { + 'x-uid': 'df0kmwka9ra', + name: 'e3hmz8upyle', + 'x-component-props': { dateFormat: 'YYYY-MM-DD', showTime: true, timeFormat: 'HH:mm:ss' }, + type: 'datetime', + title: '更新时间', + 'x-component': 'DatePicker', + 'x-read-pretty': true, + }, + field: 'updatedAt', + }, + { + key: 'tm66hbii6zz', + name: 'f_cht6rsiiiko', + type: 'string', + interface: 'select', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'tkfjlde4ppl', + uiSchema: { + 'x-uid': 'tkfjlde4ppl', + name: 'p0rccyurzle', + enum: [ + { value: 'uduxfqpr5t2', label: '菜单' }, + { value: 'dnphxkwat35', label: '权限' }, + { value: '5cso9lpizog', label: '数据表' }, + { value: 'wlg329nsu3a', label: '字段' }, + { value: 'm8z7r06vk9z', label: '表格' }, + { value: 'ftfccbha25a', label: '表单' }, + { value: 'o33ljfkje2h', label: '详情' }, + { value: 'ym9wwk3aqbu', label: '日历' }, + { value: 'wge57qtiz14', label: '看板' }, + { value: 'm4xoc8rbafg', label: '操作' }, + { value: 'r6l2wg1snxc', label: '配置' }, + { value: 'j1cv1vf16fm', label: '附件' }, + { value: 'l464jdsy7vr', label: '容器' }, + ], + type: 'string', + 'x-component': 'Select', + title: '分类', + }, + }, + { + key: 'c9hzonl1hzq', + name: 'f_47f2d9wgofm', + type: 'belongsToMany', + interface: 'linkTo', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: '7mufig9muag', + uiSchemaUid: 'rbl27ngs563', + uiSchema: { + 'x-uid': 'rbl27ngs563', + name: 'xrvwttiv064', + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'id', value: 'id' } }, + title: '相关任务', + }, + target: 't_j6omof6tza8', + targetKey: 'id', + sourceKey: 'id', + through: 't_qhgbwomd2t7', + foreignKey: 'f_jes256zqwr1', + otherKey: 'f_gtgjj059mye', + }, + { + key: '7mufig9muag', + name: 'f_qmlomqm7lvb', + type: 'belongsToMany', + interface: 'linkTo', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: 'c9hzonl1hzq', + uiSchemaUid: 'vgy9cgq9v6b', + uiSchema: { + 'x-uid': 'vgy9cgq9v6b', + name: 'sf99arn65jj', + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'id', value: 'id' } }, + title: '任务', + }, + target: 't_j6omof6tza8', + through: 't_qhgbwomd2t7', + sourceKey: 'id', + foreignKey: 'f_gtgjj059mye', + targetKey: 'id', + otherKey: 'f_jes256zqwr1', + }, + { + key: 'hv3kx2z8edn', + name: 'f_hpmvdltzs6m_sort', + type: 'sort', + interface: null, + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + hidden: true, + scopeKey: 'f_hpmvdltzs6m', + }, + { + key: 'q6wy6jcpdfr', + name: 'f_a4z4h45vi5b', + type: 'string', + interface: 'select', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'lx4k023vm7k', + uiSchema: { + 'x-uid': 'lx4k023vm7k', + name: 'e9u95lm0c2q', + enum: [ + { value: 'hgobsqbfnd4', label: 'SchemaInitializer', color: 'red' }, + { value: 't8prrpfk7kl', label: 'SchemaSettings', color: 'magenta' }, + { value: 'r3fxlrunwd8', label: 'SchemaComponent', color: 'lime' }, + { value: 'bidogljo0l3', label: 'CollectionField', color: 'blue' }, + { value: 'ygy1zfdr1z6', label: 'ACL', color: 'purple' }, + { value: 'ef9qf0wzpt5', label: 'DndContext', color: 'cyan' }, + { value: '45sfwd7ie30', label: 'useRequest', color: 'volcano' }, + ], + type: 'string', + 'x-component': 'Select', + title: '开发分类', + }, + }, + { + key: '936iis76zdx', + name: 'f_a4z4h45vi5b_sort', + type: 'sort', + interface: null, + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + hidden: true, + scopeKey: 'f_a4z4h45vi5b', + }, + { + key: 'zre21o0a5a9', + name: 'f_d93g4r08krl', + type: 'string', + interface: 'input', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: '96geljdh9j6', + uiSchema: { + 'x-uid': '96geljdh9j6', + name: 'zplxa37mb0y', + type: 'string', + 'x-component': 'Input', + title: '评估', + enum: [], + }, + }, + { + key: 'jasdf0kcqbs', + name: 'f_1ckuegfab9s', + type: 'text', + interface: 'textarea', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: '61h9pserdqr', + uiSchema: { + 'x-uid': '61h9pserdqr', + name: 'uepfpdv289y', + type: 'string', + 'x-component': 'Input.TextArea', + title: '评估备注', + }, + }, + ], + createdBy: true, + updatedBy: true, + sortable: true, + }, + { + key: 'nlxapox70a3', + name: 't_ab12qiwruwk', + title: null, + inherit: true, + fields: [ + { + key: 'q9xx9ixxxyb', + name: 'f_m7ibo1vrvnm', + type: 'string', + interface: 'input', + collectionName: 't_ab12qiwruwk', + parentKey: '9x87w5z0ea6', + reverseKey: null, + uiSchemaUid: 'ucn6wm03zrq', + uiSchema: { + 'x-uid': 'ucn6wm03zrq', + name: 'k07s9nhhj6c', + type: 'string', + 'x-component': 'Input', + title: '名称', + }, + }, + { + key: 'y91qig4eyzm', + name: 'f_kukaw9kddyj', + type: 'belongsToMany', + interface: 'linkTo', + collectionName: 't_ab12qiwruwk', + parentKey: '9x87w5z0ea6', + reverseKey: 'tvwwt24vukv', + uiSchemaUid: '7c9ch1fknbr', + uiSchema: { + 'x-uid': '7c9ch1fknbr', + name: 'gv1k2nhucte', + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: false, fieldNames: { label: 'id', value: 'id' } }, + title: '负责人', + }, + target: 'users', + targetKey: 'id', + sourceKey: 'id', + through: 't_gsv43y8ebt8', + foreignKey: 'f_hbbvlsbe9gs', + otherKey: 'f_bhj1xy7uyc8', + }, + { + key: 'gg0xx1vln6w', + name: 'f_4mpiovytw4d', + type: 'text', + interface: 'textarea', + collectionName: 't_ab12qiwruwk', + parentKey: '9x87w5z0ea6', + reverseKey: null, + uiSchemaUid: '3xgmf0ee5fn', + uiSchema: { + 'x-uid': '3xgmf0ee5fn', + name: '28sf67whpfs', + type: 'string', + 'x-component': 'Input.TextArea', + title: '描述', + }, + }, + { + key: 'zpz6o74z9u4', + name: 'f_lxsum89wkzd', + type: 'belongsToMany', + interface: 'attachment', + collectionName: 't_ab12qiwruwk', + parentKey: '9x87w5z0ea6', + reverseKey: null, + uiSchemaUid: 'wosew16td91', + uiSchema: { + 'x-uid': 'wosew16td91', + name: 'p82ihvtkxtf', + 'x-component-props': { multiple: true, action: 'attachments:upload' }, + type: 'array', + 'x-component': 'Upload.Attachment', + title: '附件', + }, + target: 'attachments', + targetKey: 'id', + sourceKey: 'id', + through: 't_cn6cweinuw7', + foreignKey: 'f_r824sp05l43', + otherKey: 'f_8k3e4i0d04y', + }, + ], + createdBy: true, + updatedBy: true, + sortable: true, + }, + { + key: 'oftbzt8nm2o', + name: 'users', + title: '{{t("Users")}}', + inherit: false, + fields: [ + { + key: 'khov1egnsur', + name: 'nickname', + type: 'string', + interface: 'input', + collectionName: 'users', + parentKey: null, + reverseKey: null, + uiSchemaUid: '3ohfk46bf7o', + uiSchema: { + 'x-uid': '3ohfk46bf7o', + name: 'prmmf3kg8rx', + type: 'string', + title: '{{t("Nickname")}}', + 'x-component': 'Input', + }, + }, + { + key: 'u6cvmn45o6a', + name: 'email', + type: 'string', + interface: 'email', + collectionName: 'users', + parentKey: null, + reverseKey: null, + uiSchemaUid: '69ywpwux1zh', + uiSchema: { + 'x-uid': '69ywpwux1zh', + name: 'u8djuprwttj', + type: 'string', + title: '{{t("Email")}}', + 'x-component': 'Input', + require: true, + }, + unique: true, + }, + { + key: 'jwaky9982f2', + name: 'password', + type: 'password', + interface: 'password', + collectionName: 'users', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'qs374baqkh0', + uiSchema: { + 'x-uid': 'qs374baqkh0', + name: 'tf5dolxh2i4', + type: 'string', + title: '{{t("Password")}}', + 'x-component': 'Password', + }, + hidden: true, + }, + { + key: '4g5nics20go', + name: 'roles', + type: 'belongsToMany', + interface: 'linkTo', + collectionName: 'users', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'lpkacakd4my', + uiSchema: { + 'x-uid': 'lpkacakd4my', + name: '3oylvillmwu', + type: 'array', + title: '{{t("Roles")}}', + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'title', value: 'name' } }, + }, + target: 'roles', + foreignKey: 'userId', + otherKey: 'roleName', + sourceKey: 'id', + targetKey: 'name', + through: 'rolesUsers', + }, + { + key: 'cqqhyq32tvf', + name: 'appLang', + type: 'string', + interface: null, + collectionName: 'users', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + }, + { + key: 't7t97s1rwid', + name: 'token', + type: 'string', + interface: null, + collectionName: 'users', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + unique: true, + hidden: true, + }, + { + key: '5myliungho1', + name: 'resetToken', + type: 'string', + interface: null, + collectionName: 'users', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + unique: true, + hidden: true, + }, + { + key: 'q2eg83quxls', + name: 'sort', + type: 'sort', + interface: null, + collectionName: 'users', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + hidden: true, + }, + { + key: 's5w3l6pl1hn', + name: 'f_xd4v2uljqcu', + type: 'belongsToMany', + interface: 'linkTo', + collectionName: 'users', + parentKey: null, + reverseKey: 'kwdiruosgsb', + uiSchemaUid: 'zv4gvihowwm', + uiSchema: { + 'x-uid': 'zv4gvihowwm', + name: 'mjjpnphkkhf', + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'id', value: 'id' } }, + title: '负责人', + }, + target: 't_j6omof6tza8', + through: 't_7476m1wwsfe', + sourceKey: 'id', + foreignKey: 'f_q84aphtx7eb', + targetKey: 'id', + otherKey: 'f_9zwkbo7dd18', + }, + { + key: 'tvwwt24vukv', + name: 'f_qpsqy5rtc8t', + type: 'belongsToMany', + interface: 'linkTo', + collectionName: 'users', + parentKey: null, + reverseKey: 'y91qig4eyzm', + uiSchemaUid: '5ksz03zw47c', + uiSchema: { + 'x-uid': '5ksz03zw47c', + name: '8ukjiawzduz', + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'id', value: 'id' } }, + title: '负责人', + }, + target: 't_ab12qiwruwk', + through: 't_gsv43y8ebt8', + sourceKey: 'id', + foreignKey: 'f_bhj1xy7uyc8', + targetKey: 'id', + otherKey: 'f_hbbvlsbe9gs', + }, + ], + sortable: 'sort', + }, + { + key: 'tyztw0ulriv', + name: 't_94rsj6kbzvn', + title: '迭代', + inherit: false, + fields: [ + { + key: 'nq4mp00jwip', + name: 'f_zio9ewkxss7', + type: 'string', + interface: 'input', + collectionName: 't_94rsj6kbzvn', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'q7vois7ptlt', + uiSchema: { + 'x-uid': 'q7vois7ptlt', + name: 'o9ue1s4tu8k', + type: 'string', + 'x-component': 'Input', + title: '名称', + enum: [], + }, + }, + { + key: 'ec2833af7n1', + name: 'f_ojboh2wxpju', + type: 'text', + interface: 'textarea', + collectionName: 't_94rsj6kbzvn', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'pco1f8sq2jq', + uiSchema: { + 'x-uid': 'pco1f8sq2jq', + name: 'lb2hdydextn', + type: 'string', + 'x-component': 'Input.TextArea', + title: '描述', + enum: [], + }, + }, + { + key: '82uqmdjr3v4', + name: 'f_nunmzapigvk', + type: 'date', + interface: 'datetime', + collectionName: 't_94rsj6kbzvn', + parentKey: null, + reverseKey: null, + uiSchemaUid: '2gawk8arctt', + uiSchema: { + 'x-uid': '2gawk8arctt', + name: 'j9j7c2wyev2', + 'x-component-props': { dateFormat: 'YYYY-MM-DD', showTime: false }, + type: 'datetime', + 'x-component': 'DatePicker', + title: '开始日期', + enum: [], + }, + }, + { + key: 'mdx2eqz9ei8', + name: 'f_rberbnphu9u', + type: 'date', + interface: 'datetime', + collectionName: 't_94rsj6kbzvn', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'pj0znpff63y', + uiSchema: { + 'x-uid': 'pj0znpff63y', + name: 'c5h7agr2qy4', + 'x-component-props': { dateFormat: 'YYYY-MM-DD', showTime: false }, + type: 'datetime', + 'x-component': 'DatePicker', + title: '结束日期', + enum: [], + }, + }, + { + key: 'z70jtfabrm7', + name: 'f_rabhmdetc3p', + type: 'belongsToMany', + interface: 'linkTo', + collectionName: 't_94rsj6kbzvn', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'aun2xsfua8d', + uiSchema: { + 'x-uid': 'aun2xsfua8d', + name: 'i508c1outz2', + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'id', value: 'id' } }, + title: '任务', + enum: [], + }, + target: 't_j6omof6tza8', + through: 't_ewdf2dsjlu3', + sourceKey: 'id', + foreignKey: 'f_8do8o1wsavk', + targetKey: 'id', + otherKey: 'f_72tnpdz6kfg', + }, + ], + createdBy: true, + updatedBy: true, + sortable: true, + }, + { + key: 'vv8umfa9592', + name: 'test_sheet', + title: '测试表', + inherit: false, + fields: [ + { + key: 'q1xye1ooeol', + name: 'f_7mh6k6re0ey', + type: 'string', + interface: 'phone', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 't9ddc5n5pv8', + uiSchema: { + 'x-uid': 't9ddc5n5pv8', + name: 'd3hwsg020u8', + type: 'string', + 'x-component': 'Input', + 'x-validator': 'phone', + title: '手机', + }, + }, + { + key: '25icwn35oin', + name: 'f_turkjlzi52z', + type: 'string', + interface: 'email', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: '3bkgd97f63v', + uiSchema: { + 'x-uid': '3bkgd97f63v', + name: 'zkvhu5cm4no', + type: 'string', + 'x-component': 'Input', + 'x-validator': 'email', + title: '邮箱', + }, + }, + { + key: '3cqe0mpcrmg', + name: 'f_c14whhblhtd', + type: 'float', + interface: 'number', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: '4bajzziz060', + uiSchema: { + 'x-uid': '4bajzziz060', + name: '366t9rvl34b', + 'x-component-props': { step: '0.01', stringMode: true }, + type: 'number', + 'x-component': 'InputNumber', + title: '数字', + }, + }, + { + key: 'y39vi1z69pw', + name: 'f_z1b674d907e', + type: 'float', + interface: 'percent', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'vbnoib14uzl', + uiSchema: { + 'x-uid': 'vbnoib14uzl', + name: 'rumohzj4lh6', + 'x-component-props': { step: '0.1', stringMode: true, addonAfter: '%' }, + type: 'string', + 'x-component': 'InputNumber', + title: '百分比', + }, + }, + { + key: 'eouuwoag6ka', + name: 'f_4tzp4nhmvt2', + type: 'string', + interface: 'icon', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'qmmw3zc8l60', + uiSchema: { + 'x-uid': 'qmmw3zc8l60', + name: 'aivg5c51gc9', + type: 'string', + 'x-component': 'IconPicker', + title: '图标', + }, + }, + { + key: 'qurfmfe2hfi', + name: 'f_z0s4m3hgadd', + type: 'password', + interface: 'password', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'tkp46kn682m', + uiSchema: { + 'x-uid': 'tkp46kn682m', + name: '7q7yeobot0j', + type: 'string', + 'x-component': 'Password', + title: '密码', + }, + }, + { + key: 'ka4k5tfh3k5', + name: 'f_q5qnozezx9s', + type: 'boolean', + interface: 'checkbox', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'wgyf4ozsvdi', + uiSchema: { + 'x-uid': 'wgyf4ozsvdi', + name: 'x6agce8s18w', + type: 'boolean', + 'x-component': 'Checkbox', + title: '勾选', + }, + }, + { + key: '3dhczyw3ye2', + name: 'f_jbzwvldpyaa', + type: 'array', + interface: 'multipleSelect', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'hlmfjs2pu9t', + uiSchema: { + 'x-uid': 'hlmfjs2pu9t', + name: 'ao52nx5pune', + enum: [ + { value: '6mbqdwuzfca', label: '选项1', color: 'red' }, + { value: '9zi6s2fbj0q', label: '选项2', color: 'geekblue' }, + ], + type: 'array', + 'x-component': 'Select', + 'x-component-props': { mode: 'multiple' }, + title: '多选', + }, + defaultValue: [], + }, + { + key: 'erdrp1gi587', + name: 'f_04krym08wxq', + type: 'array', + interface: 'checkboxGroup', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: '6has8wyzohq', + uiSchema: { + 'x-uid': '6has8wyzohq', + name: 'fvwsjt58xx4', + enum: [ + { value: '4lsgdvfl7xq', label: '复选2', color: 'cyan' }, + { value: 'qzuki8bmilg', label: '复选1', color: 'purple' }, + ], + type: 'string', + 'x-component': 'Checkbox.Group', + title: '复选', + }, + defaultValue: [], + }, + { + key: 'rykbub69xu7', + name: 'f_xclbl3htmxs', + type: 'belongsToMany', + interface: 'chinaRegion', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'yml5yqzgvn8', + uiSchema: { + 'x-uid': 'yml5yqzgvn8', + name: '2ye7y09agup', + 'x-component-props': { + maxLevel: 5, + useDataSource: '{{ useChinaRegionDataSource }}', + useLoadData: '{{ useChinaRegionLoadData }}', + changeOnSelectLast: true, + labelInValue: true, + fieldNames: { label: 'name', value: 'code', children: 'children' }, + }, + type: 'array', + 'x-component': 'Cascader', + title: '中国地区', + }, + target: 'chinaRegions', + targetKey: 'code', + sortBy: 'level', + sourceKey: 'id', + through: 't_o9ry49u6xez', + foreignKey: 'f_44r8nsvdvz7', + otherKey: 'f_l0tzoymin3i', + }, + { + key: 'acm789iz922', + name: 'f_v4cgx7l5hwm', + type: 'text', + interface: 'markdown', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: '8a2erdhwcvb', + uiSchema: { + 'x-uid': '8a2erdhwcvb', + name: 'rqeiz26vuns', + type: 'string', + 'x-component': 'Markdown', + title: 'Markdown', + }, + }, + { + key: 'zmip3swglzv', + name: 'f_ztaop411cdw', + type: 'time', + interface: 'time', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'r6s6rrjzfml', + uiSchema: { + 'x-uid': 'r6s6rrjzfml', + name: 'ct893ay5apv', + 'x-component-props': { format: 'HH:mm:ss' }, + type: 'string', + 'x-component': 'TimePicker', + title: '时间', + }, + }, + { + key: 'nx1p69b6g96', + name: 'f_mf8ccm0pjmh', + type: 'date', + interface: 'datetime', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: '08p9oztyjf2', + uiSchema: { + 'x-uid': '08p9oztyjf2', + name: 'bkdnndmfpeo', + 'x-component-props': { dateFormat: 'DD/MM/YYYY', showTime: true, timeFormat: 'HH:mm:ss' }, + type: 'datetime', + 'x-component': 'DatePicker', + title: '日期', + enum: [], + }, + }, + { + key: 'ge57kma4gue', + name: 'f_dqq0xjkdqbo', + type: 'belongsTo', + interface: 'createdBy', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'zzmst1r01gc', + uiSchema: { + 'x-uid': 'zzmst1r01gc', + name: 'def1ngennlx', + type: 'object', + title: '创建人', + 'x-component': 'RecordPicker', + 'x-component-props': { fieldNames: { value: 'id', label: 'nickname' } }, + 'x-read-pretty': true, + }, + target: 'users', + foreignKey: 'createdById', + targetKey: 'id', + }, + { + key: 'yqye22n1f1c', + name: 'f_7ukoar9jevs', + type: 'date', + interface: 'createdAt', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'lgnqb28lb91', + uiSchema: { + 'x-uid': 'lgnqb28lb91', + name: 'jdjz5cngm3j', + 'x-component-props': { dateFormat: 'YYYY-MM-DD', showTime: true, timeFormat: 'HH:mm:ss' }, + type: 'datetime', + title: '创建日期', + 'x-component': 'DatePicker', + 'x-read-pretty': true, + }, + field: 'createdAt', + }, + { + key: 'n4ap9l1l4wd', + name: 'f_81orjgxlbuo', + type: 'date', + interface: 'updatedAt', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'z19egimonh9', + uiSchema: { + 'x-uid': 'z19egimonh9', + name: 'xtzu6cpnsm4', + 'x-component-props': { dateFormat: 'YYYY-MM-DD', showTime: true }, + type: 'datetime', + title: '修改日期', + 'x-component': 'DatePicker', + 'x-read-pretty': true, + }, + field: 'updatedAt', + }, + { + key: 'oqy67fm6af8', + name: 'f_e31b9oqe9ej', + type: 'belongsTo', + interface: 'updatedBy', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'tm4f7z0nyso', + uiSchema: { + 'x-uid': 'tm4f7z0nyso', + name: '19srjtwyce1', + type: 'object', + title: '修改人', + 'x-component': 'RecordPicker', + 'x-component-props': { fieldNames: { value: 'id', label: 'nickname' } }, + 'x-read-pretty': true, + }, + target: 'users', + foreignKey: 'updatedById', + targetKey: 'id', + }, + ], + createdBy: true, + updatedBy: true, + sortable: true, + }, + ], +}; diff --git a/packages/client/src/schema-component/antd/calendar-v2/demos/data.ts b/packages/client/src/schema-component/antd/calendar-v2/demos/data.ts new file mode 100644 index 000000000..51a0deb0a --- /dev/null +++ b/packages/client/src/schema-component/antd/calendar-v2/demos/data.ts @@ -0,0 +1,131 @@ +export default { + data: [ + { + id: 4, + createdAt: '2022-03-06T07:22:21.163Z', + updatedAt: '2022-03-12T04:34:19.147Z', + createdById: 1, + updatedById: 1, + f_1ckuegfab9s: null, + f_a4z4h45vi5b: 'hgobsqbfnd4', + f_cht6rsiiiko: 'ftfccbha25a', + f_d93g4r08krl: '1', + f_f7txg1oc3nt: '2022-03-07T07:22:21.163Z', + f_g8j5jvalqh0: '子表格字段缺少标题', + f_hpmvdltzs6m: null, + f_tegyd222bcc: '见附件,在此位置显示标题,包括表单和详情', + f_yc8jbfiqfvh: 'v25527dxseu', + f_z27302tl2bf: '9yyv2tawmry', + f_zek99qhv0vc: '2022-03-12T04:34:19.147Z', + }, + { + id: 5, + createdAt: '2022-03-07T11:10:50.869Z', + updatedAt: '2022-03-12T04:34:37.792Z', + createdById: 1, + updatedById: 1, + f_1ckuegfab9s: null, + f_a4z4h45vi5b: 'r3fxlrunwd8', + f_cht6rsiiiko: 'ftfccbha25a', + f_d93g4r08krl: '2', + f_f7txg1oc3nt: '2022-03-07T11:10:50.869Z', + f_g8j5jvalqh0: '抽屉关闭时,如果内容已更改未保存,弹出提示', + f_hpmvdltzs6m: '67snt3e6yld', + f_tegyd222bcc: null, + f_yc8jbfiqfvh: 'c4hobfb5k07', + f_z27302tl2bf: 'mkvgt7ndsqd', + f_zek99qhv0vc: '2022-03-12T04:34:37.792Z', + }, + { + id: 6, + createdAt: '2022-03-07T11:12:49.853Z', + updatedAt: '2022-03-11T15:37:00.663Z', + createdById: 1, + updatedById: 1, + f_1ckuegfab9s: null, + f_a4z4h45vi5b: 't8prrpfk7kl', + f_cht6rsiiiko: 'ftfccbha25a', + f_d93g4r08krl: null, + f_f7txg1oc3nt: '2022-03-07T11:12:49.853Z', + f_g8j5jvalqh0: '字段设置必填后,未验证必填', + f_hpmvdltzs6m: 'irepvdzp1ac', + f_tegyd222bcc: null, + f_yc8jbfiqfvh: 'v25527dxseu', + f_z27302tl2bf: 'mkvgt7ndsqd', + f_zek99qhv0vc: '2022-03-11T15:37:00.663Z', + }, + { + id: 7, + createdAt: '2022-03-07T11:13:45.123Z', + updatedAt: '2022-03-10T16:02:25.805Z', + createdById: 1, + updatedById: 1, + f_1ckuegfab9s: null, + f_a4z4h45vi5b: null, + f_cht6rsiiiko: null, + f_d93g4r08krl: null, + f_f7txg1oc3nt: '2022-03-07T11:13:45.123Z', + f_g8j5jvalqh0: '菜单项移动功能', + f_hpmvdltzs6m: 'irepvdzp1ac', + f_tegyd222bcc: '同上一个版本', + f_yc8jbfiqfvh: 's7hocw6twfk', + f_z27302tl2bf: 'mkvgt7ndsqd', + f_zek99qhv0vc: '2022-03-10T16:02:25.805Z', + }, + { + id: 8, + createdAt: '2022-03-07T11:16:15.876Z', + updatedAt: '2022-03-10T16:02:25.805Z', + createdById: 1, + updatedById: 1, + f_1ckuegfab9s: null, + f_a4z4h45vi5b: null, + f_cht6rsiiiko: null, + f_d93g4r08krl: null, + f_f7txg1oc3nt: '2022-03-07T11:16:15.876Z', + f_g8j5jvalqh0: '附件样式', + f_hpmvdltzs6m: 'irepvdzp1ac', + f_tegyd222bcc: + '修改后,有附件的行跟没附件的行的高度还是不一样\n\n1. 表格里显示更小的缩略图,同之前版本\n2. 还有子表格里\n3. 鼠标移上去之后的图标位置\n\n备注:行高不一的未解决,以后统一解决,需要给每个 td 加上字段类型的 className 方便调整不同字段的样式问题', + f_yc8jbfiqfvh: 's7hocw6twfk', + f_z27302tl2bf: '9yyv2tawmry', + f_zek99qhv0vc: '2022-03-10T16:02:25.805Z', + }, + { + id: 9, + createdAt: '2022-03-07T11:17:11.423Z', + updatedAt: '2022-03-12T06:11:09.330Z', + createdById: 1, + updatedById: 1, + f_1ckuegfab9s: null, + f_a4z4h45vi5b: null, + f_cht6rsiiiko: null, + f_d93g4r08krl: null, + f_f7txg1oc3nt: '2022-03-07T11:17:11.423Z', + f_g8j5jvalqh0: '日志', + f_hpmvdltzs6m: 'ht963n365al', + f_tegyd222bcc: '同原来版本的日志功能。\n- ActionLogBlockInitializer;\n- ActionLog.Designer;', + f_yc8jbfiqfvh: 'v25527dxseu', + f_z27302tl2bf: 'mkvgt7ndsqd', + f_zek99qhv0vc: '2022-03-12T06:11:09.330Z', + }, + { + id: 10, + createdAt: '2022-03-07T11:18:40.719Z', + updatedAt: '2022-03-12T04:24:31.402Z', + createdById: 1, + updatedById: 1, + f_1ckuegfab9s: null, + f_a4z4h45vi5b: null, + f_cht6rsiiiko: 'r6l2wg1snxc', + f_d93g4r08krl: null, + f_f7txg1oc3nt: '2022-03-07T11:18:40.719Z', + f_g8j5jvalqh0: '配置图标没有居中', + f_hpmvdltzs6m: 'irepvdzp1ac', + f_tegyd222bcc: null, + f_yc8jbfiqfvh: 'v25527dxseu', + f_z27302tl2bf: '9yyv2tawmry', + f_zek99qhv0vc: '2022-03-12T04:24:31.402Z', + }, + ], +}; diff --git a/packages/client/src/schema-component/antd/calendar-v2/demos/defaultValues.ts b/packages/client/src/schema-component/antd/calendar-v2/demos/defaultValues.ts new file mode 100644 index 000000000..ab3a2f736 --- /dev/null +++ b/packages/client/src/schema-component/antd/calendar-v2/demos/defaultValues.ts @@ -0,0 +1,180 @@ +const now = new Date(); + +export default [ + { + id: 0, + title: 'All Day Event very long title', + allDay: true, + start: new Date(2015, 3, 0), + end: new Date(2015, 3, 1), + }, + { + id: 1, + title: 'Long Event', + start: new Date(2015, 3, 7), + end: new Date(2015, 3, 10), + }, + + { + id: 2, + title: 'DTS STARTS', + start: new Date(2016, 2, 13, 0, 0, 0), + end: new Date(2016, 2, 20, 0, 0, 0), + }, + + { + id: 3, + title: 'DTS ENDS', + start: new Date(2016, 10, 6, 0, 0, 0), + end: new Date(2016, 10, 13, 0, 0, 0), + }, + + { + id: 4, + title: 'Some Event', + start: new Date(2015, 3, 9, 0, 0, 0), + end: new Date(2015, 3, 10, 0, 0, 0), + }, + { + id: 5, + title: 'Conference', + start: new Date(2015, 3, 11), + end: new Date(2015, 3, 13), + desc: 'Big conference for important people', + }, + { + id: 6, + title: 'Meeting', + start: new Date(2015, 3, 12, 10, 30, 0, 0), + end: new Date(2015, 3, 12, 12, 30, 0, 0), + desc: 'Pre-meeting meeting, to prepare for the meeting', + }, + { + id: 7, + title: 'Lunch', + start: new Date(2015, 3, 12, 12, 0, 0, 0), + end: new Date(2015, 3, 12, 13, 0, 0, 0), + desc: 'Power lunch', + }, + { + id: 8, + title: 'Meeting', + start: new Date(2015, 3, 12, 14, 0, 0, 0), + end: new Date(2015, 3, 12, 15, 0, 0, 0), + }, + { + id: 9, + title: 'Happy Hour', + start: new Date(2015, 3, 12, 17, 0, 0, 0), + end: new Date(2015, 3, 12, 17, 30, 0, 0), + desc: 'Most important meal of the day', + }, + { + id: 10, + title: 'Dinner', + start: new Date(2015, 3, 12, 20, 0, 0, 0), + end: new Date(2015, 3, 12, 21, 0, 0, 0), + }, + { + id: 11, + title: 'Planning Meeting with Paige', + start: new Date(2015, 3, 13, 8, 0, 0), + end: new Date(2015, 3, 13, 10, 30, 0), + }, + { + id: 11.1, + title: 'Inconvenient Conference Call', + start: new Date(2015, 3, 13, 9, 30, 0), + end: new Date(2015, 3, 13, 12, 0, 0), + }, + { + id: 11.2, + title: "Project Kickoff - Lou's Shoes", + start: new Date(2015, 3, 13, 11, 30, 0), + end: new Date(2015, 3, 13, 14, 0, 0), + }, + { + id: 11.3, + title: 'Quote Follow-up - Tea by Tina', + start: new Date(2015, 3, 13, 15, 30, 0), + end: new Date(2015, 3, 13, 16, 0, 0), + }, + { + id: 12, + title: 'Late Night Event', + start: new Date(2015, 3, 17, 19, 30, 0), + end: new Date(2015, 3, 18, 2, 0, 0), + }, + { + id: 12.5, + title: 'Late Same Night Event', + start: new Date(2015, 3, 17, 19, 30, 0), + end: new Date(2015, 3, 17, 23, 30, 0), + }, + { + id: 13, + title: 'Multi-day Event', + start: new Date(2015, 3, 20, 19, 30, 0), + end: new Date(2015, 3, 22, 2, 0, 0), + }, + { + id: 14, + title: 'Today', + start: new Date(new Date().setHours(new Date().getHours() - 3)), + end: new Date(new Date().setHours(new Date().getHours() + 3)), + }, + { + id: 15, + title: 'Point in Time Event', + start: now, + end: now, + }, + { + id: 16, + title: 'Video Record', + start: new Date(2015, 3, 14, 15, 30, 0), + end: new Date(2015, 3, 14, 19, 0, 0), + }, + { + id: 17, + title: 'Dutch Song Producing', + start: new Date(2015, 3, 14, 16, 30, 0), + end: new Date(2015, 3, 14, 20, 0, 0), + }, + { + id: 18, + title: 'Itaewon Halloween Meeting', + start: new Date(2015, 3, 14, 16, 30, 0), + end: new Date(2015, 3, 14, 17, 30, 0), + }, + { + id: 19, + title: 'Online Coding Test', + start: new Date(2015, 3, 14, 17, 30, 0), + end: new Date(2015, 3, 14, 20, 30, 0), + }, + { + id: 20, + title: 'An overlapped Event', + start: new Date(2015, 3, 14, 17, 0, 0), + end: new Date(2015, 3, 14, 18, 30, 0), + }, + { + id: 21, + title: 'Phone Interview', + start: new Date(2015, 3, 14, 17, 0, 0), + end: new Date(2015, 3, 14, 18, 30, 0), + }, + { + id: 22, + title: 'Cooking Class', + start: new Date(2015, 3, 14, 17, 30, 0), + end: new Date(2015, 3, 14, 19, 0, 0), + }, + { + id: 23, + title: 'Go to the gym', + start: new Date(2015, 3, 14, 18, 30, 0), + end: new Date(2015, 3, 14, 20, 0, 0), + }, +]; diff --git a/packages/client/src/schema-component/antd/calendar-v2/demos/demo1.tsx b/packages/client/src/schema-component/antd/calendar-v2/demos/demo1.tsx new file mode 100644 index 000000000..dd55494fd --- /dev/null +++ b/packages/client/src/schema-component/antd/calendar-v2/demos/demo1.tsx @@ -0,0 +1,67 @@ +/** + * title: Calendar + */ +import { + AntdSchemaComponentProvider, + SchemaComponent, + SchemaComponentProvider, + SchemaInitializerProvider +} from '@nocobase/client'; +import React from 'react'; +import defaultValues from './defaultValues'; + +const schema = { + type: 'array', + name: 'calendar1', + 'x-component': 'CalendarV2', + 'x-component-props': {}, + default: defaultValues, + properties: { + toolBar: { + type: 'void', + 'x-component': 'CalendarV2.ActionBar', + properties: { + today: { + type: 'void', + title: '今天', + 'x-component': 'CalendarV2.Today', + 'x-action': 'calendar:today', + 'x-align': 'left', + }, + nav: { + type: 'void', + title: '翻页', + 'x-component': 'CalendarV2.Nav', + 'x-action': 'calendar:nav', + 'x-align': 'left', + }, + title: { + type: 'void', + title: '标题', + 'x-component': 'CalendarV2.Title', + 'x-action': 'calendar:title', + 'x-align': 'left', + }, + viewSelect: { + type: 'void', + title: '视图切换', + 'x-component': 'CalendarV2.ViewSelect', + 'x-action': 'calendar:viewSelect', + 'x-align': 'right', + }, + }, + }, + }, +}; + +export default () => { + return ( + + + + + + + + ); +}; diff --git a/packages/client/src/schema-component/antd/calendar-v2/demos/demo2.tsx b/packages/client/src/schema-component/antd/calendar-v2/demos/demo2.tsx new file mode 100644 index 000000000..3f8acbc67 --- /dev/null +++ b/packages/client/src/schema-component/antd/calendar-v2/demos/demo2.tsx @@ -0,0 +1,117 @@ +import { ISchema } from '@formily/react'; +import { + AntdSchemaComponentProvider, + APIClient, + APIClientProvider, + BlockSchemaComponentProvider, + CollectionManagerProvider, + SchemaComponent, + SchemaComponentProvider +} from '@nocobase/client'; +import MockAdapter from 'axios-mock-adapter'; +import React from 'react'; +import collections from './collections'; +import data from './data'; + +const schema: ISchema = { + type: 'object', + properties: { + block: { + type: 'void', + 'x-decorator': 'CalendarBlockProvider', + 'x-decorator-props': { + collection: 't_j6omof6tza8', + resource: 't_j6omof6tza8', + action: 'list', + fieldNames: { + id: 'id', + start: 'createdAt', + end: 'createdAt', + title: 'f_g8j5jvalqh0', + }, + params: { + paginate: false, + }, + }, + properties: { + calendar: { + type: 'array', + name: 'calendar1', + 'x-component': 'CalendarV2', + 'x-component-props': { + useProps: '{{ useCalendarBlockProps }}', + }, + properties: { + toolBar: { + type: 'void', + 'x-component': 'CalendarV2.ActionBar', + properties: { + today: { + type: 'void', + title: '今天', + 'x-component': 'CalendarV2.Today', + 'x-action': 'calendar:today', + 'x-align': 'left', + }, + nav: { + type: 'void', + title: '翻页', + 'x-component': 'CalendarV2.Nav', + 'x-action': 'calendar:nav', + 'x-align': 'left', + }, + title: { + type: 'void', + title: '标题', + 'x-component': 'CalendarV2.Title', + 'x-action': 'calendar:title', + 'x-align': 'left', + }, + viewSelect: { + type: 'void', + title: '视图切换', + 'x-component': 'CalendarV2.ViewSelect', + 'x-action': 'calendar:viewSelect', + 'x-align': 'right', + }, + }, + }, + }, + }, + }, + }, + }, +}; + +const apiClient = new APIClient({ + baseURL: 'http://localhost:3000/api', +}); + +const sleep = (value: number) => new Promise((resolve) => setTimeout(resolve, value)); + +const mock = (api: APIClient) => { + const mock = new MockAdapter(api.axios); + + mock.onGet('/t_j6omof6tza8:list').reply(async (config) => { + await sleep(2000); + return [200, data]; + }); +}; + +mock(apiClient); + +export default () => { + return ( + + + + + + + + + + + + ); +}; diff --git a/packages/client/src/schema-component/antd/calendar-v2/index.md b/packages/client/src/schema-component/antd/calendar-v2/index.md new file mode 100644 index 000000000..38268c37b --- /dev/null +++ b/packages/client/src/schema-component/antd/calendar-v2/index.md @@ -0,0 +1,18 @@ +--- +nav: + path: /client +group: + path: /schema-components +--- + +# CalendarV2 + +## Examples + +#### Basic + + + +### CalendarBlockProvider + + diff --git a/packages/client/src/schema-component/antd/calendar-v2/index.ts b/packages/client/src/schema-component/antd/calendar-v2/index.ts new file mode 100644 index 000000000..298ac64dc --- /dev/null +++ b/packages/client/src/schema-component/antd/calendar-v2/index.ts @@ -0,0 +1,19 @@ +import { ActionBar } from '../action'; +import { Calendar as CalendarV2 } from './Calendar'; +import { CalendarDesigner } from './Calendar.Designer'; +import { Event } from './Event'; +import { Nav } from './Nav'; +import './style.less'; +import { Title } from './Title'; +import { Today } from './Today'; +import { ViewSelect } from './ViewSelect'; + +CalendarV2.ActionBar = ActionBar; +CalendarV2.Event = Event; +CalendarV2.Title = Title; +CalendarV2.Today = Today; +CalendarV2.Nav = Nav; +CalendarV2.ViewSelect = ViewSelect; +CalendarV2.Designer = CalendarDesigner; + +export { CalendarV2 }; diff --git a/packages/client/src/schema-component/antd/calendar-v2/style.less b/packages/client/src/schema-component/antd/calendar-v2/style.less new file mode 100644 index 000000000..aefe3d99d --- /dev/null +++ b/packages/client/src/schema-component/antd/calendar-v2/style.less @@ -0,0 +1,767 @@ +@charset "UTF-8"; +.rbc-btn { + color: inherit; + font: inherit; + margin: 0; +} + +button.rbc-btn { + overflow: visible; + text-transform: none; + -webkit-appearance: button; + cursor: pointer; +} + +button[disabled].rbc-btn { + cursor: not-allowed; +} + +button.rbc-input::-moz-focus-inner { + border: 0; + padding: 0; +} + +.rbc-calendar { + box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: stretch; + height: 100%; +} + +.rbc-calendar *, +.rbc-calendar *:before, +.rbc-calendar *:after { + box-sizing: inherit; +} + +.rbc-abs-full, +.rbc-row-bg { + overflow: hidden; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +.rbc-ellipsis, +.rbc-event-label, +.rbc-row-segment .rbc-event-content, +.rbc-show-more { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.rbc-rtl { + direction: rtl; +} + +.rbc-off-range { + color: rgba(0, 0, 0, 0.25); +} + +.rbc-off-range-bg { + // background: #e6e6e6; +} + +.rbc-header { + // font-size: 16px; + overflow: hidden; + flex: 1 0 0%; + text-overflow: ellipsis; + white-space: nowrap; + padding: 4px 12px; + text-align: right; + vertical-align: middle; + // font-weight: 500; + min-height: 32px; + color: rgba(0, 0, 0, 0.85); + margin: 0 4px; + border-bottom: 2px solid #f0f0f0; +} +.rbc-header + .rbc-header { + // border-left: 1px solid #f0f0f0; +} +.rbc-rtl .rbc-header + .rbc-header { + border-left-width: 0; + border-right: 1px solid #f0f0f0; +} +.rbc-header > a, +.rbc-header > a:active, +.rbc-header > a:visited { + color: inherit; + text-decoration: none; +} + +.rbc-row-content { + position: relative; + user-select: none; + -webkit-user-select: none; + z-index: 4; +} + +.rbc-row-content-scrollable { + display: flex; + flex-direction: column; + height: 100%; +} +.rbc-row-content-scrollable .rbc-row-content-scroll-container { + height: 100%; + overflow-y: scroll; + /* Hide scrollbar for Chrome, Safari and Opera */ + -ms-overflow-style: none; + /* IE and Edge */ + scrollbar-width: none; + /* Firefox */ +} +.rbc-row-content-scrollable .rbc-row-content-scroll-container::-webkit-scrollbar { + display: none; +} + +.rbc-toolbar { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; + margin-bottom: 10px; + font-size: 16px; +} +.rbc-toolbar .rbc-toolbar-label { + flex-grow: 1; + padding: 0 10px; + text-align: center; +} +.rbc-toolbar button { + outline: none; + font-size: 14px; + line-height: 1.5715; + height: 32px; + color: #373a3c; + display: inline-block; + margin: 0; + position: relative; + z-index: 1; + text-align: center; + vertical-align: middle; + background: none; + background-image: none; + border: 1px solid #d9d9d9; + padding: 4px 15px; + border-radius: 2px; + line-height: normal; + white-space: nowrap; +} +.rbc-toolbar button:active, +.rbc-toolbar button.rbc-active { + // background-image: none; + // box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + // background-color: #e6e6e6; + z-index: 2; + color: #1890ff; + border-color: #1890ff; +} +.rbc-toolbar button:active:hover, +.rbc-toolbar button:active:focus, +.rbc-toolbar button.rbc-active:hover, +.rbc-toolbar button.rbc-active:focus { + // color: #373a3c; + // background-color: #d4d4d4; + // border-color: #8c8c8c; + z-index: 2; + color: #40a9ff; + border-color: #40a9ff; +} +// .rbc-toolbar button:focus { +// color: #373a3c; +// background-color: #e6e6e6; +// border-color: #adadad; } +.rbc-toolbar button:hover { + // color: #373a3c; + // background-color: #e6e6e6; + z-index: 2; + color: #40a9ff; + border-color: #40a9ff; +} + +.rbc-btn-group { + display: inline-block; + white-space: nowrap; +} +.rbc-btn-group > button:first-child:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.rbc-btn-group > button:last-child:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.rbc-rtl .rbc-btn-group > button:first-child:not(:last-child) { + border-radius: 4px; + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.rbc-rtl .rbc-btn-group > button:last-child:not(:first-child) { + border-radius: 4px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.rbc-btn-group > button:not(:first-child):not(:last-child) { + border-radius: 0; +} +.rbc-btn-group button + button { + margin-left: -1px; +} +.rbc-rtl .rbc-btn-group button + button { + margin-left: 0; + margin-right: -1px; +} +.rbc-btn-group + .rbc-btn-group, +.rbc-btn-group + button { + margin-left: 10px; +} + +.rbc-event { + border: none; + box-sizing: border-box; + box-shadow: none; + margin: 0; + padding: 2px 5px; + background-color: rgba(240, 240, 240, 0.65); + border-radius: 2px; + // color: #1890ff; + cursor: pointer; + font-size: 12px; + width: 100%; + text-align: left; + &:hover { + background-color: #e6f7ff; + color: #1890ff; + } +} +.rbc-slot-selecting .rbc-event { + cursor: inherit; + pointer-events: none; +} +.rbc-event.rbc-selected { + background-color: #e6f7ff; + color: #1890ff; +} +.rbc-event:focus { + // outline: 5px auto #3b99fc; +} + +.rbc-event-label { + font-size: 80%; +} + +.rbc-event-overlaps { + box-shadow: -1px 1px 5px 0px rgba(51, 51, 51, 0.5); +} + +.rbc-event-continues-prior { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.rbc-event-continues-after { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.rbc-event-continues-earlier { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.rbc-event-continues-later { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +.rbc-row { + display: flex; + flex-direction: row; +} + +.rbc-row-segment { + padding: 0 4px 1px 4px; +} + +.rbc-selected-cell { + background-color: rgba(0, 0, 0, 0.1); +} + +.rbc-show-more { + z-index: 4; + font-weight: bold; + font-size: 85%; + height: auto; + line-height: normal; + color: inherit; + padding: 2px 5px; +} + +.rbc-month-view { + position: relative; + // border: 1px solid #f0f0f0; + display: flex; + flex-direction: column; + flex: 1 0 0; + width: 100%; + user-select: none; + -webkit-user-select: none; + height: 68vh; +} + +.rbc-month-header { + display: flex; + flex-direction: row; +} + +.rbc-month-row { + display: flex; + position: relative; + flex-direction: column; + flex: 1 0 0; + flex-basis: 0px; + overflow: hidden; + height: 100%; +} +.rbc-month-row + .rbc-month-row { + // border-top: 1px solid #f0f0f0; +} + +.rbc-date-cell { + // cursor: pointer; + flex: 1 1 0; + min-width: 0; + padding-right: 12px; + padding-top: 4px; + text-align: right; +} +.rbc-date-cell.rbc-now a { + // font-weight: bold; + color: #1890ff; +} +.rbc-date-cell > a, +.rbc-date-cell > a:active, +.rbc-date-cell > a:visited { + color: inherit; + text-decoration: none; +} +.rbc-date-cell > a:hover { + color: #1890ff; +} + +.rbc-row-bg { + display: flex; + flex-direction: row; + flex: 1 0 0; + overflow: hidden; +} + +.rbc-day-bg { + flex: 1 0 0%; + // border-top: 2px solid #f0f0f0; + margin: 0 4px; + &:hover { + background: #f5f5f5; + } + // cursor: pointer; +} + +.rbc-month-view { + .rbc-day-bg { + border-top: 2px solid #f0f0f0; + } + .rbc-today { + border-color: #1890ff !important; + background-color: #e6f7ff !important; + } + .rbc-header { + border-bottom: 0 !important; + } +} + +.rbc-day-bg + .rbc-day-bg { + // margin-left: 8px; + // border-left: 1px solid #f0f0f0; +} +.rbc-rtl .rbc-day-bg + .rbc-day-bg { + // border-left-width: 0; + // border-right: 1px solid #f0f0f0; +} + +.rbc-overlay { + position: absolute; + z-index: 5; + margin-top: 5px; + border-radius: 2px; + // border: 1px solid #e5e5e5; + background-color: #fff; + box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); + padding: 12px 16px; +} +.rbc-overlay > * + * { + margin-top: 1px; +} + +.rbc-overlay-header { + font-weight: 500; + min-height: 32px; + border-bottom: 1px solid #f0f0f0; + margin: -12px -16px 12px -16px; + padding: 5px 16px 4px; +} + +.rbc-agenda-view { + display: flex; + flex-direction: column; + flex: 1 0 0; + overflow: auto; +} +.rbc-agenda-view table.rbc-agenda-table { + width: 100%; + border: 1px solid #f0f0f0; + border-spacing: 0; + border-collapse: collapse; +} +.rbc-agenda-view table.rbc-agenda-table tbody > tr > td { + padding: 5px 10px; + vertical-align: top; +} +.rbc-agenda-view table.rbc-agenda-table .rbc-agenda-time-cell { + padding-left: 15px; + padding-right: 15px; + text-transform: lowercase; +} +.rbc-agenda-view table.rbc-agenda-table tbody > tr > td + td { + border-left: 1px solid #f0f0f0; +} +.rbc-rtl .rbc-agenda-view table.rbc-agenda-table tbody > tr > td + td { + border-left-width: 0; + border-right: 1px solid #f0f0f0; +} +.rbc-agenda-view table.rbc-agenda-table tbody > tr + tr { + border-top: 1px solid #f0f0f0; +} +.rbc-agenda-view table.rbc-agenda-table thead > tr > th { + padding: 3px 5px; + text-align: left; + border-bottom: 1px solid #f0f0f0; +} +.rbc-rtl .rbc-agenda-view table.rbc-agenda-table thead > tr > th { + text-align: right; +} + +.rbc-agenda-time-cell { + text-transform: lowercase; +} +.rbc-agenda-time-cell .rbc-continues-after:after { + content: ' »'; +} +.rbc-agenda-time-cell .rbc-continues-prior:before { + content: '« '; +} + +.rbc-agenda-date-cell, +.rbc-agenda-time-cell { + white-space: nowrap; +} + +.rbc-agenda-event-cell { + width: 100%; +} + +.rbc-time-column { + display: flex; + flex-direction: column; + min-height: 100%; +} +.rbc-time-column .rbc-timeslot-group { + flex: 1; +} + +.rbc-timeslot-group { + border-bottom: 1px solid #f0f0f0; + min-height: 40px; + line-height: 39px; + display: flex; + flex-flow: column nowrap; + // cursor: pointer; + &:hover { + background: #f0f0f0; + } +} + +.rbc-time-gutter, +.rbc-header-gutter { + flex: none; +} + +.rbc-label { + padding: 0 5px; +} + +.rbc-day-slot { + position: relative; +} +.rbc-day-slot .rbc-events-container { + bottom: 0; + left: 0; + position: absolute; + right: 0; + margin-right: 10px; + top: 0; +} +.rbc-day-slot .rbc-events-container.rbc-rtl { + left: 10px; + right: 0; +} +.rbc-day-slot .rbc-event { + border: 1px solid #265985; + display: flex; + max-height: 100%; + min-height: 20px; + flex-flow: column wrap; + align-items: flex-start; + overflow: hidden; + position: absolute; +} +.rbc-day-slot .rbc-event-label { + flex: none; + padding-right: 5px; + width: auto; +} +.rbc-day-slot .rbc-event-content { + width: 100%; + flex: 1 1 0; + word-wrap: break-word; + line-height: 1; + height: 100%; + min-height: 1em; +} +.rbc-day-slot .rbc-time-slot { + // border-top: 1px solid #f7f7f7; +} +.rbc-time-header-gutter { + line-height: 40px; +} +.rbc-time-header-cell { + min-height: 32px !important; +} +.rbc-calendar.view-week { + .rbc-time-header-cell { + margin-top: -32px; + margin-bottom: 4px; + } + .rbc-time-view { + padding-top: 32px; + border-top: 0; + } + .rbc-header { + padding: 4px 8px; + } + .rbc-time-header-content { + padding: 4px 0; + } + .rbc-time-header-gutter { + padding: 0; + padding-top: 2px; + > div { + border-top: 2px solid #f0f0f0; + padding: 0 5px; + } + } +} +.rbc-time-view-resources .rbc-time-gutter, +.rbc-time-view-resources .rbc-time-header-gutter { + position: sticky; + left: 0; + background-color: white; + border-right: 1px solid #f0f0f0; + z-index: 10; + margin-right: -1px; +} + +.rbc-time-view-resources .rbc-time-header { + overflow: hidden; +} + +.rbc-time-view-resources .rbc-time-header-content { + min-width: auto; + flex: 1 0 0; + flex-basis: 0px; +} + +.rbc-time-view-resources .rbc-time-header-cell-single-day { + display: none; +} + +.rbc-time-view-resources .rbc-day-slot { + min-width: 140px; +} + +.rbc-time-view-resources .rbc-header, +.rbc-time-view-resources .rbc-day-bg { + width: 140px; + flex: 1 1 0; + flex-basis: 0 px; +} + +.rbc-time-header-content + .rbc-time-header-content { + margin-left: -1px; +} + +.rbc-time-slot { + flex: 1 0 0; +} +.rbc-time-slot.rbc-now { + font-weight: bold; +} + +.rbc-day-header { + text-align: center; +} + +.rbc-slot-selection { + z-index: 10; + position: absolute; + background-color: rgba(0, 0, 0, 0.5); + color: white; + font-size: 75%; + width: 100%; + padding: 3px; +} + +.rbc-slot-selecting { + cursor: move; +} + +.rbc-time-view { + display: flex; + flex-direction: column; + flex: 1; + width: 100%; + border-top: 1px solid #f0f0f0; + min-height: 0; +} +.rbc-time-view .rbc-time-gutter { + white-space: nowrap; +} +.rbc-time-view .rbc-allday-cell { + box-sizing: content-box; + width: 100%; + height: 100%; + position: relative; +} +.rbc-time-view .rbc-allday-cell + .rbc-allday-cell { + border-left: 1px solid #f0f0f0; +} +.rbc-time-view .rbc-allday-events { + position: relative; + z-index: 4; +} +.rbc-time-view .rbc-row { + box-sizing: border-box; + min-height: 20px; +} + +.rbc-time-header { + display: flex; + flex: 0 0 auto; + flex-direction: row; +} +.rbc-time-header.rbc-overflowing { + border-right: 1px solid #f0f0f0; +} +.rbc-rtl .rbc-time-header.rbc-overflowing { + border-right-width: 0; + border-left: 1px solid #f0f0f0; +} +.rbc-time-header > .rbc-row:first-child { + border-bottom: 1px solid #f0f0f0; +} +.rbc-time-header > .rbc-row.rbc-row-resource { + border-bottom: 1px solid #f0f0f0; +} + +.rbc-time-header-cell-single-day { + display: none; +} + +.rbc-time-header-content { + flex: 1; + display: flex; + min-width: 0; + flex-direction: column; + // border-left: 1px solid #f0f0f0; +} +.rbc-rtl .rbc-time-header-content { + border-left-width: 0; + border-right: 1px solid #f0f0f0; +} +.rbc-time-header-content > .rbc-row.rbc-row-resource { + border-bottom: 1px solid #f0f0f0; + flex-shrink: 0; +} + +.rbc-time-content { + display: flex; + flex: 1 0 0%; + align-items: flex-start; + width: 100%; + border-top: 1px solid #f0f0f0; + overflow-y: auto; + position: relative; +} +.rbc-time-content > .rbc-time-gutter { + flex: none; +} +.rbc-time-content > * + * > * { + // border-left: 1px solid #f0f0f0; +} +.rbc-rtl .rbc-time-content > * + * > * { + border-left-width: 0; + border-right: 1px solid #f0f0f0; +} +.rbc-time-content > .rbc-day-slot { + width: 100%; + user-select: none; + -webkit-user-select: none; +} + +.rbc-current-time-indicator { + position: absolute; + z-index: 3; + left: 0; + right: 0; + height: 1px; + background-color: #74ad31; + pointer-events: none; +} + +@media only screen and (max-width: 800px) { + .view-type-calendar .action-buttons { + position: relative !important; + left: 0 !important; + .filter-action-button { + left: 0 !important; + } + } + .rbc-toolbar .rbc-toolbar-label { + position: absolute; + top: 28px; + left: 50%; + transform: translateX(-50%); + } + .rbc-toolbar { + justify-content: space-between !important; + } + .rbc-header { + padding: 4px !important; + } +} diff --git a/packages/client/src/schema-component/antd/calendar-v2/types.d.ts b/packages/client/src/schema-component/antd/calendar-v2/types.d.ts new file mode 100644 index 000000000..d3f3470d3 --- /dev/null +++ b/packages/client/src/schema-component/antd/calendar-v2/types.d.ts @@ -0,0 +1,8 @@ +export interface ToolbarProps { + localizer?: any; + label?: any; + view?: any; + views?: any; + onNavigate?: (action: string) => void; + onView?: (view: string) => void; +} diff --git a/packages/client/src/schema-component/antd/calendar-v2/utils.ts b/packages/client/src/schema-component/antd/calendar-v2/utils.ts new file mode 100644 index 000000000..f72d5efba --- /dev/null +++ b/packages/client/src/schema-component/antd/calendar-v2/utils.ts @@ -0,0 +1,13 @@ +import { get } from 'lodash'; +import { i18n } from '../../../i18n'; + +export const toEvents = (data: any[], fieldNames: any) => { + return data?.map((item) => { + return { + id: get(item, fieldNames.id || 'id'), + title: get(item, fieldNames.title) || i18n.t('Untitle'), + start: new Date(get(item, fieldNames.start)), + end: new Date(get(item, fieldNames.end || fieldNames.start)), + }; + }); +}; diff --git a/packages/client/src/schema-component/antd/filter/DynamicComponent.tsx b/packages/client/src/schema-component/antd/filter/DynamicComponent.tsx index 0c4beb5a3..61e00673b 100644 --- a/packages/client/src/schema-component/antd/filter/DynamicComponent.tsx +++ b/packages/client/src/schema-component/antd/filter/DynamicComponent.tsx @@ -1,42 +1,43 @@ import { createForm, onFieldValueChange } from '@formily/core'; +import { FieldContext, FormContext } from '@formily/react'; import React, { useContext, useMemo } from 'react'; -import { FormProvider, SchemaComponent } from '../../../schema-component'; +import { SchemaComponent } from '../../core'; import { useComponent } from '../../hooks'; import { FilterContext } from './context'; export const DynamicComponent = (props) => { const { dynamicComponent } = useContext(FilterContext); const component = useComponent(dynamicComponent); - const form = useMemo( - () => - createForm({ - values: { - value: props.value, - }, - effects() { - onFieldValueChange('value', (field) => { - props?.onChange?.(field.value); - }); - }, - }), - [JSON.stringify(props.schema), JSON.stringify(props.value)], - ); + const form = useMemo(() => { + return createForm({ + values: { + value: props.value, + }, + effects() { + onFieldValueChange('value', (field) => { + props?.onChange?.(field.value); + }); + }, + }); + }, [JSON.stringify(props.schema), JSON.stringify(props.value)]); const renderSchemaComponent = () => { return ( - + + + ); }; return ( - + {component ? React.createElement(component, { value: props.value, @@ -44,6 +45,6 @@ export const DynamicComponent = (props) => { renderSchemaComponent, }) : renderSchemaComponent()} - + ); }; diff --git a/packages/client/src/schema-component/antd/filter/Filter.Action.Designer.tsx b/packages/client/src/schema-component/antd/filter/Filter.Action.Designer.tsx new file mode 100644 index 000000000..bfe4b4d08 --- /dev/null +++ b/packages/client/src/schema-component/antd/filter/Filter.Action.Designer.tsx @@ -0,0 +1,90 @@ +import { ISchema, useField, useFieldSchema } from '@formily/react'; +import React from 'react'; +import { useDesignable } from '../..'; +import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings'; + +export const FilterActionDesigner = (props) => { + const initialValue = {}; + const field = useField(); + const fieldSchema = useFieldSchema(); + const { dn } = useDesignable(); + const isPopupAction = ['create', 'update', 'view'].includes(fieldSchema['x-action'] || ''); + return ( + + { + if (title) { + fieldSchema.title = title; + field.title = title; + field.componentProps.icon = icon; + fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {}; + fieldSchema['x-component-props'].icon = icon; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + title, + 'x-component-props': { + ...fieldSchema['x-component-props'], + }, + }, + }); + dn.refresh(); + } + }} + /> + {isPopupAction && ( + { + field.componentProps.openMode = value; + fieldSchema['x-component-props']['openMode'] = value; + dn.emit('patch', { + schema: { + 'x-uid': fieldSchema['x-uid'], + 'x-component-props': fieldSchema['x-component-props'], + }, + }); + dn.refresh(); + }} + /> + )} + + { + return s['x-component'] === 'Space' || s['x-component'] === 'ActionBar'; + }} + /> + + ); +}; diff --git a/packages/client/src/schema-component/antd/filter/Filter.tsx b/packages/client/src/schema-component/antd/filter/Filter.tsx index 33e04725e..0bc1ea2da 100644 --- a/packages/client/src/schema-component/antd/filter/Filter.tsx +++ b/packages/client/src/schema-component/antd/filter/Filter.tsx @@ -3,6 +3,8 @@ import { observer, useField, useFieldSchema } from '@formily/react'; import React from 'react'; import { useRequest } from '../../../api-client'; import { FilterContext } from './context'; +import { FilterActionDesigner } from './Filter.Action.Designer'; +import { FilterAction } from './FilterAction'; import { FilterGroup } from './FilterGroup'; import { SaveDefaultValue } from './SaveDefaultValue'; @@ -17,7 +19,6 @@ export const Filter: any = observer((props: any) => { const fieldSchema = useFieldSchema(); useDataSource({ onSuccess(data) { - console.log('onSuccess', data?.data); field.dataSource = data?.data || []; }, }); @@ -26,9 +27,11 @@ export const Filter: any = observer((props: any) => { - {/*
{JSON.stringify(field.value, null, 2)}
*/} ); }); Filter.SaveDefaultValue = SaveDefaultValue; + +Filter.Action = FilterAction; +Filter.Action.Designer = FilterActionDesigner; diff --git a/packages/client/src/schema-component/antd/filter/FilterAction.tsx b/packages/client/src/schema-component/antd/filter/FilterAction.tsx new file mode 100644 index 000000000..c56e4f820 --- /dev/null +++ b/packages/client/src/schema-component/antd/filter/FilterAction.tsx @@ -0,0 +1,124 @@ +import { css } from '@emotion/css'; +import { createForm, Field, Form } from '@formily/core'; +import { observer, useField, useFieldSchema, useForm } from '@formily/react'; +import { Button, Popover, Space } from 'antd'; +import React, { createContext, useContext, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { FormProvider, SchemaComponent } from '../../core'; +import { useDesignable } from '../../hooks'; +import { useProps } from '../../hooks/useProps'; +import { Action } from '../action'; + +export const FilterActionContext = createContext(null); + +export const FilterAction = observer((props: any) => { + const { t } = useTranslation(); + const field = useField(); + const [visible, setVisible] = useState(false); + const { designable, dn } = useDesignable(); + const fieldSchema = useFieldSchema(); + const form = useMemo
(() => props.form || createForm(), [visible]); + const { options, onSubmit, onReset, ...others } = useProps(props); + return ( + + { + setVisible(visible); + }} + trigger={'click'} + content={ + + + +
+ + + + + +
+
+ + } + > + +
+
+ ); +}); + +const SaveConditions = () => { + const { fieldSchema, field, designable, dn } = useContext(FilterActionContext); + const form = useForm(); + const { t } = useTranslation(); + if (!designable) { + return null; + } + return ( + + ); +}; diff --git a/packages/client/src/schema-component/antd/filter/FilterItem.tsx b/packages/client/src/schema-component/antd/filter/FilterItem.tsx index 4b5d1563d..e445f9f49 100644 --- a/packages/client/src/schema-component/antd/filter/FilterItem.tsx +++ b/packages/client/src/schema-component/antd/filter/FilterItem.tsx @@ -1,5 +1,5 @@ import { CloseCircleOutlined } from '@ant-design/icons'; -import { observer, useField } from '@formily/react'; +import { observer } from '@formily/react'; import { Cascader, Select, Space } from 'antd'; import React, { useContext } from 'react'; import { useCompile } from '../..'; @@ -8,10 +8,9 @@ import { DynamicComponent } from './DynamicComponent'; import { useValues } from './useValues'; export const FilterItem = observer((props: any) => { - const field = useField(); - const remove = useContext(RemoveConditionContext); - const { option, options, dataIndex, operator, setDataIndex, setOperator, value, setValue } = useValues(); const compile = useCompile(); + const remove = useContext(RemoveConditionContext); + const { schema, fields, operators, dataIndex, operator, setDataIndex, setOperator, value, setValue } = useValues(); return (
@@ -25,16 +24,15 @@ export const FilterItem = observer((props: any) => { width: 150, }} changeOnSelect={false} - key={field.address.toString()} value={dataIndex} - options={compile(options)} + options={compile(fields)} onChange={(value) => { setDataIndex(value); }} /> { + setVisible(true); + }} + allowClear + onChange={(changed: any) => { + if (!changed) { + onChange(null); + setSelectedRows([]); + } else if (!changed?.length) { + onChange(null); + setSelectedRows([]); + } else if (Array.isArray(changed)) { + const values = options?.filter((option) => changed.includes(option[fieldNames.value])); + onChange(values); + setSelectedRows(values); + } + }} + options={options} + value={multiple ? values : values?.[0]} + open={false} + /> + + + + + + + + + + + +
+ ); +}; diff --git a/packages/client/src/schema-component/antd/record-picker/ReadPrettyRecordPicker.tsx b/packages/client/src/schema-component/antd/record-picker/ReadPrettyRecordPicker.tsx new file mode 100644 index 000000000..ea050bd45 --- /dev/null +++ b/packages/client/src/schema-component/antd/record-picker/ReadPrettyRecordPicker.tsx @@ -0,0 +1,48 @@ +import { Field } from '@formily/core'; +import { observer, RecursionField, useField, useFieldSchema } from '@formily/react'; +import { toArr } from '@formily/shared'; +import { Space } from 'antd'; +import React, { useState } from 'react'; +import { CollectionProvider, useCollection } from '../../../collection-manager'; +import { RecordProvider } from '../../../record-provider'; +import { FormProvider } from '../../core'; +import { ActionContext } from '../action'; +import { useFieldNames } from './useFieldNames'; + +export const ReadPrettyRecordPicker: React.FC = observer((props: any) => { + const fieldSchema = useFieldSchema(); + const field = useField(); + const fieldNames = useFieldNames(props); + const [visible, setVisible] = useState(false); + const { getField } = useCollection(); + const collectionField = getField(fieldSchema.name); + return ( +
+ + + , }> + {toArr(field.value).map((record, index) => { + return ( + + { + console.log('setVisible'); + setVisible(true); + }} + > + {record?.[fieldNames?.label || 'label']} + + + + + + + + ); + })} + + + +
+ ); +}); diff --git a/packages/client/src/schema-component/antd/record-picker/RecordPicker.tsx b/packages/client/src/schema-component/antd/record-picker/RecordPicker.tsx index afc86160d..f16b53165 100644 --- a/packages/client/src/schema-component/antd/record-picker/RecordPicker.tsx +++ b/packages/client/src/schema-component/antd/record-picker/RecordPicker.tsx @@ -1,205 +1,9 @@ -import { LoadingOutlined } from '@ant-design/icons'; -import { createForm, Field, onFormSubmit } from '@formily/core'; -import { - connect, - FieldContext, - FormContext, - mapProps, - mapReadPretty, - observer, - RecursionField, - useField, - useFieldSchema, - useForm -} from '@formily/react'; -import { toArr } from '@formily/shared'; -import { Button, Drawer, Select, Space } from 'antd'; -import React, { createContext, useContext, useEffect, useMemo, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { RecordProvider } from '../../../record-provider'; -import { useAttach } from '../../hooks/useAttach'; -import { ActionContext, useActionContext } from '../action'; - -const findRowSelection = (fieldSchema) => { - return fieldSchema.reduceProperties((buf, s) => { - if (s['x-component'] === 'Table.RowSelection') { - return s; - } - const r = findRowSelection(s); - if (r) { - return r; - } - return buf; - }, null); -}; - -const InputRecordPicker: React.FC = (props) => { - const { multiple, onChange } = props; - const fieldNames = { label: 'label', value: 'value', ...props.fieldNames }; - const [visible, setVisible] = useState(false); - const fieldSchema = useFieldSchema(); - const field = useField(); - const s = findRowSelection(fieldSchema); - const [value, setValue] = useState(field.value); - useEffect(() => { - setValue(field.value); - }, [JSON.stringify(field.value)]); - const form = useMemo( - () => - createForm({ - initialValues: s?.name - ? { - [s.name]: field.value, - } - : {}, - effects() { - onFormSubmit((form) => { - setValue(form.values[s.name]); - onChange?.(form.values[s.name]); - }); - }, - }), - [JSON.stringify(field.value)], - ); - const toValue = (value) => { - if (!value) { - return; - } - if (Array.isArray(value)) { - return value.map((item) => { - return { - label: item[fieldNames.label] || item[fieldNames.value], - value: item[fieldNames.value], - }; - }); - } - return { - label: value[fieldNames.label] || value[fieldNames.value], - value: value[fieldNames.value], - }; - }; - const f = useAttach(form.createVoidField({ ...field.props, basePath: '' })); - return ( -
- - - - - { - return s['x-component'] === 'RecordPicker.Options'; - }} - /> - - - -
- ); -}; - -const RowContext = createContext({}); - -const ReadPrettyRecordPicker: React.FC = (props) => { - const fieldSchema = useFieldSchema(); - const field = useField(); - return ( -
- , }> - {toArr(field.value).map((record, index) => { - return ( - - { - return s['x-component'] === 'RecordPicker.SelectedItem'; - }} - /> - - ); - })} - -
- ); -}; - -const mapSuffixProps = (props, field) => { - return { - ...props, - suffix: {field?.['loading'] || field?.['validating'] ? : props.suffix}, - }; -}; +import { connect, mapReadPretty } from '@formily/react'; +import { InputRecordPicker } from './InputRecordPicker'; +import { ReadPrettyRecordPicker } from './ReadPrettyRecordPicker'; export const RecordPicker: any = connect( InputRecordPicker, - mapProps(mapSuffixProps), + // mapProps(mapSuffixProps), mapReadPretty(ReadPrettyRecordPicker), ); - -RecordPicker.Options = observer((props) => { - const field = useField(); - const form = useForm(); - const { visible, setVisible } = useActionContext(); - const { t } = useTranslation(); - return ( - setVisible(false)} - footer={ - - - - - } - /> - ); -}); - -RecordPicker.SelectedItem = () => { - const ctx = useContext(RowContext); - const fieldSchema = useFieldSchema(); - const [visible, setVisible] = useState(false); - const fieldNames = ctx.field.componentProps.fieldNames; - return ( - - setVisible(true)}> - {ctx.record[fieldNames?.label || 'id']} - - - - - - ); -}; diff --git a/packages/client/src/schema-component/antd/record-picker/index.tsx b/packages/client/src/schema-component/antd/record-picker/index.tsx index 104723bcf..8884fc5ca 100644 --- a/packages/client/src/schema-component/antd/record-picker/index.tsx +++ b/packages/client/src/schema-component/antd/record-picker/index.tsx @@ -1 +1,8 @@ -export * from './RecordPicker'; +import { Action } from '../action'; +import { RecordPicker } from './RecordPicker'; + +RecordPicker.Viewer = Action.Container; +RecordPicker.Selector = Action.Container; + +export { RecordPicker }; + diff --git a/packages/client/src/schema-component/antd/record-picker/useFieldNames.ts b/packages/client/src/schema-component/antd/record-picker/useFieldNames.ts new file mode 100644 index 000000000..e93ca7e4d --- /dev/null +++ b/packages/client/src/schema-component/antd/record-picker/useFieldNames.ts @@ -0,0 +1,7 @@ +import { useFieldSchema } from "@formily/react"; + +export const useFieldNames = (props) => { + const fieldSchema = useFieldSchema(); + const fieldNames = fieldSchema?.['x-component-props']?.['fieldNames'] || props.fieldNames; + return { label: 'label', value: 'value', ...fieldNames }; +}; diff --git a/packages/client/src/schema-component/antd/table-v2/Table.ActionColumnDesigner.tsx b/packages/client/src/schema-component/antd/table-v2/Table.ActionColumnDesigner.tsx new file mode 100644 index 000000000..0c1f11dfc --- /dev/null +++ b/packages/client/src/schema-component/antd/table-v2/Table.ActionColumnDesigner.tsx @@ -0,0 +1,23 @@ +import { DragOutlined } from '@ant-design/icons'; +import { useFieldSchema } from '@formily/react'; +import { Space } from 'antd'; +import React from 'react'; +import { DragHandler } from '../../../schema-component'; +import { useSchemaInitializer } from '../../../schema-initializer'; + +export const TableActionColumnDesigner = (props: any) => { + const fieldSchema = useFieldSchema(); + const { render } = useSchemaInitializer(fieldSchema['x-initializer']); + return ( +
+
+ + + + + {render()} + +
+
+ ); +}; diff --git a/packages/client/src/schema-component/antd/table-v2/Table.Column.ActionBar.tsx b/packages/client/src/schema-component/antd/table-v2/Table.Column.ActionBar.tsx new file mode 100644 index 000000000..4974970dd --- /dev/null +++ b/packages/client/src/schema-component/antd/table-v2/Table.Column.ActionBar.tsx @@ -0,0 +1,53 @@ +import { css } from '@emotion/css'; +import { observer } from '@formily/react'; +import React from 'react'; +import { SortableItem, useDesigner } from '../..'; + +export const designerCss = css` + position: relative; + &:hover { + > .general-schema-designer { + display: block; + } + } + > .general-schema-designer { + position: absolute; + z-index: 999; + top: 0; + bottom: 0; + left: 0; + right: 0; + display: none; + background: rgba(241, 139, 98, 0.06) !important; + border: 0 !important; + top: -16px !important; + bottom: -16px !important; + left: -16px !important; + right: -16px !important; + pointer-events: none; + > .general-schema-designer-icons { + position: absolute; + right: 2px; + top: 2px; + line-height: 16px; + pointer-events: all; + .ant-space-item { + background-color: #f18b62; + color: #fff; + line-height: 16px; + width: 16px; + padding-left: 1px; + } + } + } +`; + +export const TableColumnActionBar = observer((props) => { + const Designer = useDesigner(); + return ( + + + {props.children} + + ); +}); diff --git a/packages/client/src/schema-component/antd/table-v2/Table.Column.Decorator.tsx b/packages/client/src/schema-component/antd/table-v2/Table.Column.Decorator.tsx new file mode 100644 index 000000000..8f527e939 --- /dev/null +++ b/packages/client/src/schema-component/antd/table-v2/Table.Column.Decorator.tsx @@ -0,0 +1,59 @@ +import { useField, useFieldSchema } from '@formily/react'; +import React, { useLayoutEffect } from 'react'; +import { SortableItem, useCollection, useCompile, useDesignable, useDesigner } from '../../../'; +import { designerCss } from './Table.Column.ActionBar'; + +export const useColumnSchema = () => { + const { getField } = useCollection(); + const compile = useCompile(); + const columnSchema = useFieldSchema(); + const fieldSchema = columnSchema.reduceProperties((buf, s) => { + if (s['x-component'] === 'CollectionField') { + return s; + } + return buf; + }, null); + if (!fieldSchema) { + return {}; + } + const collectionField = getField(fieldSchema.name); + return { columnSchema, fieldSchema, collectionField, uiSchema: compile(collectionField?.uiSchema) }; +}; + +export const TableColumnDecorator = (props) => { + const Designer = useDesigner(); + const field = useField(); + const { fieldSchema, uiSchema, collectionField } = useColumnSchema(); + const { refresh } = useDesignable(); + const compile = useCompile(); + useLayoutEffect(() => { + if (field.title) { + return; + } + if (!fieldSchema) { + return; + } + if (uiSchema?.title) { + field.title = uiSchema?.title; + } + }, [uiSchema?.title]); + return ( + + + {/* */} + {field.title || compile(uiSchema?.title)} + {/*
{ + field.title = uid(); + // columnSchema.title = field.title = field.title; + // refresh(); + // field.query(`.*.${fieldSchema.name}`).take((f) => { + // f.componentProps.dateFormat = 'YYYY-MM-DD'; + // }); + }} + > + Edit +
*/} +
+ ); +}; diff --git a/packages/client/src/schema-component/antd/table-v2/Table.Column.Designer.tsx b/packages/client/src/schema-component/antd/table-v2/Table.Column.Designer.tsx new file mode 100644 index 000000000..bf71f620c --- /dev/null +++ b/packages/client/src/schema-component/antd/table-v2/Table.Column.Designer.tsx @@ -0,0 +1,103 @@ +import { ISchema, useField, useFieldSchema } from '@formily/react'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useCollectionManager } from '../../../collection-manager'; +import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings'; +import { useCompile, useDesignable } from '../../hooks'; + +const useLabelFields = (collectionName?: any) => { + if (!collectionName) { + return []; + } + const compile = useCompile(); + const { getCollectionFields } = useCollectionManager(); + const targetFields = getCollectionFields(collectionName); + return targetFields + ?.filter?.((field) => field?.interface && !field?.target && field.type !== 'boolean') + ?.map?.((field) => { + return { + value: field.name, + label: compile(field?.uiSchema?.title || field.name), + }; + }); +}; + +export const TableColumnDesigner = (props) => { + const { uiSchema, fieldSchema, collectionField } = props; + const field = useField(); + const { t } = useTranslation(); + const columnSchema = useFieldSchema(); + const { dn } = useDesignable(); + const fieldNames = + fieldSchema?.['x-component-props']?.['fieldNames'] || uiSchema?.['x-component-props']?.['fieldNames']; + const options = useLabelFields(collectionField?.target); + return ( + + { + if (title) { + field.title = title; + columnSchema.title = title; + dn.emit('patch', { + schema: { + 'x-uid': columnSchema['x-uid'], + title: columnSchema.title, + }, + }); + } + dn.refresh(); + }} + /> + {collectionField?.interface === 'linkTo' && ( + { + const fieldNames = { + ...fieldSchema['x-component-props']['fieldNames'], + label, + }; + fieldSchema['x-component-props']['fieldNames'] = fieldNames; + field.query(`.*.${fieldSchema.name}`).take((f) => { + f.componentProps.fieldNames = fieldNames; + }); + dn.emit('patch', { + schema: { + 'x-uid': fieldSchema['x-uid'], + 'x-component-props': { + fieldNames, + }, + }, + }); + dn.refresh(); + }} + /> + )} + + + + ); +}; diff --git a/packages/client/src/schema-component/antd/table-v2/Table.Column.tsx b/packages/client/src/schema-component/antd/table-v2/Table.Column.tsx new file mode 100644 index 000000000..7c360ca5a --- /dev/null +++ b/packages/client/src/schema-component/antd/table-v2/Table.Column.tsx @@ -0,0 +1,7 @@ +import { useField } from '@formily/react'; +import React from 'react'; + +export const TableColumn = (props) => { + const field = useField(); + return
{field.title}
; +}; diff --git a/packages/client/src/schema-component/antd/table-v2/Table.Designer.tsx b/packages/client/src/schema-component/antd/table-v2/Table.Designer.tsx new file mode 100644 index 000000000..00360cf98 --- /dev/null +++ b/packages/client/src/schema-component/antd/table-v2/Table.Designer.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { useCollection } from '../../../collection-manager'; +import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings'; + +export const TableDesigner = () => { + const { name, title } = useCollection(); + return ( + + + + ); +}; diff --git a/packages/client/src/schema-component/antd/table-v2/Table.tsx b/packages/client/src/schema-component/antd/table-v2/Table.tsx new file mode 100644 index 000000000..c22808b7b --- /dev/null +++ b/packages/client/src/schema-component/antd/table-v2/Table.tsx @@ -0,0 +1,280 @@ +import { MenuOutlined } from '@ant-design/icons'; +import { css } from '@emotion/css'; +import { ArrayField } from '@formily/core'; +import { observer, RecursionField, Schema, useField, useFieldSchema } from '@formily/react'; +import { Table as AntdTable, TableColumnProps } from 'antd'; +import { default as classNames, default as cls } from 'classnames'; +import React from 'react'; +import ReactDragListView from 'react-drag-listview'; +import { useTranslation } from 'react-i18next'; +import { DndContext } from '../..'; +import { RecordIndexProvider, RecordProvider, useSchemaInitializer } from '../../../'; + +const isColumnComponent = (schema: Schema) => { + return schema['x-component']?.endsWith('.Column') > -1; +}; + +const useTableColumns = () => { + const start = Date.now(); + const field = useField(); + const schema = useFieldSchema(); + const { exists, render } = useSchemaInitializer(schema['x-initializer']); + const columns = schema + .reduceProperties((buf, s) => { + if (isColumnComponent(s)) { + return buf.concat([s]); + } + }, []) + .map((s: Schema) => { + return { + title: , + dataIndex: s.name, + key: s.name, + // width: 300, + render: (v, record) => { + const index = field.value?.indexOf(record); + console.log((Date.now() - start) / 1000); + return ( + + + + + + ); + }, + } as TableColumnProps; + }); + if (!exists) { + return columns; + } + return columns.concat({ + title: render(), + dataIndex: 'TABLE_COLUMN_INITIALIZER', + key: 'TABLE_COLUMN_INITIALIZER', + }); +}; + +export const components = { + header: { + wrapper: (props) => { + return ( + +
+ + ); + }, + cell: (props) => { + return ( + + + ); + }, + row: (props) => { + return ; + }, + cell: (props) => ( +
+ ); + }, + }, + body: { + wrapper: (props) => { + return ( + +
+ ), + }, +}; + +const SortHandle = () => { + return ; +}; + +const TableIndex = (props) => { + const { index } = props; + return ( +
+ {index + 1} +
+ ); +}; + +const usePaginationProps = (pagination1, pagination2) => { + const { t } = useTranslation(); + if (pagination2 === false) { + return false; + } + if (!pagination2 && pagination1 === false) { + return false; + } + return { + showTotal: (total) => t('Total {{count}} items', { count: total }), + showSizeChanger: true, + ...pagination1, + ...pagination2, + }; +}; + +export const Table: any = observer((props: any) => { + const field = useField(); + const columns = useTableColumns(); + const { pagination: pagination1, useProps, onChange, ...others1 } = props; + const { pagination: pagination2, ...others2 } = useProps?.() || {}; + const { + dragSort = false, + showIndex = true, + onRowDragEnd, + onRowSelectionChange, + onChange: onTableChange, + rowSelection, + ...others + } = { ...others1, ...others2 } as any; + const paginationProps = usePaginationProps(pagination1, pagination2); + const restProps = { + rowSelection: rowSelection + ? { + type: 'checkbox', + selectedRowKeys: field?.data?.selectedRowKeys || [], + onChange(selectedRowKeys: any[], selectedRows: any[]) { + field.data = field.data || {}; + field.data.selectedRowKeys = selectedRowKeys; + onRowSelectionChange?.(selectedRowKeys, selectedRows); + }, + renderCell: (checked, record, index, originNode) => { + if (!dragSort && !showIndex) { + return originNode; + } + const current = props?.pagination?.current; + const pageSize = props?.pagination?.pageSize || 20; + if (current) { + index = index + (current - 1) * pageSize; + } + return ( +
+
+ {dragSort && } + {showIndex && } +
+
+ {originNode} +
+
+ ); + }, + ...rowSelection, + } + : undefined, + }; + const defaultRowKey = (record: any) => { + return field.value?.indexOf?.(record); + }; + return ( +
+ { + const from = field.value[fromIndex]; + const to = field.value[toIndex]; + field.move(fromIndex, toIndex); + onRowDragEnd({ fromIndex, toIndex, from, to }); + }} + lineClassName={css` + border-bottom: 2px solid rgba(241, 139, 98, 0.6) !important; + `} + > + { + onTableChange?.(pagination, filters, sorter, extra); + }} + // tableLayout={'auto'} + // scroll={{ x: 12 * 300 + 80 }} + columns={columns} + dataSource={field?.value?.slice?.()} + /> + +
+ ); +}); diff --git a/packages/client/src/schema-component/antd/table-v2/TableBlockDesigner.tsx b/packages/client/src/schema-component/antd/table-v2/TableBlockDesigner.tsx new file mode 100644 index 000000000..599c710d6 --- /dev/null +++ b/packages/client/src/schema-component/antd/table-v2/TableBlockDesigner.tsx @@ -0,0 +1,213 @@ +import { ArrayItems } from '@formily/antd'; +import { ISchema, useField, useFieldSchema } from '@formily/react'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useTableBlockContext } from '../../../block-provider'; +import { useCollection } from '../../../collection-manager'; +import { useCollectionFilterOptions, useSortFields } from '../../../collection-manager/action-hooks'; +import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings'; +import { useSchemaTemplate } from '../../../schema-templates'; +import { useDesignable } from '../../hooks'; + +export const TableBlockDesigner = () => { + const { name, title } = useCollection(); + const field = useField(); + const fieldSchema = useFieldSchema(); + const dataSource = useCollectionFilterOptions(name); + const sortFields = useSortFields(name); + const { service } = useTableBlockContext(); + const { t } = useTranslation(); + const { dn } = useDesignable(); + const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {}; + const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || []; + const sort = defaultSort?.map((item: string) => { + return item.startsWith('-') + ? { + field: item.substring(1), + direction: 'desc', + } + : { + field: item, + direction: 'asc', + }; + }); + const template = useSchemaTemplate(); + const { dragSort } = field.decoratorProps; + return ( + + { + field.decoratorProps.dragSort = dragSort; + fieldSchema['x-decorator-props'].dragSort = dragSort; + service.run({ ...service.params?.[0], sort: defaultSort }); + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': fieldSchema['x-decorator-props'], + }, + }); + }} + /> + { + const params = field.decoratorProps.params || {}; + params.filter = filter; + field.decoratorProps.params = params; + fieldSchema['x-decorator-props']['params'] = params; + service.run({ ...service.params?.[0], filter }); + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': fieldSchema['x-decorator-props'], + }, + }); + }} + /> + {!dragSort && ( + { + const sortArr = sort.map((item) => { + return item.direction === 'desc' ? `-${item.field}` : item.field; + }); + const params = field.decoratorProps.params || {}; + params.sort = sortArr; + field.decoratorProps.params = params; + fieldSchema['x-decorator-props']['params'] = params; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': fieldSchema['x-decorator-props'], + }, + }); + service.run({ ...service.params?.[0], sort: sortArr }); + }} + /> + )} + + { + const params = field.decoratorProps.params || {}; + params.pageSize = pageSize; + field.decoratorProps.params = params; + fieldSchema['x-decorator-props']['params'] = params; + service.run({ ...service.params?.[0], pageSize }); + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': fieldSchema['x-decorator-props'], + }, + }); + }} + /> + + + + + + ); +}; diff --git a/packages/client/src/schema-component/antd/table-v2/TableField.tsx b/packages/client/src/schema-component/antd/table-v2/TableField.tsx new file mode 100644 index 000000000..de5570574 --- /dev/null +++ b/packages/client/src/schema-component/antd/table-v2/TableField.tsx @@ -0,0 +1,25 @@ +import { observer, useField, useFieldSchema, useForm } from '@formily/react'; +import React, { useEffect } from 'react'; +import { useCollection } from '../../../collection-manager'; +import { ActionBar } from '../action'; + +export const TableField: any = observer((props) => { + const fieldSchema = useFieldSchema(); + const { getField } = useCollection(); + const field = useField(); + const collectionField = getField(fieldSchema.name); + useEffect(() => { + if (!field.title) { + field.title = collectionField?.uiSchema?.title; + } + }, []); + return
{props.children}
; +}); + +TableField.ActionBar = observer((props) => { + const form = useForm(); + if (form.readPretty) { + return null; + } + return
; +}); diff --git a/packages/client/src/schema-component/antd/table-v2/TableSelector.tsx b/packages/client/src/schema-component/antd/table-v2/TableSelector.tsx new file mode 100644 index 000000000..b9a1d7b6b --- /dev/null +++ b/packages/client/src/schema-component/antd/table-v2/TableSelector.tsx @@ -0,0 +1,3 @@ +import { Table } from './Table'; + +export const TableSelector = Table; diff --git a/packages/client/src/schema-component/antd/table-v2/TableSelectorDesigner.tsx b/packages/client/src/schema-component/antd/table-v2/TableSelectorDesigner.tsx new file mode 100644 index 000000000..1417f9bd1 --- /dev/null +++ b/packages/client/src/schema-component/antd/table-v2/TableSelectorDesigner.tsx @@ -0,0 +1,196 @@ +import { ArrayItems } from '@formily/antd'; +import { ISchema, useField, useFieldSchema } from '@formily/react'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useTableBlockContext } from '../../../block-provider'; +import { useCollection } from '../../../collection-manager'; +import { useCollectionFilterOptions, useSortFields } from '../../../collection-manager/action-hooks'; +import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings'; +import { useSchemaTemplate } from '../../../schema-templates'; +import { useDesignable } from '../../hooks'; + +export const TableSelectorDesigner = () => { + const { name, title } = useCollection(); + const field = useField(); + const fieldSchema = useFieldSchema(); + const dataSource = useCollectionFilterOptions(name); + const sortFields = useSortFields(name); + const { service } = useTableBlockContext(); + const { t } = useTranslation(); + const { dn } = useDesignable(); + const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {}; + const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || []; + const sort = defaultSort?.map((item: string) => { + return item.startsWith('-') + ? { + field: item.substring(1), + direction: 'desc', + } + : { + field: item, + direction: 'asc', + }; + }); + const template = useSchemaTemplate(); + const { dragSort } = field.decoratorProps; + return ( + + { + const params = field.decoratorProps.params || {}; + params.filter = filter; + field.decoratorProps.params = params; + fieldSchema['x-decorator-props']['params'] = params; + service.run({ ...service.params?.[0], filter }); + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': fieldSchema['x-decorator-props'], + }, + }); + }} + /> + {!dragSort && ( + { + const sortArr = sort.map((item) => { + return item.direction === 'desc' ? `-${item.field}` : item.field; + }); + const params = field.decoratorProps.params || {}; + params.sort = sortArr; + field.decoratorProps.params = params; + fieldSchema['x-decorator-props']['params'] = params; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': fieldSchema['x-decorator-props'], + }, + }); + service.run({ ...service.params?.[0], sort: sortArr }); + }} + /> + )} + + { + const params = field.decoratorProps.params || {}; + params.pageSize = pageSize; + field.decoratorProps.params = params; + fieldSchema['x-decorator-props']['params'] = params; + service.run({ ...service.params?.[0], pageSize }); + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': fieldSchema['x-decorator-props'], + }, + }); + }} + /> + + + + ); +}; diff --git a/packages/client/src/schema-component/antd/table-v2/demos/collections.ts b/packages/client/src/schema-component/antd/table-v2/demos/collections.ts new file mode 100644 index 000000000..2eed0197b --- /dev/null +++ b/packages/client/src/schema-component/antd/table-v2/demos/collections.ts @@ -0,0 +1,1321 @@ +export default { + data: [ + { + key: '17zb9wfq0vt', + name: 'product', + title: 's', + inherit: false, + fields: [], + createdBy: true, + updatedBy: true, + sortable: true, + }, + { + key: '1s4aq7jbxdh', + name: 'roles', + title: '{{t("Roles")}}', + inherit: false, + fields: [ + { + key: 'gs6190e27z5', + name: 'name', + type: 'uid', + interface: 'input', + collectionName: 'roles', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'vy6i30jm9jm', + uiSchema: { + 'x-uid': 'vy6i30jm9jm', + name: 'vlldvknkn3t', + type: 'string', + title: '{{t("Role UID")}}', + 'x-component': 'Input', + }, + prefix: 'r_', + primaryKey: true, + }, + { + key: 'w0hvovoxnjh', + name: 'title', + type: 'string', + interface: 'input', + collectionName: 'roles', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'pyq24tp0zkj', + uiSchema: { + 'x-uid': 'pyq24tp0zkj', + name: 'wtku9z07h69', + type: 'string', + title: '{{t("Role name")}}', + 'x-component': 'Input', + }, + unique: true, + }, + { + key: 'lxysmomsdhy', + name: 'description', + type: 'string', + interface: null, + collectionName: 'roles', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + }, + { + key: 'ynyhe4kgxku', + name: 'strategy', + type: 'json', + interface: null, + collectionName: 'roles', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + }, + { + key: 'qr9949ps9dv', + name: 'default', + type: 'boolean', + interface: null, + collectionName: 'roles', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + defaultValue: false, + }, + { + key: '11ts4nwrm8c', + name: 'allowConfigure', + type: 'boolean', + interface: null, + collectionName: 'roles', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + }, + { + key: 'nxj4s7xev9g', + name: 'allowNewMenu', + type: 'boolean', + interface: null, + collectionName: 'roles', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + }, + { + key: 'ibwisd53xeg', + name: 'menuUiSchemas', + type: 'belongsToMany', + interface: null, + collectionName: 'roles', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + target: 'uiSchemas', + targetKey: 'x-uid', + foreignKey: 'roleName', + sourceKey: 'name', + otherKey: 'uiSchemaXUid', + through: 'rolesUischemas', + }, + { + key: '0vt1uoy8f47', + name: 'resources', + type: 'hasMany', + interface: null, + collectionName: 'roles', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + target: 'rolesResources', + sourceKey: 'name', + targetKey: 'id', + foreignKey: 'roleName', + }, + ], + autoGenId: false, + model: 'RoleModel', + filterTargetKey: 'name', + }, + { + key: 'nhng5sgypw8', + name: 't_j6omof6tza8', + title: '任务', + inherit: false, + fields: [ + { + key: 'aabs1ya9aux', + name: 'f_g8j5jvalqh0', + type: 'string', + interface: 'input', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'tzb6h0tsbis', + uiSchema: { + 'x-uid': 'tzb6h0tsbis', + name: 'y94c05v2hp2', + type: 'string', + 'x-component': 'Input', + title: '名称', + enum: [], + }, + }, + { + key: 'kidn54kisne', + name: 'f_tegyd222bcc', + type: 'text', + interface: 'textarea', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'p4uucjm3rrx', + uiSchema: { + 'x-uid': 'p4uucjm3rrx', + name: 'ri9ulk87owm', + type: 'string', + 'x-component': 'Input.TextArea', + title: '描述', + enum: [], + }, + }, + { + key: 'kwdiruosgsb', + name: 'f_u007sq2jg93', + type: 'belongsToMany', + interface: 'linkTo', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: 's5w3l6pl1hn', + uiSchemaUid: 'iea79k3kmao', + uiSchema: { + 'x-uid': 'iea79k3kmao', + name: 'njked4wch3i', + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'id', value: 'id' } }, + title: '负责人', + enum: [], + }, + target: 'users', + targetKey: 'id', + sourceKey: 'id', + through: 't_7476m1wwsfe', + foreignKey: 'f_9zwkbo7dd18', + otherKey: 'f_q84aphtx7eb', + }, + { + key: 'w7civak6gp3', + name: 'f_hpmvdltzs6m', + type: 'string', + interface: 'radioGroup', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: '481uv9zj072', + uiSchema: { + 'x-uid': '481uv9zj072', + name: '8fyelcfuzfu', + enum: [ + { value: '67snt3e6yld', label: '未开始', color: 'default' }, + { value: 'ht963n365al', label: '进行中', color: 'green' }, + { value: '2s2s8wzcnm0', label: '测试中', color: 'volcano' }, + { value: 'irepvdzp1ac', label: '已完成', color: 'blue' }, + ], + type: 'string', + 'x-component': 'Radio.Group', + title: '状态', + }, + }, + { + key: '9x87w5z0ea6', + name: 'f_jj9cyhron1d', + type: 'hasMany', + interface: 'subTable', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'pndvx008mmn', + uiSchema: { + 'x-uid': 'pndvx008mmn', + name: 'l8xqzpkxj3p', + type: 'array', + 'x-component': 'Table', + 'x-component-props': {}, + enum: [], + title: '子任务', + }, + targetKey: 'id', + sourceKey: 'id', + foreignKey: 'f_yzivojrp6l8', + target: 't_ab12qiwruwk', + }, + { + key: '3f4y1iq16ux', + name: 'f_ooar0pto2ko', + type: 'belongsToMany', + interface: 'attachment', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: '5xf7izxjny5', + uiSchema: { + 'x-uid': '5xf7izxjny5', + name: '9az9003ijcm', + 'x-component-props': { multiple: true, action: 'attachments:upload' }, + type: 'array', + 'x-component': 'Upload.Attachment', + title: '附件', + enum: [], + }, + target: 'attachments', + targetKey: 'id', + sourceKey: 'id', + through: 't_5a7wh2qhk4l', + foreignKey: 'f_tqfsxclrexu', + otherKey: 'f_j2krlyq6oka', + }, + { + key: '4um5urxskg0', + name: 'f_f7txg1oc3nt', + type: 'date', + interface: 'createdAt', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'jjax6r7c84m', + uiSchema: { + 'x-uid': 'jjax6r7c84m', + name: 'clidfmw96u3', + 'x-component-props': { dateFormat: 'YYYY-MM-DD', showTime: true, timeFormat: 'HH:mm:ss' }, + type: 'datetime', + title: '创建时间', + 'x-component': 'DatePicker', + 'x-read-pretty': true, + enum: [], + }, + field: 'createdAt', + }, + { + key: 'dnwqg2s3oph', + name: 'f_2dpc76bszit', + type: 'belongsTo', + interface: 'createdBy', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'po3y2pc112z', + uiSchema: { + 'x-uid': 'po3y2pc112z', + name: 'ke2rte2zikm', + type: 'object', + title: '创建人', + 'x-component': 'RecordPicker', + 'x-component-props': { fieldNames: { value: 'id', label: 'nickname' } }, + 'x-read-pretty': true, + enum: [], + }, + target: 'users', + foreignKey: 'createdById', + targetKey: 'id', + }, + { + key: '1sayjktagk3', + name: 'f_z27302tl2bf', + type: 'string', + interface: 'select', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: '0a28rxnov8o', + uiSchema: { + 'x-uid': '0a28rxnov8o', + name: '2vg44ojdq4j', + enum: [ + { value: 'mkvgt7ndsqd', label: '缺陷', color: 'volcano' }, + { value: '9yyv2tawmry', label: '交互', color: 'green' }, + { value: 'vx4bhmtsuus', label: '需求', color: 'cyan' }, + ], + type: 'string', + 'x-component': 'Select', + title: '类型', + }, + }, + { + key: 'zhvtzwta7bz', + name: 'f_yc8jbfiqfvh', + type: 'string', + interface: 'radioGroup', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'jijqs397op9', + uiSchema: { + 'x-uid': 'jijqs397op9', + name: '99v9hnujiuh', + enum: [ + { value: 's7hocw6twfk', label: '高', color: 'red' }, + { value: 'v25527dxseu', label: '中', color: 'blue' }, + { value: 'c4hobfb5k07', label: '低', color: 'lime' }, + ], + type: 'string', + 'x-component': 'Radio.Group', + title: '优先级', + }, + }, + { + key: 'nobsmkp6ypg', + name: 'f_ksgzy9vmgce', + type: 'belongsToMany', + interface: 'linkTo', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: 'z70jtfabrm7', + uiSchemaUid: '3gbviwfd0cv', + uiSchema: { + 'x-uid': '3gbviwfd0cv', + name: '6sabm3win42', + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: false, fieldNames: { label: 'id', value: 'id' } }, + title: '迭代', + enum: [], + }, + target: 't_94rsj6kbzvn', + targetKey: 'id', + sourceKey: 'id', + through: 't_ewdf2dsjlu3', + foreignKey: 'f_72tnpdz6kfg', + otherKey: 'f_8do8o1wsavk', + }, + { + key: 'rwtqeecnbst', + name: 'f_zek99qhv0vc', + type: 'date', + interface: 'updatedAt', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'df0kmwka9ra', + uiSchema: { + 'x-uid': 'df0kmwka9ra', + name: 'e3hmz8upyle', + 'x-component-props': { dateFormat: 'YYYY-MM-DD', showTime: true, timeFormat: 'HH:mm:ss' }, + type: 'datetime', + title: '更新时间', + 'x-component': 'DatePicker', + 'x-read-pretty': true, + }, + field: 'updatedAt', + }, + { + key: 'tm66hbii6zz', + name: 'f_cht6rsiiiko', + type: 'string', + interface: 'select', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'tkfjlde4ppl', + uiSchema: { + 'x-uid': 'tkfjlde4ppl', + name: 'p0rccyurzle', + enum: [ + { value: 'uduxfqpr5t2', label: '菜单' }, + { value: 'dnphxkwat35', label: '权限' }, + { value: '5cso9lpizog', label: '数据表' }, + { value: 'wlg329nsu3a', label: '字段' }, + { value: 'm8z7r06vk9z', label: '表格' }, + { value: 'ftfccbha25a', label: '表单' }, + { value: 'o33ljfkje2h', label: '详情' }, + { value: 'ym9wwk3aqbu', label: '日历' }, + { value: 'wge57qtiz14', label: '看板' }, + { value: 'm4xoc8rbafg', label: '操作' }, + { value: 'r6l2wg1snxc', label: '配置' }, + { value: 'j1cv1vf16fm', label: '附件' }, + { value: 'l464jdsy7vr', label: '容器' }, + ], + type: 'string', + 'x-component': 'Select', + title: '分类', + }, + }, + { + key: 'c9hzonl1hzq', + name: 'f_47f2d9wgofm', + type: 'belongsToMany', + interface: 'linkTo', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: '7mufig9muag', + uiSchemaUid: 'rbl27ngs563', + uiSchema: { + 'x-uid': 'rbl27ngs563', + name: 'xrvwttiv064', + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'id', value: 'id' } }, + title: '相关任务', + }, + target: 't_j6omof6tza8', + targetKey: 'id', + sourceKey: 'id', + through: 't_qhgbwomd2t7', + foreignKey: 'f_jes256zqwr1', + otherKey: 'f_gtgjj059mye', + }, + { + key: '7mufig9muag', + name: 'f_qmlomqm7lvb', + type: 'belongsToMany', + interface: 'linkTo', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: 'c9hzonl1hzq', + uiSchemaUid: 'vgy9cgq9v6b', + uiSchema: { + 'x-uid': 'vgy9cgq9v6b', + name: 'sf99arn65jj', + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'id', value: 'id' } }, + title: '任务', + }, + target: 't_j6omof6tza8', + through: 't_qhgbwomd2t7', + sourceKey: 'id', + foreignKey: 'f_gtgjj059mye', + targetKey: 'id', + otherKey: 'f_jes256zqwr1', + }, + { + key: 'hv3kx2z8edn', + name: 'f_hpmvdltzs6m_sort', + type: 'sort', + interface: null, + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + hidden: true, + scopeKey: 'f_hpmvdltzs6m', + }, + { + key: 'q6wy6jcpdfr', + name: 'f_a4z4h45vi5b', + type: 'string', + interface: 'select', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'lx4k023vm7k', + uiSchema: { + 'x-uid': 'lx4k023vm7k', + name: 'e9u95lm0c2q', + enum: [ + { value: 'hgobsqbfnd4', label: 'SchemaInitializer', color: 'red' }, + { value: 't8prrpfk7kl', label: 'SchemaSettings', color: 'magenta' }, + { value: 'r3fxlrunwd8', label: 'SchemaComponent', color: 'lime' }, + { value: 'bidogljo0l3', label: 'CollectionField', color: 'blue' }, + { value: 'ygy1zfdr1z6', label: 'ACL', color: 'purple' }, + { value: 'ef9qf0wzpt5', label: 'DndContext', color: 'cyan' }, + { value: '45sfwd7ie30', label: 'useRequest', color: 'volcano' }, + ], + type: 'string', + 'x-component': 'Select', + title: '开发分类', + }, + }, + { + key: '936iis76zdx', + name: 'f_a4z4h45vi5b_sort', + type: 'sort', + interface: null, + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + hidden: true, + scopeKey: 'f_a4z4h45vi5b', + }, + { + key: 'zre21o0a5a9', + name: 'f_d93g4r08krl', + type: 'string', + interface: 'input', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: '96geljdh9j6', + uiSchema: { + 'x-uid': '96geljdh9j6', + name: 'zplxa37mb0y', + type: 'string', + 'x-component': 'Input', + title: '评估', + enum: [], + }, + }, + { + key: 'jasdf0kcqbs', + name: 'f_1ckuegfab9s', + type: 'text', + interface: 'textarea', + collectionName: 't_j6omof6tza8', + parentKey: null, + reverseKey: null, + uiSchemaUid: '61h9pserdqr', + uiSchema: { + 'x-uid': '61h9pserdqr', + name: 'uepfpdv289y', + type: 'string', + 'x-component': 'Input.TextArea', + title: '评估备注', + }, + }, + ], + createdBy: true, + updatedBy: true, + sortable: true, + }, + { + key: 'nlxapox70a3', + name: 't_ab12qiwruwk', + title: null, + inherit: true, + fields: [ + { + key: 'q9xx9ixxxyb', + name: 'f_m7ibo1vrvnm', + type: 'string', + interface: 'input', + collectionName: 't_ab12qiwruwk', + parentKey: '9x87w5z0ea6', + reverseKey: null, + uiSchemaUid: 'ucn6wm03zrq', + uiSchema: { + 'x-uid': 'ucn6wm03zrq', + name: 'k07s9nhhj6c', + type: 'string', + 'x-component': 'Input', + title: '名称', + }, + }, + { + key: 'y91qig4eyzm', + name: 'f_kukaw9kddyj', + type: 'belongsToMany', + interface: 'linkTo', + collectionName: 't_ab12qiwruwk', + parentKey: '9x87w5z0ea6', + reverseKey: 'tvwwt24vukv', + uiSchemaUid: '7c9ch1fknbr', + uiSchema: { + 'x-uid': '7c9ch1fknbr', + name: 'gv1k2nhucte', + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: false, fieldNames: { label: 'id', value: 'id' } }, + title: '负责人', + }, + target: 'users', + targetKey: 'id', + sourceKey: 'id', + through: 't_gsv43y8ebt8', + foreignKey: 'f_hbbvlsbe9gs', + otherKey: 'f_bhj1xy7uyc8', + }, + { + key: 'gg0xx1vln6w', + name: 'f_4mpiovytw4d', + type: 'text', + interface: 'textarea', + collectionName: 't_ab12qiwruwk', + parentKey: '9x87w5z0ea6', + reverseKey: null, + uiSchemaUid: '3xgmf0ee5fn', + uiSchema: { + 'x-uid': '3xgmf0ee5fn', + name: '28sf67whpfs', + type: 'string', + 'x-component': 'Input.TextArea', + title: '描述', + }, + }, + { + key: 'zpz6o74z9u4', + name: 'f_lxsum89wkzd', + type: 'belongsToMany', + interface: 'attachment', + collectionName: 't_ab12qiwruwk', + parentKey: '9x87w5z0ea6', + reverseKey: null, + uiSchemaUid: 'wosew16td91', + uiSchema: { + 'x-uid': 'wosew16td91', + name: 'p82ihvtkxtf', + 'x-component-props': { multiple: true, action: 'attachments:upload' }, + type: 'array', + 'x-component': 'Upload.Attachment', + title: '附件', + }, + target: 'attachments', + targetKey: 'id', + sourceKey: 'id', + through: 't_cn6cweinuw7', + foreignKey: 'f_r824sp05l43', + otherKey: 'f_8k3e4i0d04y', + }, + ], + createdBy: true, + updatedBy: true, + sortable: true, + }, + { + key: 'oftbzt8nm2o', + name: 'users', + title: '{{t("Users")}}', + inherit: false, + fields: [ + { + key: 'khov1egnsur', + name: 'nickname', + type: 'string', + interface: 'input', + collectionName: 'users', + parentKey: null, + reverseKey: null, + uiSchemaUid: '3ohfk46bf7o', + uiSchema: { + 'x-uid': '3ohfk46bf7o', + name: 'prmmf3kg8rx', + type: 'string', + title: '{{t("Nickname")}}', + 'x-component': 'Input', + }, + }, + { + key: 'u6cvmn45o6a', + name: 'email', + type: 'string', + interface: 'email', + collectionName: 'users', + parentKey: null, + reverseKey: null, + uiSchemaUid: '69ywpwux1zh', + uiSchema: { + 'x-uid': '69ywpwux1zh', + name: 'u8djuprwttj', + type: 'string', + title: '{{t("Email")}}', + 'x-component': 'Input', + require: true, + }, + unique: true, + }, + { + key: 'jwaky9982f2', + name: 'password', + type: 'password', + interface: 'password', + collectionName: 'users', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'qs374baqkh0', + uiSchema: { + 'x-uid': 'qs374baqkh0', + name: 'tf5dolxh2i4', + type: 'string', + title: '{{t("Password")}}', + 'x-component': 'Password', + }, + hidden: true, + }, + { + key: '4g5nics20go', + name: 'roles', + type: 'belongsToMany', + interface: 'linkTo', + collectionName: 'users', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'lpkacakd4my', + uiSchema: { + 'x-uid': 'lpkacakd4my', + name: '3oylvillmwu', + type: 'array', + title: '{{t("Roles")}}', + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'title', value: 'name' } }, + }, + target: 'roles', + foreignKey: 'userId', + otherKey: 'roleName', + sourceKey: 'id', + targetKey: 'name', + through: 'rolesUsers', + }, + { + key: 'cqqhyq32tvf', + name: 'appLang', + type: 'string', + interface: null, + collectionName: 'users', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + }, + { + key: 't7t97s1rwid', + name: 'token', + type: 'string', + interface: null, + collectionName: 'users', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + unique: true, + hidden: true, + }, + { + key: '5myliungho1', + name: 'resetToken', + type: 'string', + interface: null, + collectionName: 'users', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + unique: true, + hidden: true, + }, + { + key: 'q2eg83quxls', + name: 'sort', + type: 'sort', + interface: null, + collectionName: 'users', + parentKey: null, + reverseKey: null, + uiSchemaUid: null, + uiSchema: {}, + hidden: true, + }, + { + key: 's5w3l6pl1hn', + name: 'f_xd4v2uljqcu', + type: 'belongsToMany', + interface: 'linkTo', + collectionName: 'users', + parentKey: null, + reverseKey: 'kwdiruosgsb', + uiSchemaUid: 'zv4gvihowwm', + uiSchema: { + 'x-uid': 'zv4gvihowwm', + name: 'mjjpnphkkhf', + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'id', value: 'id' } }, + title: '负责人', + }, + target: 't_j6omof6tza8', + through: 't_7476m1wwsfe', + sourceKey: 'id', + foreignKey: 'f_q84aphtx7eb', + targetKey: 'id', + otherKey: 'f_9zwkbo7dd18', + }, + { + key: 'tvwwt24vukv', + name: 'f_qpsqy5rtc8t', + type: 'belongsToMany', + interface: 'linkTo', + collectionName: 'users', + parentKey: null, + reverseKey: 'y91qig4eyzm', + uiSchemaUid: '5ksz03zw47c', + uiSchema: { + 'x-uid': '5ksz03zw47c', + name: '8ukjiawzduz', + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'id', value: 'id' } }, + title: '负责人', + }, + target: 't_ab12qiwruwk', + through: 't_gsv43y8ebt8', + sourceKey: 'id', + foreignKey: 'f_bhj1xy7uyc8', + targetKey: 'id', + otherKey: 'f_hbbvlsbe9gs', + }, + ], + sortable: 'sort', + }, + { + key: 'tyztw0ulriv', + name: 't_94rsj6kbzvn', + title: '迭代', + inherit: false, + fields: [ + { + key: 'nq4mp00jwip', + name: 'f_zio9ewkxss7', + type: 'string', + interface: 'input', + collectionName: 't_94rsj6kbzvn', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'q7vois7ptlt', + uiSchema: { + 'x-uid': 'q7vois7ptlt', + name: 'o9ue1s4tu8k', + type: 'string', + 'x-component': 'Input', + title: '名称', + enum: [], + }, + }, + { + key: 'ec2833af7n1', + name: 'f_ojboh2wxpju', + type: 'text', + interface: 'textarea', + collectionName: 't_94rsj6kbzvn', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'pco1f8sq2jq', + uiSchema: { + 'x-uid': 'pco1f8sq2jq', + name: 'lb2hdydextn', + type: 'string', + 'x-component': 'Input.TextArea', + title: '描述', + enum: [], + }, + }, + { + key: '82uqmdjr3v4', + name: 'f_nunmzapigvk', + type: 'date', + interface: 'datetime', + collectionName: 't_94rsj6kbzvn', + parentKey: null, + reverseKey: null, + uiSchemaUid: '2gawk8arctt', + uiSchema: { + 'x-uid': '2gawk8arctt', + name: 'j9j7c2wyev2', + 'x-component-props': { dateFormat: 'YYYY-MM-DD', showTime: false }, + type: 'datetime', + 'x-component': 'DatePicker', + title: '开始日期', + enum: [], + }, + }, + { + key: 'mdx2eqz9ei8', + name: 'f_rberbnphu9u', + type: 'date', + interface: 'datetime', + collectionName: 't_94rsj6kbzvn', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'pj0znpff63y', + uiSchema: { + 'x-uid': 'pj0znpff63y', + name: 'c5h7agr2qy4', + 'x-component-props': { dateFormat: 'YYYY-MM-DD', showTime: false }, + type: 'datetime', + 'x-component': 'DatePicker', + title: '结束日期', + enum: [], + }, + }, + { + key: 'z70jtfabrm7', + name: 'f_rabhmdetc3p', + type: 'belongsToMany', + interface: 'linkTo', + collectionName: 't_94rsj6kbzvn', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'aun2xsfua8d', + uiSchema: { + 'x-uid': 'aun2xsfua8d', + name: 'i508c1outz2', + 'x-component': 'RecordPicker', + 'x-component-props': { multiple: true, fieldNames: { label: 'id', value: 'id' } }, + title: '任务', + enum: [], + }, + target: 't_j6omof6tza8', + through: 't_ewdf2dsjlu3', + sourceKey: 'id', + foreignKey: 'f_8do8o1wsavk', + targetKey: 'id', + otherKey: 'f_72tnpdz6kfg', + }, + ], + createdBy: true, + updatedBy: true, + sortable: true, + }, + { + key: 'vv8umfa9592', + name: 'test_sheet', + title: '测试表', + inherit: false, + fields: [ + { + key: 'q1xye1ooeol', + name: 'f_7mh6k6re0ey', + type: 'string', + interface: 'phone', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 't9ddc5n5pv8', + uiSchema: { + 'x-uid': 't9ddc5n5pv8', + name: 'd3hwsg020u8', + type: 'string', + 'x-component': 'Input', + 'x-validator': 'phone', + title: '手机', + }, + }, + { + key: '25icwn35oin', + name: 'f_turkjlzi52z', + type: 'string', + interface: 'email', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: '3bkgd97f63v', + uiSchema: { + 'x-uid': '3bkgd97f63v', + name: 'zkvhu5cm4no', + type: 'string', + 'x-component': 'Input', + 'x-validator': 'email', + title: '邮箱', + }, + }, + { + key: '3cqe0mpcrmg', + name: 'f_c14whhblhtd', + type: 'float', + interface: 'number', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: '4bajzziz060', + uiSchema: { + 'x-uid': '4bajzziz060', + name: '366t9rvl34b', + 'x-component-props': { step: '0.01', stringMode: true }, + type: 'number', + 'x-component': 'InputNumber', + title: '数字', + }, + }, + { + key: 'y39vi1z69pw', + name: 'f_z1b674d907e', + type: 'float', + interface: 'percent', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'vbnoib14uzl', + uiSchema: { + 'x-uid': 'vbnoib14uzl', + name: 'rumohzj4lh6', + 'x-component-props': { step: '0.1', stringMode: true, addonAfter: '%' }, + type: 'string', + 'x-component': 'InputNumber', + title: '百分比', + }, + }, + { + key: 'eouuwoag6ka', + name: 'f_4tzp4nhmvt2', + type: 'string', + interface: 'icon', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'qmmw3zc8l60', + uiSchema: { + 'x-uid': 'qmmw3zc8l60', + name: 'aivg5c51gc9', + type: 'string', + 'x-component': 'IconPicker', + title: '图标', + }, + }, + { + key: 'qurfmfe2hfi', + name: 'f_z0s4m3hgadd', + type: 'password', + interface: 'password', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'tkp46kn682m', + uiSchema: { + 'x-uid': 'tkp46kn682m', + name: '7q7yeobot0j', + type: 'string', + 'x-component': 'Password', + title: '密码', + }, + }, + { + key: 'ka4k5tfh3k5', + name: 'f_q5qnozezx9s', + type: 'boolean', + interface: 'checkbox', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'wgyf4ozsvdi', + uiSchema: { + 'x-uid': 'wgyf4ozsvdi', + name: 'x6agce8s18w', + type: 'boolean', + 'x-component': 'Checkbox', + title: '勾选', + }, + }, + { + key: '3dhczyw3ye2', + name: 'f_jbzwvldpyaa', + type: 'array', + interface: 'multipleSelect', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'hlmfjs2pu9t', + uiSchema: { + 'x-uid': 'hlmfjs2pu9t', + name: 'ao52nx5pune', + enum: [ + { value: '6mbqdwuzfca', label: '选项1', color: 'red' }, + { value: '9zi6s2fbj0q', label: '选项2', color: 'geekblue' }, + ], + type: 'array', + 'x-component': 'Select', + 'x-component-props': { mode: 'multiple' }, + title: '多选', + }, + defaultValue: [], + }, + { + key: 'erdrp1gi587', + name: 'f_04krym08wxq', + type: 'array', + interface: 'checkboxGroup', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: '6has8wyzohq', + uiSchema: { + 'x-uid': '6has8wyzohq', + name: 'fvwsjt58xx4', + enum: [ + { value: '4lsgdvfl7xq', label: '复选2', color: 'cyan' }, + { value: 'qzuki8bmilg', label: '复选1', color: 'purple' }, + ], + type: 'string', + 'x-component': 'Checkbox.Group', + title: '复选', + }, + defaultValue: [], + }, + { + key: 'rykbub69xu7', + name: 'f_xclbl3htmxs', + type: 'belongsToMany', + interface: 'chinaRegion', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'yml5yqzgvn8', + uiSchema: { + 'x-uid': 'yml5yqzgvn8', + name: '2ye7y09agup', + 'x-component-props': { + maxLevel: 5, + useDataSource: '{{ useChinaRegionDataSource }}', + useLoadData: '{{ useChinaRegionLoadData }}', + changeOnSelectLast: true, + labelInValue: true, + fieldNames: { label: 'name', value: 'code', children: 'children' }, + }, + type: 'array', + 'x-component': 'Cascader', + title: '中国地区', + }, + target: 'chinaRegions', + targetKey: 'code', + sortBy: 'level', + sourceKey: 'id', + through: 't_o9ry49u6xez', + foreignKey: 'f_44r8nsvdvz7', + otherKey: 'f_l0tzoymin3i', + }, + { + key: 'acm789iz922', + name: 'f_v4cgx7l5hwm', + type: 'text', + interface: 'markdown', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: '8a2erdhwcvb', + uiSchema: { + 'x-uid': '8a2erdhwcvb', + name: 'rqeiz26vuns', + type: 'string', + 'x-component': 'Markdown', + title: 'Markdown', + }, + }, + { + key: 'zmip3swglzv', + name: 'f_ztaop411cdw', + type: 'time', + interface: 'time', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'r6s6rrjzfml', + uiSchema: { + 'x-uid': 'r6s6rrjzfml', + name: 'ct893ay5apv', + 'x-component-props': { format: 'HH:mm:ss' }, + type: 'string', + 'x-component': 'TimePicker', + title: '时间', + }, + }, + { + key: 'nx1p69b6g96', + name: 'f_mf8ccm0pjmh', + type: 'date', + interface: 'datetime', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: '08p9oztyjf2', + uiSchema: { + 'x-uid': '08p9oztyjf2', + name: 'bkdnndmfpeo', + 'x-component-props': { dateFormat: 'DD/MM/YYYY', showTime: true, timeFormat: 'HH:mm:ss' }, + type: 'datetime', + 'x-component': 'DatePicker', + title: '日期', + enum: [], + }, + }, + { + key: 'ge57kma4gue', + name: 'f_dqq0xjkdqbo', + type: 'belongsTo', + interface: 'createdBy', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'zzmst1r01gc', + uiSchema: { + 'x-uid': 'zzmst1r01gc', + name: 'def1ngennlx', + type: 'object', + title: '创建人', + 'x-component': 'RecordPicker', + 'x-component-props': { fieldNames: { value: 'id', label: 'nickname' } }, + 'x-read-pretty': true, + }, + target: 'users', + foreignKey: 'createdById', + targetKey: 'id', + }, + { + key: 'yqye22n1f1c', + name: 'f_7ukoar9jevs', + type: 'date', + interface: 'createdAt', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'lgnqb28lb91', + uiSchema: { + 'x-uid': 'lgnqb28lb91', + name: 'jdjz5cngm3j', + 'x-component-props': { dateFormat: 'YYYY-MM-DD', showTime: true, timeFormat: 'HH:mm:ss' }, + type: 'datetime', + title: '创建日期', + 'x-component': 'DatePicker', + 'x-read-pretty': true, + }, + field: 'createdAt', + }, + { + key: 'n4ap9l1l4wd', + name: 'f_81orjgxlbuo', + type: 'date', + interface: 'updatedAt', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'z19egimonh9', + uiSchema: { + 'x-uid': 'z19egimonh9', + name: 'xtzu6cpnsm4', + 'x-component-props': { dateFormat: 'YYYY-MM-DD', showTime: true }, + type: 'datetime', + title: '修改日期', + 'x-component': 'DatePicker', + 'x-read-pretty': true, + }, + field: 'updatedAt', + }, + { + key: 'oqy67fm6af8', + name: 'f_e31b9oqe9ej', + type: 'belongsTo', + interface: 'updatedBy', + collectionName: 'test_sheet', + parentKey: null, + reverseKey: null, + uiSchemaUid: 'tm4f7z0nyso', + uiSchema: { + 'x-uid': 'tm4f7z0nyso', + name: '19srjtwyce1', + type: 'object', + title: '修改人', + 'x-component': 'RecordPicker', + 'x-component-props': { fieldNames: { value: 'id', label: 'nickname' } }, + 'x-read-pretty': true, + }, + target: 'users', + foreignKey: 'updatedById', + targetKey: 'id', + }, + ], + createdBy: true, + updatedBy: true, + sortable: true, + }, + ], +}; diff --git a/packages/client/src/schema-component/antd/table-v2/demos/data.ts b/packages/client/src/schema-component/antd/table-v2/demos/data.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/client/src/schema-component/antd/table-v2/demos/demo1.tsx b/packages/client/src/schema-component/antd/table-v2/demos/demo1.tsx new file mode 100644 index 000000000..937318295 --- /dev/null +++ b/packages/client/src/schema-component/antd/table-v2/demos/demo1.tsx @@ -0,0 +1,220 @@ +import { ISchema, useForm } from '@formily/react'; +import { uid } from '@formily/shared'; +import { + AntdSchemaComponentProvider, + APIClient, + APIClientProvider, + BlockSchemaComponentProvider, + CollectionManagerProvider, + SchemaComponent, + SchemaComponentProvider, + useFormBlockContext, + useTableBlockContext +} from '@nocobase/client'; +import MockAdapter from 'axios-mock-adapter'; +import { range } from 'lodash'; +import React from 'react'; +import collections from './collections'; + +function useAction() { + const ctx = useTableBlockContext(); + return { + async run() { + const params = ctx.service.params?.[0] || {}; + ctx.service.run({ ...params, page: 2 }); + }, + }; +} + +function useCreateAction() { + const ctx = useFormBlockContext(); + const form = useForm(); + return { + async run() { + console.log('form.values', form.values); + // await ctx.resource.create({ + // values: form.values, + // }); + }, + }; +} + +const formSchema: ISchema = { + type: 'void', + 'x-decorator': 'FormBlockProvider', + 'x-decorator-props': { + collection: 'users', + resource: 'users', + action: 'get', + useParams: '{{ useParamsFromRecord }}', + }, + properties: { + form: { + type: 'void', + 'x-component': 'FormV2', + 'x-component-props': { + useProps: '{{ useFormBlockProps }}', + }, + properties: { + id: { + type: 'string', + title: 'ID', + 'x-decorator': 'FormItem', + 'x-component': 'InputNumber', + 'x-read-pretty': true, + }, + nickname: { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'CollectionField', + }, + button: { + title: '提交', + 'x-component': 'Action', + 'x-component-props': { + htmlType: 'submit', + type: 'primary', + useAction: '{{ useCreateAction }}', + }, + }, + }, + }, + }, +}; + +const schema: ISchema = { + type: 'object', + properties: { + block: { + type: 'void', + 'x-decorator': 'TableBlockProvider', + 'x-decorator-props': { + collection: 'users', + resource: 'users', + action: 'list', + params: { + pageSize: 2, + }, + showIndex: true, + dragSort: false, + }, + properties: { + button: { + title: '刷新', + 'x-component': 'Action', + 'x-component-props': { + useAction, + }, + }, + table: { + type: 'array', + title: `编辑模式`, + 'x-component': 'TableV2', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + useProps: '{{ useTableBlockProps }}', + }, + properties: { + column1: { + type: 'void', + title: 'Name', + 'x-component': 'TableV2.Column', + properties: { + nickname: { + type: 'string', + 'x-component': 'CollectionField', + 'x-read-pretty': true, + }, + }, + }, + actions: { + type: 'void', + title: 'Actions', + 'x-component': 'TableV2.Column', + properties: { + view: { + type: 'void', + title: 'View', + 'x-component': 'Action', + 'x-component-props': {}, + properties: { + drawer: { + 'x-component': 'Action.Drawer', + type: 'void', + title: 'Drawer Title', + properties: { + formSchema, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +}; + +const sleep = (value: number) => new Promise((resolve) => setTimeout(resolve, value)); + +const apiClient = new APIClient({ + baseURL: 'http://localhost:3000/api', +}); + +const mock = (api: APIClient) => { + const mock = new MockAdapter(api.axios); + + mock.onGet('/users:list').reply(async (config) => { + const { page = 1, pageSize = 10 } = config.params; + await sleep(200); + return [ + 200, + { + data: range(0, pageSize).map((index) => { + return { + id: index + (page - 1) * pageSize + 1, + nickname: uid(), + }; + }), + meta: { count: 100, page, pageSize }, + }, + ]; + }); + + mock.onGet('/users:get').reply(async (config) => { + const { filterByTk } = config.params; + await sleep(200); + return [ + 200, + { + data: { + id: filterByTk, + nickname: uid(), + }, + }, + ]; + }); +}; + +mock(apiClient); + +export default () => { + return ( + + + + + + + + + + + + ); +}; diff --git a/packages/client/src/schema-component/antd/table-v2/demos/demo2.tsx b/packages/client/src/schema-component/antd/table-v2/demos/demo2.tsx new file mode 100644 index 000000000..0bcbc9719 --- /dev/null +++ b/packages/client/src/schema-component/antd/table-v2/demos/demo2.tsx @@ -0,0 +1,192 @@ +import { ISchema } from '@formily/react'; +import { + AntdSchemaComponentProvider, + APIClient, + APIClientProvider, + BlockSchemaComponentProvider, + CollectionManagerProvider, + RecordProvider, + SchemaComponent, + SchemaComponentProvider +} from '@nocobase/client'; +import React from 'react'; +import collections from './collections'; + +const schema: ISchema = { + type: 'object', + properties: { + block: { + type: 'void', + 'x-decorator': 'TableFieldProvider', + 'x-decorator-props': { + collection: 't_ab12qiwruwk', + association: 't_j6omof6tza8.f_jj9cyhron1d', + resource: 't_j6omof6tza8.f_jj9cyhron1d', + action: 'list', + params: { + // pageSize: 999, + paginate: false, + }, + showIndex: true, + dragSort: false, + }, + properties: { + button: { + type: 'void', + title: '打开', + 'x-component': 'Action', + 'x-component-props': {}, + properties: { + drawer: { + 'x-component': 'Action.Drawer', + type: 'void', + title: 'Drawer Title', + properties: { + hello1: { + 'x-content': 'Hello', + title: 'T1', + }, + }, + }, + }, + }, + table: { + type: 'array', + 'x-component': 'TableV2', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + useProps: '{{ useTableFieldProps }}', + }, + properties: { + column1: { + type: 'void', + title: 'Name', + 'x-component': 'TableV2.Column', + properties: { + f_m7ibo1vrvnm: { + type: 'string', + 'x-component': 'CollectionField', + 'x-read-pretty': true, + }, + }, + }, + }, + }, + }, + }, + }, +}; + +const sleep = (value: number) => new Promise((resolve) => setTimeout(resolve, value)); + +const apiClient = new APIClient({ + baseURL: 'http://localhost:3000/api', +}); + +// const mock = (api: APIClient) => { +// const mock = new MockAdapter(api.axios); + +// mock.onGet('/users:list').reply(async (config) => { +// const { page = 1, pageSize = 10 } = config.params; +// await sleep(2000); +// return [ +// 200, +// { +// data: range(0, pageSize).map((index) => { +// return { +// id: index + (page - 1) * pageSize, +// nickname: uid(), +// }; +// }), +// meta: { count: 100, page, pageSize }, +// }, +// ]; +// }); +// }; + +// mock(apiClient); + +const record = { + id: 40, + createdAt: '2022-03-09T00:19:13.743Z', + updatedAt: '2022-03-12T05:39:40.670Z', + createdById: 1, + updatedById: 1, + f_1ckuegfab9s: null, + f_a4z4h45vi5b: null, + f_cht6rsiiiko: null, + f_d93g4r08krl: '1', + f_f7txg1oc3nt: '2022-03-09T00:19:13.743Z', + f_g8j5jvalqh0: '开源协议', + f_hpmvdltzs6m: null, + f_tegyd222bcc: null, + f_yc8jbfiqfvh: 'c4hobfb5k07', + f_z27302tl2bf: 'vx4bhmtsuus', + f_zek99qhv0vc: '2022-03-12T05:39:40.670Z', + f_u007sq2jg93: [], + f_jj9cyhron1d: [ + { + id: 4, + createdAt: '2022-03-09T00:19:13.756Z', + updatedAt: '2022-03-12T05:39:40.703Z', + f_yzivojrp6l8: 40, + createdById: 1, + updatedById: 1, + f_4mpiovytw4d: null, + f_m7ibo1vrvnm: '协议修改为 Apache', + }, + { + id: 5, + createdAt: '2022-03-09T00:19:13.766Z', + updatedAt: '2022-03-12T05:39:40.708Z', + f_yzivojrp6l8: 40, + createdById: 1, + updatedById: 1, + f_4mpiovytw4d: null, + f_m7ibo1vrvnm: '调整开源范围', + }, + { + id: 6, + createdAt: '2022-03-09T00:19:13.772Z', + updatedAt: '2022-03-12T05:39:40.713Z', + f_yzivojrp6l8: 40, + createdById: 1, + updatedById: 1, + f_4mpiovytw4d: null, + f_m7ibo1vrvnm: '代码里补充完整版权信息', + }, + ], + f_ooar0pto2ko: [], + f_2dpc76bszit: { + id: 1, + createdAt: '2022-03-06T09:41:36.042Z', + updatedAt: '2022-03-22T02:36:45.960Z', + appLang: 'zh-CN', + email: 'admin@nocobase.com', + nickname: 'Super Admin', + }, + f_ksgzy9vmgce: [], + f_47f2d9wgofm: [], + f_qmlomqm7lvb: [], +}; + +export default () => { + return ( + + + + + + + + + + + + + + ); +}; diff --git a/packages/client/src/schema-component/antd/table-v2/index.md b/packages/client/src/schema-component/antd/table-v2/index.md new file mode 100644 index 000000000..04fd3c51d --- /dev/null +++ b/packages/client/src/schema-component/antd/table-v2/index.md @@ -0,0 +1,16 @@ +--- +nav: + path: /client +group: + path: /schema-components +--- + +# TableV2 + +## TableBlock + + + +## TableField + + diff --git a/packages/client/src/schema-component/antd/table-v2/index.ts b/packages/client/src/schema-component/antd/table-v2/index.ts new file mode 100644 index 000000000..312168f59 --- /dev/null +++ b/packages/client/src/schema-component/antd/table-v2/index.ts @@ -0,0 +1,20 @@ +import { Table } from './Table'; +import { TableActionColumnDesigner } from './Table.ActionColumnDesigner'; +import { TableColumn } from './Table.Column'; +import { TableColumnActionBar } from './Table.Column.ActionBar'; +import { TableColumnDecorator } from './Table.Column.Decorator'; +import { TableColumnDesigner } from './Table.Column.Designer'; +import { TableSelector } from './TableSelector'; + +export * from './TableBlockDesigner'; +export * from './TableField'; +export * from './TableSelectorDesigner'; + +export const TableV2 = Table; + +TableV2.Column = TableColumn; +TableV2.Column.ActionBar = TableColumnActionBar; +TableV2.Column.Decorator = TableColumnDecorator; +TableV2.Column.Designer = TableColumnDesigner; +TableV2.ActionColumnDesigner = TableActionColumnDesigner; +TableV2.Selector = TableSelector; diff --git a/packages/client/src/schema-component/antd/table/Table.Array.tsx b/packages/client/src/schema-component/antd/table/Table.Array.tsx index 01d09b46e..6e73ec4ad 100644 --- a/packages/client/src/schema-component/antd/table/Table.Array.tsx +++ b/packages/client/src/schema-component/antd/table/Table.Array.tsx @@ -14,6 +14,7 @@ const isColumnComponent = (schema: Schema) => { }; const useTableColumns = () => { + const start = Date.now(); const field = useField(); const schema = useFieldSchema(); const { exists, render } = useSchemaInitializer(schema['x-initializer']); @@ -30,6 +31,7 @@ const useTableColumns = () => { key: s.name, render: (v, record) => { const index = field.value?.indexOf(record); + console.log((Date.now() - start) / 1000); return ( diff --git a/packages/client/src/schema-component/antd/tree-select/TreeSelect.tsx b/packages/client/src/schema-component/antd/tree-select/TreeSelect.tsx index 51bf5f6b9..201b6eab8 100644 --- a/packages/client/src/schema-component/antd/tree-select/TreeSelect.tsx +++ b/packages/client/src/schema-component/antd/tree-select/TreeSelect.tsx @@ -4,7 +4,7 @@ import { TreeSelect as AntdTreeSelect } from 'antd'; import React from 'react'; import { ReadPretty } from './ReadPretty'; -export const TreeSelect = connect( +export const TreeSelect: any = connect( AntdTreeSelect, mapProps( { diff --git a/packages/client/src/schema-component/common/sortable-item/SortableItem.tsx b/packages/client/src/schema-component/common/sortable-item/SortableItem.tsx index 74397a098..7cf38628c 100644 --- a/packages/client/src/schema-component/common/sortable-item/SortableItem.tsx +++ b/packages/client/src/schema-component/common/sortable-item/SortableItem.tsx @@ -28,11 +28,15 @@ export const Sortable = (props: any) => { droppableStyle['color'] = 'rgba(241, 139, 98, .1)'; } - return React.createElement(component || 'div', { - ...others, - ref: setNodeRef, - style: droppableStyle, - }, children); + return React.createElement( + component || 'div', + { + ...others, + ref: setNodeRef, + style: droppableStyle, + }, + children, + ); }; export const SortableItem: React.FC = observer((props) => { diff --git a/packages/client/src/schema-component/core/FormProvider.tsx b/packages/client/src/schema-component/core/FormProvider.tsx index 312890b4e..1eacc8574 100644 --- a/packages/client/src/schema-component/core/FormProvider.tsx +++ b/packages/client/src/schema-component/core/FormProvider.tsx @@ -1,21 +1,21 @@ +import { createForm } from '@formily/core'; import { FormProvider as FormilyFormProvider, - IProviderProps, SchemaExpressionScopeContext, SchemaOptionsContext } from '@formily/react'; -import React, { useContext } from 'react'; +import React, { useContext, useMemo } from 'react'; import { SchemaComponentOptions } from './SchemaComponentOptions'; -export const FormProvider: React.FC = (props) => { +export const FormProvider: React.FC = (props) => { const { children, ...others } = props; let options = useContext(SchemaOptionsContext); const expressionScope = useContext(SchemaExpressionScopeContext); const scope = { ...options?.scope, ...expressionScope }; const components = { ...options?.components }; - + const form = useMemo(() => props.form || createForm(), []); return ( - + {children} diff --git a/packages/client/src/schema-component/hooks/useDesignable.tsx b/packages/client/src/schema-component/hooks/useDesignable.tsx index 4dc23de59..a9b066f67 100644 --- a/packages/client/src/schema-component/hooks/useDesignable.tsx +++ b/packages/client/src/schema-component/hooks/useDesignable.tsx @@ -107,6 +107,9 @@ export class Designable { } this.on('insertAdjacent', async ({ onSuccess, current, position, schema, wrap, removed }) => { refresh(); + if (!current['x-uid']) { + return; + } await api.request({ url: `/uiSchemas:insertAdjacent/${current['x-uid']}?position=${position}`, method: 'post', @@ -126,25 +129,27 @@ export class Designable { }); this.on('patch', async ({ schema }) => { refresh(); - if (schema?.['x-uid']) { - await api.request({ - url: `/uiSchemas:patch`, - method: 'post', - data: { - ...schema, - }, - }); + if (!schema?.['x-uid']) { + return; } + await api.request({ + url: `/uiSchemas:patch`, + method: 'post', + data: { + ...schema, + }, + }); message.success('配置保存成功!', 0.2); }); this.on('remove', async ({ removed }) => { refresh(); - if (removed?.['x-uid']) { - await api.request({ - url: `/uiSchemas:remove/${removed['x-uid']}`, - method: 'post', - }); + if (!removed?.['x-uid']) { + return; } + await api.request({ + url: `/uiSchemas:remove/${removed['x-uid']}`, + method: 'post', + }); message.success('配置保存成功!', 0.2); }); } diff --git a/packages/client/src/schema-component/hooks/useProps.ts b/packages/client/src/schema-component/hooks/useProps.ts new file mode 100644 index 000000000..f8ec83b37 --- /dev/null +++ b/packages/client/src/schema-component/hooks/useProps.ts @@ -0,0 +1,16 @@ +import { merge } from '@formily/shared'; + +interface Options { + arrayMerge?(target: any[], source: any[], options?: Options): any[]; + clone?: boolean; + assign?: boolean; + customMerge?: (key: string, options?: Options) => ((x: any, y: any) => any) | undefined; + isMergeableObject?(value: object): boolean; + cloneUnlessOtherwiseSpecified?: (value: any, options: Options) => any; +} + +export const useProps = (props: any, options?: Options) => { + const { useProps, ...props1 } = props; + let props2 = useProps?.() || {}; + return merge(props1 || {}, props2, options); +}; diff --git a/packages/client/src/schema-initializer/Initializers/DetailsActionInitializers.tsx b/packages/client/src/schema-initializer/Initializers/DetailsActionInitializers.tsx deleted file mode 100644 index 042db2e19..000000000 --- a/packages/client/src/schema-initializer/Initializers/DetailsActionInitializers.tsx +++ /dev/null @@ -1,30 +0,0 @@ -// 详情的操作配置 -export const DetailsActionInitializers = { - title: 'Configure actions', - items: [ - { - type: 'itemGroup', - title: 'Enable actions', - children: [ - { - type: 'item', - title: 'Edit', - component: 'ActionInitializer', - schema: { - title: 'Edit', - 'x-action': 'update', - }, - }, - { - type: 'item', - title: 'Delete', - component: 'ActionInitializer', - schema: { - title: 'Delete', - 'x-action': 'delete', - }, - }, - ], - }, - ], -}; diff --git a/packages/client/src/schema-initializer/Initializers/FormActionInitializers.tsx b/packages/client/src/schema-initializer/Initializers/FormActionInitializers.tsx deleted file mode 100644 index 81fa8b2c4..000000000 --- a/packages/client/src/schema-initializer/Initializers/FormActionInitializers.tsx +++ /dev/null @@ -1,27 +0,0 @@ -// 普通表单的操作配置 -export const FormActionInitializers = { - title: '{{t("Configure actions")}}', - items: [ - { - type: 'itemGroup', - title: '{{t("Enable actions")}}', - children: [ - { - type: 'item', - title: '{{t("Submit")}}', - component: 'ActionInitializer', - schema: { - title: '{{t("Submit")}}', - 'x-action': 'submit', - 'x-align': 'left', - 'x-component': 'Action', - 'x-component-props': { - type: 'primary', - useAction: '{{ cm.useCreateActionWithoutRefresh }}', - }, - }, - }, - ], - }, - ], -}; diff --git a/packages/client/src/schema-initializer/Initializers/Items/ActionInitializer.tsx b/packages/client/src/schema-initializer/Initializers/Items/ActionInitializer.tsx deleted file mode 100644 index eb7467827..000000000 --- a/packages/client/src/schema-initializer/Initializers/Items/ActionInitializer.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Switch } from 'antd'; -import React from 'react'; -import { SchemaInitializer } from '../../SchemaInitializer'; -import { useCurrentSchema } from '../utils'; - -export const ActionInitializer = (props) => { - const { item, insert } = props; - const { exists, remove } = useCurrentSchema(item.schema['x-action'], 'x-action', item.find); - - return ( - { - if (exists) { - return remove(); - } - insert({ - ...item.schema, - }); - }} - > -
- {item.title} -
-
- ); -}; diff --git a/packages/client/src/schema-initializer/Initializers/Items/AddNewActionInitializer.tsx b/packages/client/src/schema-initializer/Initializers/Items/AddNewActionInitializer.tsx deleted file mode 100644 index b5f347153..000000000 --- a/packages/client/src/schema-initializer/Initializers/Items/AddNewActionInitializer.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { Switch } from 'antd'; -import React from 'react'; -import { SchemaInitializer } from '../../SchemaInitializer'; -import { useCurrentSchema } from '../utils'; - -export const AddNewActionInitializer = (props) => { - const { item, insert } = props; - const { exists, remove } = useCurrentSchema(item?.schema?.['x-action'] || 'create', 'x-action', item.find); - - return ( - { - if (exists) { - return remove(); - } - insert({ - type: 'void', - title: '{{ t("Add new") }}', - 'x-action': 'create', - 'x-designer': 'Action.Designer', - 'x-component': 'Action', - 'x-component-props': { - type: 'primary', - openMode: 'drawer', - }, - properties: { - drawer: { - type: 'void', - title: '{{ t("Add new record") }}', - 'x-component': 'Action.Container', - 'x-component-props': {}, - 'x-decorator': 'Form', - properties: { - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-initializer': 'GridFormItemInitializers', - properties: {}, - }, - footer: { - type: 'void', - 'x-component': 'Action.Container.Footer', - properties: { - actions: { - type: 'void', - 'x-initializer': 'PopupFormActionInitializers', - 'x-decorator': 'DndContext', - 'x-component': 'ActionBar', - 'x-component-props': { - layout: 'one-column', - }, - properties: {}, - }, - }, - }, - }, - }, - }, - ...item.schema, - }); - }} - > -
- {item.title} -
-
- ); -}; diff --git a/packages/client/src/schema-initializer/Initializers/Items/CalendarBlockInitializer.tsx b/packages/client/src/schema-initializer/Initializers/Items/CalendarBlockInitializer.tsx deleted file mode 100644 index 6c5d38daf..000000000 --- a/packages/client/src/schema-initializer/Initializers/Items/CalendarBlockInitializer.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import { FormOutlined } from '@ant-design/icons'; -import { FormDialog, FormLayout } from '@formily/antd'; -import { ISchema, SchemaOptionsContext } from '@formily/react'; -import React, { useContext } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useCollectionManager } from '../../../collection-manager'; -import { SchemaComponent, SchemaComponentOptions } from '../../../schema-component'; -import { useSchemaTemplateManager } from '../../../schema-templates'; -import { SchemaInitializer } from '../../SchemaInitializer'; -import { useCollectionDataSourceItems } from '../utils'; - -const createSchema = (collectionName, { title, start, end }) => { - const schema: ISchema = { - type: 'void', - 'x-collection': 'collections', - 'x-decorator': 'ResourceActionProvider', - 'x-decorator-props': { - collection: collectionName, - request: { - resource: collectionName, - action: 'list', - params: {}, - }, - }, - 'x-designer': 'Calendar.Designer', - 'x-component': 'CardItem', - properties: { - calendar: { - type: 'void', - 'x-component': 'Calendar', - 'x-component-props': { - useDataSource: '{{ cm.useDataSourceFromRAC }}', - fieldNames: { - title, - start, - end, - }, - }, - properties: { - toolBar: { - type: 'void', - 'x-component': 'Calendar.ActionBar', - 'x-component-props': { - style: { - marginBottom: 24, - }, - }, - 'x-initializer': 'CalendarActionInitializers', - properties: {}, - }, - event: { - type: 'void', - name: 'event', - 'x-component': 'Calendar.Event', - properties: { - drawer: { - type: 'void', - 'x-decorator': 'Form', - 'x-decorator-props': { - useValues: '{{ cm.useValuesFromRecord }}', - }, - 'x-component': 'Action.Drawer', - 'x-component-props': { - className: 'nb-action-popup', - }, - title: '{{ t("View record") }}', - properties: { - tabs: { - type: 'void', - 'x-component': 'Tabs', - 'x-component-props': {}, - 'x-initializer': 'TabPaneInitializers', - properties: { - tab1: { - type: 'void', - title: '详情', - 'x-component': 'Tabs.TabPane', - 'x-designer': 'Tabs.Designer', - 'x-component-props': {}, - properties: { - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-initializer': 'RecordBlockInitializers', - properties: {}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }; - return schema; -}; - -export const CalendarBlockInitializer = (props) => { - const { insert } = props; - const { collections, getCollection } = useCollectionManager(); - const { t } = useTranslation(); - const options = useContext(SchemaOptionsContext); - const { getTemplateSchemaByMode } = useSchemaTemplateManager(); - return ( - } - onClick={async ({ item }) => { - if (item.template) { - const s = await getTemplateSchemaByMode(item); - insert(s); - return; - } - const collection = getCollection(item.name); - const stringFields = collection?.fields - ?.filter((field) => field.type === 'string') - ?.map((field) => { - return { - label: field?.uiSchema?.title, - value: field.name, - }; - }); - const dateFields = collection?.fields - ?.filter((field) => field.type === 'date') - ?.map((field) => { - return { - label: field?.uiSchema?.title, - value: field.name, - }; - }); - const values = await FormDialog('创建日历区块', () => { - return ( - - - - - - ); - }).open({ - initialValues: {}, - }); - - insert(createSchema(item.name, values)); - }} - items={useCollectionDataSourceItems('Calendar')} - /> - ); -}; diff --git a/packages/client/src/schema-initializer/Initializers/Items/CollectionFieldInitializer.tsx b/packages/client/src/schema-initializer/Initializers/Items/CollectionFieldInitializer.tsx deleted file mode 100644 index f09826b25..000000000 --- a/packages/client/src/schema-initializer/Initializers/Items/CollectionFieldInitializer.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Switch } from 'antd'; -import React from 'react'; -import { SchemaInitializer } from '../../SchemaInitializer'; -import { useCurrentSchema } from '../utils'; - -export const CollectionFieldInitializer = (props) => { - const { item, insert } = props; - const { exists, remove } = useCurrentSchema( - item.schema['x-collection-field'], - 'x-collection-field', - item.find, - item.remove, - ); - return ( - { - console.log(item, exists); - if (exists) { - return remove(); - } - insert({ - ...item.schema, - }); - }} - > -
- {item.title} -
-
- ); -}; diff --git a/packages/client/src/schema-initializer/Initializers/Items/FilterActionInitializer.tsx b/packages/client/src/schema-initializer/Initializers/Items/FilterActionInitializer.tsx deleted file mode 100644 index 02558f6f9..000000000 --- a/packages/client/src/schema-initializer/Initializers/Items/FilterActionInitializer.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { Switch } from 'antd'; -import React from 'react'; -import { SchemaInitializer } from '../../SchemaInitializer'; -import { useCurrentSchema } from '../utils'; - -const schema = { - 'x-component': 'Action', - 'x-component-props': { - popover: true, - }, - type: 'void', - title: '{{t("Filter")}}', - properties: { - popover: { - type: 'void', - 'x-decorator': 'Form', - 'x-decorator-props': {}, - 'x-component': 'Action.Popover', - 'x-component-props': { - trigger: 'click', - placement: 'bottomLeft', - }, - properties: { - filter: { - type: 'object', - default: { - $and: [{}], - }, - 'x-component': 'Filter', - 'x-component-props': { - useDataSource: '{{cm.useFilterDataSource}}', - }, - }, - footer: { - type: 'void', - 'x-component': 'Action.Popover.Footer', - properties: { - actions: { - type: 'void', - 'x-component': 'ActionBar', - properties: { - saveDefault: { - type: 'void', - 'x-component': 'Filter.SaveDefaultValue', - 'x-component-props': {}, - }, - reset: { - type: 'void', - title: '{{t("Reset")}}', - 'x-component': 'Action', - 'x-component-props': { - useAction: '{{cm.useResetFilterAction}}', - }, - }, - submit: { - type: 'void', - title: '{{t("Submit")}}', - 'x-component': 'Action', - 'x-component-props': { - type: 'primary', - useAction: '{{cm.useFilterAction}}', - }, - }, - }, - }, - }, - }, - }, - }, - }, -}; - -export const FilterActionInitializer = (props) => { - const { item, insert } = props; - const { exists, remove } = useCurrentSchema(item.schema['x-action'], 'x-action', item.find); - - return ( - { - if (exists) { - return remove(); - } - insert({ - ...schema, - ...item.schema, - }); - }} - > -
- {item.title} -
-
- ); -}; diff --git a/packages/client/src/schema-initializer/Initializers/Items/FormBlockInitializer.tsx b/packages/client/src/schema-initializer/Initializers/Items/FormBlockInitializer.tsx deleted file mode 100644 index 28273894d..000000000 --- a/packages/client/src/schema-initializer/Initializers/Items/FormBlockInitializer.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { FormOutlined } from '@ant-design/icons'; -import { ISchema } from '@formily/react'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { SchemaInitializer } from '../..'; -import { useCollectionManager } from '../../../collection-manager'; -import { useSchemaTemplateManager } from '../../../schema-templates'; -import { useCollectionDataSourceItems } from '../utils'; - -const createSchema = (collectionName) => { - const schema: ISchema = { - type: 'void', - 'x-collection': 'collections', - 'x-decorator': 'ResourceActionProvider', - 'x-decorator-props': { - collection: collectionName, - request: { - resource: collectionName, - action: 'get', - params: {}, - }, - }, - 'x-designer': 'Form.Designer', - 'x-component': 'CardItem', - properties: { - form: { - type: 'void', - 'x-decorator': 'Form', - 'x-decorator-props': {}, - properties: { - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-initializer': 'GridFormItemInitializers', - properties: {}, - }, - actions: { - type: 'void', - 'x-initializer': 'FormActionInitializers', - 'x-component': 'ActionBar', - 'x-component-props': { - layout: 'one-column', - style: { - marginTop: 24, - }, - }, - properties: {}, - }, - }, - }, - }, - }; - return schema; -}; - -export const FormBlockInitializer = (props) => { - const { insert } = props; - const { collections } = useCollectionManager(); - const { t } = useTranslation(); - const { getTemplateSchemaByMode } = useSchemaTemplateManager(); - return ( - } - onClick={async ({ item }) => { - if (item.template) { - const s = await getTemplateSchemaByMode(item); - insert(s); - } else { - insert(createSchema(item.name)); - } - }} - items={useCollectionDataSourceItems('Form')} - /> - ); -}; diff --git a/packages/client/src/schema-initializer/Initializers/Items/GeneralInitializer.tsx b/packages/client/src/schema-initializer/Initializers/Items/GeneralInitializer.tsx deleted file mode 100644 index b35b4244f..000000000 --- a/packages/client/src/schema-initializer/Initializers/Items/GeneralInitializer.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import { SchemaInitializer } from '../../SchemaInitializer'; - -export const GeneralInitializer = (props) => { - const { item, insert } = props; - return ( - { - insert({ - ...item.schema, - }); - }} - /> - ); -}; diff --git a/packages/client/src/schema-initializer/Initializers/Items/KanbanBlockInitializer.tsx b/packages/client/src/schema-initializer/Initializers/Items/KanbanBlockInitializer.tsx deleted file mode 100644 index 616230f68..000000000 --- a/packages/client/src/schema-initializer/Initializers/Items/KanbanBlockInitializer.tsx +++ /dev/null @@ -1,233 +0,0 @@ -import { FormOutlined } from '@ant-design/icons'; -import { FormDialog, FormLayout } from '@formily/antd'; -import { ISchema, SchemaOptionsContext } from '@formily/react'; -import React, { useContext } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useAPIClient } from '../../../api-client'; -import { useCollectionManager } from '../../../collection-manager'; -import { SchemaComponent, SchemaComponentOptions } from '../../../schema-component'; -import { useSchemaTemplateManager } from '../../../schema-templates'; -import { SchemaInitializer } from '../../SchemaInitializer'; -import { useCollectionDataSourceItems } from '../utils'; - -const createSchema = (collectionName, { groupField, sortName }) => { - const schema: ISchema = { - type: 'void', - 'x-collection': 'collections', - 'x-decorator': 'ResourceActionProvider', - 'x-decorator-props': { - collection: collectionName, - request: { - resource: collectionName, - action: 'list', - params: { - paginate: false, - sort: [sortName], - }, - }, - }, - 'x-designer': 'Kanban.Designer', - 'x-component': 'BlockItem', - properties: { - kanban: { - type: 'array', - name: 'kanban', - 'x-component': 'Kanban', - 'x-component-props': { - useDataSource: '{{ cm.useDataSourceFromRAC }}', - groupField: groupField?.uiSchema, - cardAdderPosition: 'bottom', - allowAddCard: { on: 'bottom' }, - disableColumnDrag: true, - useEvents: '{{ cm.useKanbanEvents }}', - }, - properties: { - card: { - type: 'void', - name: 'card', - 'x-component': 'Kanban.Card', - 'x-decorator': 'BlockItem', - 'x-designer': 'Kanban.Card.Designer', - 'x-read-pretty': true, - properties: { - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-read-pretty': true, - 'x-component-props': { - dndContext: false, - }, - properties: {}, - }, - }, - }, - cardAdder: { - type: 'void', - 'x-component': 'Kanban.CardAdder', - 'x-designer': 'Action.Designer', - 'x-designer-props': { - draggable: false, - }, - 'x-component-props': { - type: 'text', - openMode: 'drawer', - }, - title: '添加卡片', - properties: { - drawer: { - type: 'void', - title: '{{ t("Add new record") }}', - 'x-component': 'Action.Container', - 'x-component-props': {}, - 'x-decorator': 'Form', - 'x-decorator-props': { - useValues: '{{useCreateKanbanCardValues}}', - }, - properties: { - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-initializer': 'GridFormItemInitializers', - properties: {}, - }, - footer: { - type: 'void', - 'x-component': 'Action.Container.Footer', - properties: { - actions: { - type: 'void', - 'x-initializer': 'PopupFormActionInitializers', - 'x-decorator': 'DndContext', - 'x-component': 'ActionBar', - 'x-component-props': { - layout: 'one-column', - }, - properties: {}, - }, - }, - }, - }, - }, - }, - }, - cardViewer: { - type: 'void', - 'x-component': 'Kanban.CardViewer', - properties: { - drawer: { - type: 'void', - 'x-component': 'Action.Container', - 'x-component-props': { - className: 'nb-action-popup', - }, - title: '{{ t("View record") }}', - properties: { - tabs: { - type: 'void', - 'x-component': 'Tabs', - 'x-component-props': {}, - 'x-initializer': 'TabPaneInitializers', - properties: { - tab1: { - type: 'void', - title: '详情', - 'x-component': 'Tabs.TabPane', - 'x-designer': 'Tabs.Designer', - 'x-component-props': {}, - properties: { - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-initializer': 'RecordBlockInitializers', - properties: {}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }; - return schema; -}; - -export const KanbanBlockInitializer = (props) => { - const { insert } = props; - const { collections, getCollection } = useCollectionManager(); - const { t } = useTranslation(); - const { getTemplateSchemaByMode } = useSchemaTemplateManager(); - const options = useContext(SchemaOptionsContext); - const api = useAPIClient(); - return ( - } - onClick={async ({ item }) => { - if (item.template) { - const s = await getTemplateSchemaByMode(item); - insert(s); - return; - } - const collection = getCollection(item.name); - const fields = collection?.fields - ?.filter((field) => ['select', 'radioGroup'].includes(field.interface)) - ?.map((field) => { - return { - label: field?.uiSchema?.title, - value: field.name, - uiSchema: { - ...field.uiSchema, - name: field.name, - }, - }; - }); - const values = await FormDialog('创建看板区块', () => { - return ( - - - - - - ); - }).open({ - initialValues: {}, - }); - const sortName = `${values.groupField.value}_sort`; - const exists = collection?.fields?.find((field) => field.name === sortName); - if (!exists) { - await api.resource('collections.fields', item.name).create({ - values: { - type: 'sort', - name: sortName, - hidden: true, - scopeKey: values.groupField.value, - }, - }); - } - insert(createSchema(item.name, { ...values, sortName })); - }} - items={useCollectionDataSourceItems('Kanban')} - /> - ); -}; diff --git a/packages/client/src/schema-initializer/Initializers/Items/LinkToCollectionFieldInitializer.tsx b/packages/client/src/schema-initializer/Initializers/Items/LinkToCollectionFieldInitializer.tsx deleted file mode 100644 index 0f621a0ea..000000000 --- a/packages/client/src/schema-initializer/Initializers/Items/LinkToCollectionFieldInitializer.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import { ISchema } from '@formily/react'; -import { Switch } from 'antd'; -import React from 'react'; -import { SchemaInitializer } from '../../SchemaInitializer'; -import { useCurrentSchema } from '../utils'; - -export const LinkToFieldInitializer = (props) => { - const { item, insert } = props; - const { exists, remove } = useCurrentSchema( - item.schema['x-collection-field'], - 'x-collection-field', - item.find, - item.remove, - ); - const targetKey = item.field.targetKey || 'id'; - return ( - { - console.log(item, exists); - if (exists) { - return remove(); - } - insert({ - ...item.schema, - // default: [ - // { id: 1, name: 'name1' }, - // { id: 2, name: 'name2' }, - // ], - // 'x-decorator': 'FormItem', - 'x-component': 'CollectionField', - 'x-component-props': { - mode: 'tags', - fieldNames: { - label: targetKey, - value: targetKey, - }, - }, - properties: { - options: { - 'x-component': 'RecordPicker.Options', - type: 'void', - title: 'Drawer Title', - properties: { - block: { - type: 'void', - 'x-collection': 'collections', - 'x-decorator': 'ResourceActionProvider', - 'x-decorator-props': { - collection: item.field.target, - request: { - resource: item.field.target, - action: 'list', - params: { - pageSize: 20, - filter: {}, - // sort: ['sort'], - appends: [], - }, - }, - }, - 'x-designer': 'Table.Void.Designer', - 'x-component': 'CardItem', - properties: { - // actions: { - // type: 'void', - // 'x-initializer': 'TableActionInitializers', - // 'x-component': 'ActionBar', - // 'x-component-props': { - // style: { - // marginBottom: 16, - // }, - // }, - // properties: {}, - // }, - table: { - // type: 'void', - 'x-component': 'Table.RowSelection', - 'x-component-props': { - rowKey: targetKey, - objectValue: true, - rowSelection: { - type: 'checkbox', - }, - useDataSource: '{{ cm.useDataSourceFromRAC }}', - }, - 'x-initializer': 'TableColumnInitializers', - properties: { - // actions: { - // type: 'void', - // title: '{{ t("Actions") }}', - // 'x-decorator': 'Table.Column.ActionBar', - // 'x-component': 'Table.Column', - // 'x-designer': 'Table.RowActionDesigner', - // 'x-initializer': 'TableRecordActionInitializers', - // properties: { - // actions: { - // type: 'void', - // 'x-decorator': 'DndContext', - // 'x-component': 'Space', - // 'x-component-props': { - // split: '|', - // }, - // properties: {}, - // }, - // }, - // }, - }, - }, - }, - }, - }, - }, - item: { - 'x-component': 'RecordPicker.SelectedItem', - properties: { - drawer1: { - 'x-decorator': 'CollectionProvider', - 'x-decorator-props': { - name: item.field.target, - }, - 'x-component': 'Action.Drawer', - 'x-component-props': { - className: 'nb-action-popup', - }, - type: 'void', - title: 'Drawer Title', - properties: { - tabs: { - type: 'void', - 'x-component': 'Tabs', - 'x-component-props': {}, - 'x-initializer': 'TabPaneInitializers', - properties: { - tab1: { - type: 'void', - title: '详情', - 'x-component': 'Tabs.TabPane', - 'x-designer': 'Tabs.Designer', - 'x-component-props': {}, - properties: { - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-initializer': 'RecordBlockInitializers', - properties: {}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - } as ISchema); - }} - > -
- {item.title} -
-
- ); -}; diff --git a/packages/client/src/schema-initializer/Initializers/Items/MarkdownBlockInitializer.tsx b/packages/client/src/schema-initializer/Initializers/Items/MarkdownBlockInitializer.tsx deleted file mode 100644 index eeb6c822b..000000000 --- a/packages/client/src/schema-initializer/Initializers/Items/MarkdownBlockInitializer.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { FormOutlined } from '@ant-design/icons'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { SchemaInitializer } from '../..'; - -export const MarkdownBlockInitializer = (props) => { - const { insert } = props; - const { t } = useTranslation(); - return ( - } - onClick={() => { - insert({ - type: 'void', - 'x-designer': 'Markdown.Void.Designer', - 'x-decorator': 'CardItem', - 'x-component': 'Markdown.Void', - 'x-editable': false, - 'x-component-props': { - content: t('This is a demo text, **supports Markdown syntax**.'), - }, - }); - }} - /> - ); -}; diff --git a/packages/client/src/schema-initializer/Initializers/Items/RecordDetailsBlockInitializer.tsx b/packages/client/src/schema-initializer/Initializers/Items/RecordDetailsBlockInitializer.tsx deleted file mode 100644 index b817db1e3..000000000 --- a/packages/client/src/schema-initializer/Initializers/Items/RecordDetailsBlockInitializer.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { FormOutlined } from '@ant-design/icons'; -import { ISchema } from '@formily/react'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { SchemaInitializer } from '../..'; -import { useCollection } from '../../../collection-manager'; - -const createSchema = (collectionName) => { - const schema: ISchema = { - type: 'void', - 'x-decorator': 'ResourceActionProvider', - 'x-decorator-props': { - collection: collectionName, - request: { - resource: collectionName, - action: 'get', - params: {}, - }, - }, - 'x-designer': 'Form.Designer', - 'x-component': 'CardItem', - properties: { - form: { - type: 'void', - 'x-decorator': 'Form', - 'x-decorator-props': { - useValues: '{{ cm.useValuesFromRA }}', - }, - properties: { - actions: { - type: 'void', - 'x-initializer': 'RecordDetailsActionInitializers', - 'x-component': 'ActionBar', - 'x-component-props': { - style: { - marginBottom: 24, - }, - }, - properties: {}, - }, - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-read-pretty': true, - 'x-initializer': 'GridFormItemInitializers', - properties: {}, - }, - }, - }, - }, - }; - return schema; -}; - -export const RecordDetailsBlockInitializer = (props) => { - const { insert } = props; - const { name } = useCollection(); - const { t } = useTranslation(); - return ( - } - onClick={({ item }) => { - insert(createSchema(name)); - }} - /> - ); -}; diff --git a/packages/client/src/schema-initializer/Initializers/Items/RecordFormBlockInitializer.tsx b/packages/client/src/schema-initializer/Initializers/Items/RecordFormBlockInitializer.tsx deleted file mode 100644 index 9173e4efd..000000000 --- a/packages/client/src/schema-initializer/Initializers/Items/RecordFormBlockInitializer.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { FormOutlined } from '@ant-design/icons'; -import { ISchema } from '@formily/react'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { SchemaInitializer } from '../..'; -import { useCollection } from '../../../collection-manager'; - -const createSchema = (collectionName) => { - const schema: ISchema = { - type: 'void', - 'x-decorator': 'ResourceActionProvider', - 'x-decorator-props': { - collection: collectionName, - request: { - resource: collectionName, - action: 'get', - params: {}, - }, - }, - 'x-designer': 'Form.Designer', - 'x-component': 'CardItem', - properties: { - form: { - type: 'void', - 'x-decorator': 'Form', - 'x-decorator-props': { - useValues: '{{ cm.useValuesFromRA }}', - }, - properties: { - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-initializer': 'GridFormItemInitializers', - properties: {}, - }, - actions: { - type: 'void', - 'x-initializer': 'RecordFormActionInitializers', - 'x-component': 'ActionBar', - 'x-component-props': { - layout: 'one-column', - style: { - marginTop: 24, - }, - }, - properties: { - submit: { - title: '{{ t("Submit") }}', - 'x-action': 'submit', - 'x-component': 'Action', - 'x-component-props': { - type: 'primary', - useAction: '{{ cm.useUpdateViewAction }}', - }, - }, - }, - }, - }, - }, - }, - }; - return schema; -}; - -export const RecordFormBlockInitializer = (props) => { - const { insert } = props; - const { name } = useCollection(); - const { t } = useTranslation(); - return ( - } - onClick={({ item }) => { - insert(createSchema(name)); - }} - /> - ); -}; diff --git a/packages/client/src/schema-initializer/Initializers/Items/RecordRelationBlockInitializer.tsx b/packages/client/src/schema-initializer/Initializers/Items/RecordRelationBlockInitializer.tsx deleted file mode 100644 index 747edaed1..000000000 --- a/packages/client/src/schema-initializer/Initializers/Items/RecordRelationBlockInitializer.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { FormOutlined } from '@ant-design/icons'; -import { ISchema } from '@formily/react'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { SchemaInitializer } from '../..'; -import { useCollectionManager } from '../../../collection-manager'; -import { useRecord } from '../../../record-provider'; - -const createSchema = (field, record) => { - const resourceOf = record[field.sourceKey || 'id']; - const schema: ISchema = { - type: 'void', - 'x-collection': 'collections', - 'x-decorator': 'ResourceActionProvider', - 'x-decorator-props': { - collection: field.target, - association: { - name: field.name, - targetKey: field.targetKey, - sourceKey: field.sourceKey, - }, - request: { - resource: `${field.collectionName}.${field.name}`, - // resourceOf, - action: 'list', - params: { - pageSize: 20, - filter: {}, - // sort: ['sort'], - appends: [], - }, - }, - }, - 'x-designer': 'Table.Void.Designer', - 'x-component': 'CardItem', - properties: { - actions: { - type: 'void', - 'x-initializer': 'TableActionInitializers', - 'x-component': 'ActionBar', - 'x-component-props': { - style: { - marginBottom: 16, - }, - }, - properties: {}, - }, - table: { - type: 'void', - 'x-component': 'Table.Void', - 'x-component-props': { - rowKey: 'id', - rowSelection: { - type: 'checkbox', - }, - useDataSource: '{{ cm.useDataSourceFromRAC }}', - }, - 'x-initializer': 'TableColumnInitializers', - properties: { - actions: { - type: 'void', - title: '{{ t("Actions") }}', - 'x-decorator': 'Table.Column.ActionBar', - 'x-component': 'Table.Column', - 'x-designer': 'Table.RowActionDesigner', - 'x-initializer': 'TableRecordActionInitializers', - properties: { - actions: { - type: 'void', - 'x-decorator': 'DndContext', - 'x-component': 'Space', - 'x-component-props': { - split: '|', - }, - properties: {}, - }, - }, - }, - }, - }, - }, - }; - return schema; -}; - -export const RecordRelationBlockInitializer = (props) => { - const { insert } = props; - const { collections } = useCollectionManager(); - const { t } = useTranslation(); - const record = useRecord(); - return ( - } - onClick={({ item }) => { - insert(createSchema(item.field, record)); - }} - /> - ); -}; diff --git a/packages/client/src/schema-initializer/Initializers/Items/SubTableFieldInitializer.tsx b/packages/client/src/schema-initializer/Initializers/Items/SubTableFieldInitializer.tsx deleted file mode 100644 index 8934adefb..000000000 --- a/packages/client/src/schema-initializer/Initializers/Items/SubTableFieldInitializer.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import { ISchema } from '@formily/react'; -import { Switch } from 'antd'; -import React from 'react'; -import { SchemaInitializer } from '../../SchemaInitializer'; -import { useCurrentSchema } from '../utils'; - -export const SubTableFieldInitializer = (props) => { - const { item, insert } = props; - const { exists, remove } = useCurrentSchema( - item.schema['x-collection-field'], - 'x-collection-field', - item.find, - item.remove, - ); - return ( - { - console.log(item, exists); - if (exists) { - return remove(); - } - insert({ - ...item.schema, - properties: { - block: { - type: 'void', - 'x-component': 'DataSourceProvider', - 'x-component-props': { - collection: item?.field?.target, - association: { - collectionName: item.field.collectionName, - name: item.field.name, - sourceKey: item.field.sourceKey, - targetKey: item.field.targetKey, - }, - }, - properties: { - actions: { - type: 'void', - 'x-component': 'ActionBar', - 'x-component-props': { - style: { - marginBottom: 16, - }, - }, - properties: { - delete: { - type: 'void', - title: '{{ t("Delete") }}', - 'x-component': 'Action', - 'x-component-props': { - useAction: '{{ ds.useBulkDestroyAction }}', - confirm: { - title: "{{t('Delete record')}}", - content: "{{t('Are you sure you want to delete it?')}}", - }, - }, - }, - create: { - type: 'void', - title: '{{ t("Add new") }}', - 'x-action': 'create', - 'x-component': 'Action', - 'x-component-props': { - type: 'primary', - openMode: 'drawer', - }, - properties: { - drawer: { - type: 'void', - title: '{{ t("Add new record") }}', - 'x-component': 'Action.Container', - 'x-component-props': {}, - 'x-decorator': 'Form', - properties: { - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-initializer': 'GridFormItemInitializers', - properties: {}, - }, - footer: { - type: 'void', - 'x-component': 'Action.Container.Footer', - properties: { - actions: { - type: 'void', - 'x-component': 'ActionBar', - 'x-component-props': { - layout: 'one-column', - }, - properties: { - cancel: { - title: '{{ t("Cancel") }}', - 'x-action': 'cancel', - 'x-component': 'Action', - 'x-component-props': { - useAction: '{{ cm.useCancelAction }}', - }, - }, - submit: { - title: '{{ t("Submit") }}', - 'x-action': 'submit', - 'x-component': 'Action', - 'x-component-props': { - type: 'primary', - useAction: '{{ ds.useCreateAction }}', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - [item.field.name]: { - type: 'array', - title: '{{t("Fields")}}', - 'x-component': 'Table.Array', - 'x-initializer': 'TableColumnInitializers', - 'x-component-props': { - pagination: false, - expandable: { - childrenColumnName: '__nochildren__', - }, - rowSelection: { - type: 'checkbox', - }, - useSelectedRowKeys: '{{ ds.useSelectedRowKeys }}', - useDataSource: '{{ ds.useDataSource }}', - // scroll: { x: '100%' }, - }, - properties: { - actions: { - type: 'void', - title: '{{ t("Actions") }}', - 'x-decorator': 'Table.Column.ActionBar', - 'x-component': 'Table.Column', - 'x-designer': 'Table.RowActionDesigner', - 'x-initializer': 'TableFieldRecordActionInitializers', - properties: { - actions: { - type: 'void', - 'x-decorator': 'DndContext', - 'x-component': 'Space', - 'x-component-props': { - split: '|', - }, - properties: {}, - }, - }, - }, - }, - }, - }, - }, - }, - } as ISchema); - }} - > -
- {item.title} -
-
- ); -}; diff --git a/packages/client/src/schema-initializer/Initializers/Items/TableBlockInitializer.tsx b/packages/client/src/schema-initializer/Initializers/Items/TableBlockInitializer.tsx deleted file mode 100644 index a8abb5aa2..000000000 --- a/packages/client/src/schema-initializer/Initializers/Items/TableBlockInitializer.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { TableOutlined } from '@ant-design/icons'; -import { ISchema } from '@formily/react'; -import React from 'react'; -import { SchemaInitializer } from '../..'; -import { useSchemaTemplateManager } from '../../../schema-templates'; -import { useCollectionDataSourceItems } from '../utils'; - -export const createTableBlockSchema = (collectionName) => { - const schema: ISchema = { - type: 'void', - 'x-collection': 'collections', - 'x-decorator': 'ResourceActionProvider', - 'x-decorator-props': { - collection: collectionName, - dragSort: false, - request: { - resource: collectionName, - action: 'list', - params: { - pageSize: 20, - filter: {}, - // sort: ['sort'], - appends: [], - }, - }, - }, - 'x-designer': 'Table.Void.Designer', - 'x-component': 'CardItem', - properties: { - actions: { - type: 'void', - 'x-initializer': 'TableActionInitializers', - 'x-component': 'ActionBar', - 'x-component-props': { - style: { - marginBottom: 16, - }, - }, - properties: {}, - }, - table: { - type: 'void', - 'x-component': 'Table.Void', - 'x-component-props': { - rowKey: 'id', - rowSelection: { - type: 'checkbox', - }, - useAction: '{{cm.useMoveAction}}', - useDataSource: '{{cm.useDataSourceFromRAC}}', - }, - 'x-initializer': 'TableColumnInitializers', - properties: { - actions: { - type: 'void', - title: '{{ t("Actions") }}', - 'x-decorator': 'Table.Column.ActionBar', - 'x-component': 'Table.Column', - 'x-designer': 'Table.RowActionDesigner', - 'x-initializer': 'TableRecordActionInitializers', - properties: { - actions: { - type: 'void', - 'x-decorator': 'DndContext', - 'x-component': 'Space', - 'x-component-props': { - split: '|', - }, - properties: {}, - }, - }, - }, - }, - }, - }, - }; - return schema; -}; - -export const TableBlockInitializer = (props) => { - const { insert } = props; - const { getTemplateSchemaByMode } = useSchemaTemplateManager(); - return ( - } - onClick={async ({ item }) => { - if (item.template) { - const s = await getTemplateSchemaByMode(item); - insert(s); - } else { - insert(createTableBlockSchema(item.name)); - } - }} - items={useCollectionDataSourceItems('Table')} - /> - ); -}; diff --git a/packages/client/src/schema-initializer/Initializers/Items/UserFieldInitializer.tsx b/packages/client/src/schema-initializer/Initializers/Items/UserFieldInitializer.tsx deleted file mode 100644 index b403cfc2f..000000000 --- a/packages/client/src/schema-initializer/Initializers/Items/UserFieldInitializer.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { Switch } from 'antd'; -import React from 'react'; -import { SchemaInitializer } from '../../SchemaInitializer'; -import { useCurrentSchema } from '../utils'; - -export const UserFieldInitializer = (props) => { - const { item, insert } = props; - const { exists, remove } = useCurrentSchema( - item.schema['x-collection-field'], - 'x-collection-field', - item.find, - item.remove, - ); - const targetKey = item.field.targetKey || 'id'; - return ( - { - console.log(item, exists); - if (exists) { - return remove(); - } - insert({ - ...item.schema, - 'x-component': 'CollectionField', - 'x-component-props': { - mode: 'tags', - fieldNames: { - label: targetKey, - value: targetKey, - }, - }, - properties: { - item: { - 'x-component': 'RecordPicker.SelectedItem', - properties: { - drawer1: { - 'x-component': 'Action.Drawer', - type: 'void', - title: 'Drawer Title', - properties: { - details: { - type: 'void', - 'x-collection': 'collections', - 'x-decorator': 'ResourceActionProvider', - 'x-decorator-props': { - collection: item.field.target, - request: { - resource: item.field.target, - action: 'get', - params: {}, - }, - }, - 'x-designer': 'Form.Designer', - 'x-component': 'CardItem', - properties: { - form: { - type: 'void', - 'x-decorator': 'Form', - 'x-decorator-props': {}, - properties: { - actions: { - type: 'void', - 'x-initializer': 'FormActionInitializers', - 'x-component': 'ActionBar', - 'x-component-props': { - layout: 'one-column', - style: { - marginBottom: 16, - }, - }, - properties: {}, - }, - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-initializer': 'GridFormItemInitializers', - properties: {}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }); - }} - > -
- {item.title} -
-
- ); -}; diff --git a/packages/client/src/schema-initializer/Initializers/Items/index.ts b/packages/client/src/schema-initializer/Initializers/Items/index.ts deleted file mode 100644 index 902faccfe..000000000 --- a/packages/client/src/schema-initializer/Initializers/Items/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -export * from './ActionInitializer'; -export * from './AddNewActionInitializer'; -export * from './CalendarBlockInitializer'; -export * from './CollectionFieldInitializer'; -export * from './FilterActionInitializer'; -export * from './FormBlockInitializer'; -export * from './GeneralInitializer'; -export * from './KanbanBlockInitializer'; -export * from './LinkToCollectionFieldInitializer'; -export * from './MarkdownBlockInitializer'; -export * from './RecordDetailsBlockInitializer'; -export * from './RecordFormBlockInitializer'; -export * from './RecordRelationBlockInitializer'; -export * from './SubTableFieldInitializer'; -export * from './TableBlockInitializer'; -export * from './UserFieldInitializer'; - diff --git a/packages/client/src/schema-initializer/Initializers/PopupFormActionInitializers.tsx b/packages/client/src/schema-initializer/Initializers/PopupFormActionInitializers.tsx deleted file mode 100644 index 4450f2b58..000000000 --- a/packages/client/src/schema-initializer/Initializers/PopupFormActionInitializers.tsx +++ /dev/null @@ -1,41 +0,0 @@ -// 弹窗表单的操作配置 -export const PopupFormActionInitializers = { - title: "{{t('Configure actions')}}", - items: [ - { - type: 'itemGroup', - title: "{{t('Enable actions')}}", - children: [ - { - type: 'item', - title: "{{t('Submit')}}", - component: 'ActionInitializer', - schema: { - title: '{{ t("Submit") }}', - 'x-action': 'submit', - 'x-component': 'Action', - 'x-designer': 'Action.Designer', - 'x-component-props': { - type: 'primary', - useAction: '{{ cm.useCreateAction }}', - }, - }, - }, - { - type: 'item', - title: "{{t('Cancel')}}", - component: 'ActionInitializer', - schema: { - title: '{{ t("Cancel") }}', - 'x-action': 'cancel', - 'x-component': 'Action', - 'x-designer': 'Action.Designer', - 'x-component-props': { - useAction: '{{ cm.useCancelAction }}', - }, - }, - }, - ], - }, - ], -}; diff --git a/packages/client/src/schema-initializer/Initializers/RecordDetailsActionInitializers.tsx b/packages/client/src/schema-initializer/Initializers/RecordDetailsActionInitializers.tsx deleted file mode 100644 index 70a13d016..000000000 --- a/packages/client/src/schema-initializer/Initializers/RecordDetailsActionInitializers.tsx +++ /dev/null @@ -1,81 +0,0 @@ -// 普通表单的操作配置 -export const RecordDetailsActionInitializers = { - style: { - marginLeft: 8, - }, - title: '{{t("Configure actions")}}', - items: [ - { - type: 'itemGroup', - title: '{{t("Enable actions")}}', - children: [ - { - type: 'item', - title: '{{t("Edit")}}', - component: 'ActionInitializer', - schema: { - title: '{{t("Edit")}}', - type: 'void', - 'x-action': 'update', - 'x-designer': 'Action.Designer', - 'x-component': 'Action', - 'x-component-props': { - type: 'primary', - }, - properties: { - drawer: { - type: 'void', - 'x-decorator': 'Form', - 'x-decorator-props': { - useValues: '{{ cm.useValuesFromRA }}', - }, - 'x-component': 'Action.Drawer', - title: '{{ t("Edit record") }}', - properties: { - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-initializer': 'GridFormItemInitializers', - properties: {}, - }, - footer: { - type: 'void', - 'x-component': 'Action.Drawer.Footer', - properties: { - actions: { - type: 'void', - 'x-component': 'ActionBar', - 'x-component-props': { - layout: 'one-column', - }, - properties: { - cancel: { - title: '{{ t("Cancel") }}', - 'x-action': 'cancel', - 'x-component': 'Action', - 'x-component-props': { - useAction: '{{ cm.useCancelAction }}', - }, - }, - submit: { - title: '{{ t("Submit") }}', - 'x-action': 'submit', - 'x-component': 'Action', - 'x-component-props': { - type: 'primary', - useAction: '{{ cm.useUpdateAction }}', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - ], - }, - ], -}; diff --git a/packages/client/src/schema-initializer/Initializers/RecordFormActionInitializers.tsx b/packages/client/src/schema-initializer/Initializers/RecordFormActionInitializers.tsx deleted file mode 100644 index 0aabeb1e0..000000000 --- a/packages/client/src/schema-initializer/Initializers/RecordFormActionInitializers.tsx +++ /dev/null @@ -1,27 +0,0 @@ -// 普通表单的操作配置 -export const RecordFormActionInitializers = { - title: '{{t("Configure actions")}}', - items: [ - { - type: 'itemGroup', - title: '{{t("Enable actions")}}', - children: [ - { - type: 'item', - title: '{{t("Submit")}}', - component: 'ActionInitializer', - schema: { - title: '{{t("Submit")}}', - 'x-action': 'submit', - 'x-align': 'left', - 'x-component': 'Action', - 'x-component-props': { - type: 'primary', - useAction: '{{ cm.useUpdateViewAction }}', - }, - }, - }, - ], - }, - ], -}; diff --git a/packages/client/src/schema-initializer/Initializers/TableActionInitializers.tsx b/packages/client/src/schema-initializer/Initializers/TableActionInitializers.tsx deleted file mode 100644 index 2a6179646..000000000 --- a/packages/client/src/schema-initializer/Initializers/TableActionInitializers.tsx +++ /dev/null @@ -1,50 +0,0 @@ -// 表格操作配置 -export const TableActionInitializers = { - title: "{{t('Configure actions')}}", - style: { - marginLeft: 8, - }, - items: [ - { - type: 'itemGroup', - title: "{{t('Enable actions')}}", - children: [ - { - type: 'item', - title: "{{t('Filter')}}", - component: 'FilterActionInitializer', - schema: { - title: '{{ t("Filter") }}', - 'x-action': 'filter', - 'x-align': 'left', - 'x-component': 'Action', - 'x-designer': 'Action.Designer', - }, - }, - { - type: 'item', - title: "{{t('Add new')}}", - component: 'AddNewActionInitializer', - }, - { - type: 'item', - title: "{{t('Delete')}}", - component: 'ActionInitializer', - schema: { - title: '{{ t("Delete") }}', - 'x-action': 'destroy', - 'x-component': 'Action', - 'x-designer': 'Action.Designer', - 'x-component-props': { - confirm: { - title: "{{t('Delete record')}}", - content: "{{t('Are you sure you want to delete it?')}}", - }, - useAction: '{{ cm.useBulkDestroyAction }}', - }, - }, - }, - ], - }, - ], -}; diff --git a/packages/client/src/schema-initializer/Initializers/TableColumnInitializers.tsx b/packages/client/src/schema-initializer/Initializers/TableColumnInitializers.tsx deleted file mode 100644 index 5daf70b67..000000000 --- a/packages/client/src/schema-initializer/Initializers/TableColumnInitializers.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { SchemaInitializer } from '../SchemaInitializer'; -import { useTableColumnInitializerFields } from './utils'; - -// 表格列配置 -export const TableColumnInitializers = (props: any) => { - const { t } = useTranslation(); - return ( - { - return { - type: 'void', - 'x-decorator': 'Table.Column.Decorator', - 'x-designer': 'Table.Column.Designer', - 'x-component': 'Table.Column', - properties: { - [s.name]: { - 'x-read-pretty': true, - ...s, - }, - }, - }; - }} - items={[ - { - type: 'itemGroup', - title: t('Display fields'), - children: useTableColumnInitializerFields(), - }, - ]} - > - {t('Configure columns')} - - ); -}; diff --git a/packages/client/src/schema-initializer/Initializers/TableFieldRecordActionInitializers.tsx b/packages/client/src/schema-initializer/Initializers/TableFieldRecordActionInitializers.tsx deleted file mode 100644 index 784c74a8c..000000000 --- a/packages/client/src/schema-initializer/Initializers/TableFieldRecordActionInitializers.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import { MenuOutlined } from '@ant-design/icons'; -import { css } from '@emotion/css'; -import { useFieldSchema } from '@formily/react'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { SchemaInitializer } from '../..'; -import { useAPIClient } from '../../api-client'; -import { createDesignable, useDesignable } from '../../schema-component'; - -export const TableFieldRecordActionInitializers = (props: any) => { - const fieldSchema = useFieldSchema(); - const api = useAPIClient(); - const { refresh } = useDesignable(); - const { t } = useTranslation(); - return ( - { - const spaceSchema = fieldSchema.reduceProperties((buf, schema) => { - if (schema['x-component'] === 'Space') { - return schema; - } - return buf; - }, null); - if (!spaceSchema) { - return; - } - const dn = createDesignable({ - api, - refresh, - current: spaceSchema, - }); - dn.loadAPIClientEvents(); - dn.insertBeforeEnd(schema); - }} - items={[ - { - type: 'itemGroup', - title: t('Enable actions'), - children: [ - { - type: 'item', - title: t('View'), - component: 'ActionInitializer', - schema: { - title: '{{ t("View") }}', - type: 'void', - 'x-action': 'view', - 'x-designer': 'Action.Designer', - 'x-component': 'Action.Link', - 'x-component-props': {}, - properties: { - drawer: { - type: 'void', - 'x-component': 'Action.Drawer', - title: '{{ t("View record") }}', - properties: { - tabs: { - type: 'void', - 'x-component': 'Tabs', - 'x-component-props': {}, - properties: { - tab1: { - type: 'void', - title: '详情', - 'x-component': 'Tabs.TabPane', - 'x-component-props': {}, - properties: { - grid: { - type: 'void', - 'x-decorator': 'Form', - 'x-component': 'Grid', - 'x-read-pretty': true, - 'x-item-initializer': 'RecordBlockInitializer', - properties: {}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - type: 'item', - title: t('Edit'), - component: 'ActionInitializer', - schema: { - title: '{{ t("Edit") }}', - type: 'void', - 'x-action': 'update', - 'x-designer': 'Action.Designer', - 'x-component': 'Action.Link', - 'x-component-props': {}, - properties: { - drawer: { - type: 'void', - 'x-decorator': 'Form', - 'x-decorator-props': { - useValues: '{{ cm.useValuesFromRecord }}', - }, - 'x-component': 'Action.Drawer', - title: '{{ t("Edit record") }}', - properties: { - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-initializer': 'GridFormItemInitializers', - properties: {}, - }, - footer: { - type: 'void', - 'x-component': 'Action.Drawer.Footer', - properties: { - actions: { - type: 'void', - 'x-decorator': 'DndContext', - 'x-component': 'ActionBar', - 'x-component-props': { - layout: 'one-column', - }, - properties: { - cancel: { - title: '{{ t("Cancel") }}', - 'x-action': 'cancel', - 'x-component': 'Action', - 'x-component-props': { - useAction: '{{ cm.useCancelAction }}', - }, - }, - submit: { - title: '{{ t("Submit") }}', - 'x-action': 'submit', - 'x-component': 'Action', - 'x-component-props': { - type: 'primary', - useAction: '{{ ds.useUpdateAction }}', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - type: 'item', - title: t('Delete'), - component: 'ActionInitializer', - schema: { - title: '{{ t("Delete") }}', - 'x-action': 'destroy', - 'x-designer': 'Action.Designer', - 'x-component': 'Action.Link', - 'x-component-props': { - confirm: { - title: "{{t('Delete record')}}", - content: "{{t('Are you sure you want to delete it?')}}", - }, - useAction: '{{ ds.useDestroyAction }}', - }, - }, - }, - ], - }, - ]} - > - - - ); -}; diff --git a/packages/client/src/schema-initializer/Initializers/TableRecordActionInitializers.tsx b/packages/client/src/schema-initializer/Initializers/TableRecordActionInitializers.tsx deleted file mode 100644 index 623621370..000000000 --- a/packages/client/src/schema-initializer/Initializers/TableRecordActionInitializers.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import { MenuOutlined } from '@ant-design/icons'; -import { useFieldSchema } from '@formily/react'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { SchemaInitializer } from '../..'; -import { useAPIClient } from '../../api-client'; -import { createDesignable, useDesignable } from '../../schema-component'; - -export const TableRecordActionInitializers = (props: any) => { - const fieldSchema = useFieldSchema(); - const api = useAPIClient(); - const { refresh } = useDesignable(); - const { t } = useTranslation(); - return ( - { - const spaceSchema = fieldSchema.reduceProperties((buf, schema) => { - if (schema['x-component'] === 'Space') { - return schema; - } - return buf; - }, null); - if (!spaceSchema) { - return; - } - const dn = createDesignable({ - api, - refresh, - current: spaceSchema, - }); - dn.loadAPIClientEvents(); - dn.insertBeforeEnd(schema); - }} - items={[ - { - type: 'itemGroup', - title: t('Enable actions'), - children: [ - { - type: 'item', - title: t('View'), - component: 'ActionInitializer', - schema: { - title: '{{ t("View") }}', - type: 'void', - 'x-action': 'view', - 'x-designer': 'Action.Designer', - 'x-component': 'Action.Link', - 'x-component-props': { - openMode: 'drawer', - }, - properties: { - drawer: { - type: 'void', - 'x-component': 'Action.Container', - 'x-component-props': { - className: 'nb-action-popup', - }, - title: '{{ t("View record") }}', - properties: { - tabs: { - type: 'void', - 'x-component': 'Tabs', - 'x-component-props': {}, - 'x-initializer': 'TabPaneInitializers', - properties: { - tab1: { - type: 'void', - title: '详情', - 'x-component': 'Tabs.TabPane', - 'x-designer': 'Tabs.Designer', - 'x-component-props': {}, - properties: { - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-initializer': 'RecordBlockInitializers', - properties: {}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - type: 'item', - title: t('Edit'), - component: 'ActionInitializer', - schema: { - title: '{{ t("Edit") }}', - type: 'void', - 'x-action': 'update', - 'x-designer': 'Action.Designer', - 'x-component': 'Action.Link', - 'x-component-props': { - openMode: 'drawer', - }, - properties: { - drawer: { - type: 'void', - 'x-decorator': 'Form', - 'x-decorator-props': { - useValues: '{{ cm.useValuesFromRecord }}', - }, - 'x-component': 'Action.Container', - title: '{{ t("Edit record") }}', - properties: { - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-initializer': 'GridFormItemInitializers', - properties: {}, - }, - footer: { - type: 'void', - 'x-component': 'Action.Container.Footer', - properties: { - actions: { - type: 'void', - 'x-component': 'ActionBar', - 'x-component-props': { - layout: 'one-column', - }, - properties: { - cancel: { - title: '{{ t("Cancel") }}', - 'x-action': 'cancel', - 'x-component': 'Action', - 'x-component-props': { - useAction: '{{ cm.useCancelAction }}', - }, - }, - submit: { - title: '{{ t("Submit") }}', - 'x-action': 'submit', - 'x-component': 'Action', - 'x-component-props': { - type: 'primary', - useAction: '{{ cm.useUpdateAction }}', - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - type: 'item', - title: t('Delete'), - component: 'ActionInitializer', - schema: { - title: '{{ t("Delete") }}', - 'x-action': 'destroy', - 'x-designer': 'Action.Designer', - 'x-component': 'Action.Link', - 'x-component-props': { - confirm: { - title: "{{t('Delete record')}}", - content: "{{t('Are you sure you want to delete it?')}}", - }, - useAction: '{{ cm.useDestroyAction }}', - }, - }, - }, - ], - }, - ]} - component={} - /> - ); -}; diff --git a/packages/client/src/schema-initializer/Initializers/index.tsx b/packages/client/src/schema-initializer/Initializers/index.tsx deleted file mode 100644 index 6b3c7e380..000000000 --- a/packages/client/src/schema-initializer/Initializers/index.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { BlockInitializers } from './BlockInitializers'; -import { CalendarActionInitializers } from './CalendarActionInitializers'; -import { DetailsActionInitializers } from './DetailsActionInitializers'; -import { FormActionInitializers } from './FormActionInitializers'; -import { GridFormItemInitializers } from './GridFormItemInitializers'; -import * as Items from './Items'; -import { PopupFormActionInitializers } from './PopupFormActionInitializers'; -import { RecordBlockInitializers } from './RecordBlockInitializers'; -import { RecordDetailsActionInitializers } from './RecordDetailsActionInitializers'; -import { RecordFormActionInitializers } from './RecordFormActionInitializers'; -import { TableActionInitializers } from './TableActionInitializers'; -import { TableColumnInitializers } from './TableColumnInitializers'; -import { TableFieldRecordActionInitializers } from './TableFieldRecordActionInitializers'; -import { TableRecordActionInitializers } from './TableRecordActionInitializers'; -import { TabPaneInitializers } from './TabPaneInitializers'; - -export const items = { ...Items }; - -export const initializes = { - // 页面里的「添加区块」 - BlockInitializers, - // 日历的「操作配置」 - CalendarActionInitializers, - // 详情的「操作配置」 - DetailsActionInitializers, - // 普通表单的「操作配置」 - FormActionInitializers, - // Grid 组件里「配置字段」 - GridFormItemInitializers, - // 弹窗表单的「操作配置」 - PopupFormActionInitializers, - // 当前行记录所在面板的「添加区块」 - RecordBlockInitializers, - // 表格「操作配置」 - TableActionInitializers, - // 表格「列配置」 - TableColumnInitializers, - // 表格当前行记录的「操作配置」 - TableRecordActionInitializers, - // 表格字段(子表格)场景的当前行记录的「操作配置」 - TableFieldRecordActionInitializers, - // 添加标签页 - TabPaneInitializers, - - RecordDetailsActionInitializers, - - RecordFormActionInitializers, -}; diff --git a/packages/client/src/schema-initializer/Initializers/utils.ts b/packages/client/src/schema-initializer/Initializers/utils.ts deleted file mode 100644 index 285636c30..000000000 --- a/packages/client/src/schema-initializer/Initializers/utils.ts +++ /dev/null @@ -1,284 +0,0 @@ -import { ISchema, Schema, useFieldSchema } from '@formily/react'; -import { uid } from '@formily/shared'; -import { useTranslation } from 'react-i18next'; -import { SchemaInitializerItemOptions } from '../'; -import { useCollection, useCollectionManager } from '../../collection-manager'; -import { useDesignable } from '../../schema-component'; -import { useSchemaTemplateManager } from '../../schema-templates'; - -export const gridRowColWrap = (schema: ISchema) => { - return { - type: 'void', - 'x-component': 'Grid.Row', - properties: { - [uid()]: { - type: 'void', - 'x-component': 'Grid.Col', - properties: { - [schema.name || uid()]: schema, - }, - }, - }, - }; -}; - -export const removeTableColumn = (schema, cb) => { - cb(schema, { - removeParentsIfNoChildren: true, - // breakRemoveOn: (s) => { - // return s['x-component'].startsWith('Table.'); - // }, - }); -}; - -export const removeGridFormItem = (schema, cb) => { - cb(schema, { - removeParentsIfNoChildren: true, - breakRemoveOn: { - 'x-component': 'Grid', - }, - }); -}; - -const findTableColumn = (schema: Schema, key: string, action: string, deepth: number = 0) => { - return schema.reduceProperties((buf, s) => { - if (s[key] === action) { - return s; - } - const c = s.reduceProperties((buf, s) => { - if (s[key] === action) { - return s; - } - return buf; - }); - if (c) { - return c; - } - return buf; - }); -}; - -export const useTableColumnInitializerFields = () => { - const { name, fields = [] } = useCollection(); - return fields - .filter((field) => field?.interface && field?.interface !== 'subTable') - .map((field) => { - const componentProps = {}; - if ( - field?.uiSchema['x-component']?.startsWith?.('Input') || - field?.uiSchema['x-component']?.startsWith?.('Markdown') - ) { - componentProps['ellipsis'] = true; - } - - if (field.interface === 'attachment') { - componentProps['size'] = 'small'; - return { - type: 'item', - title: field?.uiSchema?.title || field.name, - component: 'CollectionFieldInitializer', - find: findTableColumn, - remove: removeTableColumn, - schema: { - name: field.name, - 'x-collection-field': `${name}.${field.name}`, - 'x-component': 'CollectionField', - 'x-component-props': { - ...componentProps, - }, - }, - } as SchemaInitializerItemOptions; - } - if (field.target) { - return { - field, - type: 'item', - title: field?.uiSchema?.title || field.name, - find: findTableColumn, - remove: removeTableColumn, - component: 'LinkToFieldInitializer', - schema: { - name: field.name, - 'x-collection-field': `${name}.${field.name}`, - 'x-component': 'CollectionField', - 'x-component-props': { - ...componentProps, - }, - }, - } as SchemaInitializerItemOptions; - } - return { - type: 'item', - title: field?.uiSchema?.title || field.name, - component: 'CollectionFieldInitializer', - find: findTableColumn, - remove: removeTableColumn, - schema: { - name: field.name, - 'x-collection-field': `${name}.${field.name}`, - 'x-component': 'CollectionField', - 'x-component-props': { - ...componentProps, - }, - }, - } as SchemaInitializerItemOptions; - }); -}; - -export const useFormItemInitializerFields = () => { - const { name, fields } = useCollection(); - return fields - ?.filter((field) => field?.interface) - ?.map((field) => { - if (field.interface === 'subTable') { - return { - type: 'item', - title: field?.uiSchema?.title || field.name, - component: 'SubTableFieldInitializer', - remove: removeGridFormItem, - field, - schema: { - type: 'void', - name: field.name, - 'x-designer': 'Table.Array.Designer', - 'x-component': 'div', - 'x-decorator': 'FormItem', - 'x-collection-field': `${name}.${field.name}`, - }, - } as SchemaInitializerItemOptions; - } - if (field.target) { - return { - type: 'item', - title: field?.uiSchema?.title || field.name, - component: 'LinkToFieldInitializer', - remove: removeGridFormItem, - field, - schema: { - name: field.name, - 'x-designer': 'FormItem.Designer', - 'x-component': 'CollectionField', - 'x-decorator': 'FormItem', - 'x-collection-field': `${name}.${field.name}`, - }, - } as SchemaInitializerItemOptions; - } - return { - type: 'item', - title: field?.uiSchema?.title || field.name, - component: 'CollectionFieldInitializer', - remove: removeGridFormItem, - schema: { - name: field.name, - title: field?.uiSchema?.title || field.name, - 'x-designer': 'FormItem.Designer', - 'x-component': 'CollectionField', - 'x-decorator': 'FormItem', - 'x-collection-field': `${name}.${field.name}`, - }, - } as SchemaInitializerItemOptions; - }); -}; - -const findSchema = (schema: Schema, key: string, action: string) => { - return schema.reduceProperties((buf, s) => { - if (s[key] === action) { - return s; - } - const c = findSchema(s, key, action); - if (c) { - return c; - } - return buf; - }); -}; - -const removeSchema = (schema, cb) => { - return cb(schema); -}; - -export const useCurrentSchema = (action: string, key: string, find = findSchema, rm = removeSchema) => { - const fieldSchema = useFieldSchema(); - const { remove } = useDesignable(); - const schema = find(fieldSchema, key, action); - return { - schema, - exists: !!schema, - remove() { - schema && rm(schema, remove); - }, - }; -}; - -export const useCollectionDataSourceItems = (componentName) => { - const { t } = useTranslation(); - const { collections } = useCollectionManager(); - const { getTemplatesByCollection } = useSchemaTemplateManager(); - return [ - { - key: 'tableBlock', - type: 'itemGroup', - title: t('Select data source'), - children: collections - ?.filter((item) => !item.inherit) - ?.map((item, index) => { - const templates = getTemplatesByCollection(item.name).filter((template) => { - return componentName && template.componentName === componentName; - }); - if (!templates.length) { - return { - type: 'item', - name: item.name, - title: item.title, - }; - } - return { - key: `${componentName}_table_subMenu_${index}`, - type: 'subMenu', - name: `${item.name}_${index}`, - title: item.title, - children: [ - { - type: 'item', - name: item.name, - title: '空白区块', - }, - { - type: 'divider', - }, - { - key: `${componentName}_table_subMenu_${index}_copy`, - type: 'subMenu', - name: 'copy', - title: '复制模板', - children: templates.map((template) => { - return { - type: 'item', - mode: 'copy', - name: item.name, - template, - title: template.name || '未命名', - }; - }), - }, - { - key: `${componentName}_table_subMenu_${index}_ref`, - type: 'subMenu', - name: 'ref', - title: '引用模板', - children: templates.map((template) => { - return { - type: 'item', - mode: 'reference', - name: item.name, - template, - title: template.name || '未命名', - }; - }), - }, - ], - }; - }), - }, - ]; -}; diff --git a/packages/client/src/schema-initializer/SchemaInitializer.tsx b/packages/client/src/schema-initializer/SchemaInitializer.tsx index d6bf8a0f4..76a423dc8 100644 --- a/packages/client/src/schema-initializer/SchemaInitializer.tsx +++ b/packages/client/src/schema-initializer/SchemaInitializer.tsx @@ -1,13 +1,15 @@ +import { css } from '@emotion/css'; import { ISchema, observer } from '@formily/react'; -import { Button, Dropdown, Menu } from 'antd'; +import { Button, Dropdown, Menu, Switch } from 'antd'; +import classNames from 'classnames'; import React, { createContext, useContext, useState } from 'react'; +import { Icon } from '../icon'; import { useCompile, useDesignable } from '../schema-component/hooks'; -import { initializes, items } from './Initializers'; import { SchemaInitializerButtonProps, SchemaInitializerItemComponent, SchemaInitializerItemOptions, - SchemaInitializerItemProps, + SchemaInitializerItemProps } from './types'; const defaultWrap = (s: ISchema) => s; @@ -16,10 +18,6 @@ export const SchemaInitializerItemContext = createContext(null); export const SchemaInitializer = () => null; -SchemaInitializer.items = items; - -SchemaInitializer.initializes = initializes; - SchemaInitializer.Button = observer((props: SchemaInitializerButtonProps) => { const { title, @@ -30,6 +28,7 @@ SchemaInitializer.Button = observer((props: SchemaInitializerButtonProps) => { dropdown, component, style, + icon, ...others } = props; const compile = useCompile(); @@ -74,16 +73,20 @@ SchemaInitializer.Button = observer((props: SchemaInitializerButtonProps) => { } if (item.type === 'itemGroup') { return ( - - {renderItems(item.children)} - + !!item.children?.length && ( + + {renderItems(item.children)} + + ) ); } if (item.type === 'subMenu') { return ( - - {renderItems(item.children)} - + !!item.children?.length && ( + + {renderItems(item.children)} + + ) ); } }); @@ -96,6 +99,17 @@ SchemaInitializer.Button = observer((props: SchemaInitializerButtonProps) => { return ( { setVisible(visible); @@ -114,6 +128,7 @@ SchemaInitializer.Button = observer((props: SchemaInitializerButtonProps) => { ...style, }} {...others} + icon={} > {compile(props.children || props.title)} @@ -196,3 +211,13 @@ SchemaInitializer.Item = (props: SchemaInitializerItemProps) => { SchemaInitializer.itemWrap = (component?: SchemaInitializerItemComponent) => { return component; }; + +SchemaInitializer.SwitchItem = (props) => { + return ( + +
+ {props.title} +
+
+ ); +}; diff --git a/packages/client/src/schema-initializer/SchemaInitializerProvider.tsx b/packages/client/src/schema-initializer/SchemaInitializerProvider.tsx index 93ab4aff0..ad16d950f 100644 --- a/packages/client/src/schema-initializer/SchemaInitializerProvider.tsx +++ b/packages/client/src/schema-initializer/SchemaInitializerProvider.tsx @@ -1,7 +1,8 @@ import { isPlainObj } from '@formily/shared'; import React, { createContext, useContext } from 'react'; import { SchemaComponentOptions } from '../schema-component'; -import { initializes as globals, items } from './Initializers'; +import * as globals from './buttons'; +import * as items from './items'; import { SchemaInitializer } from './SchemaInitializer'; export const SchemaInitializerContext = createContext({}); @@ -46,7 +47,6 @@ export const useSchemaInitializer = (name: string) => { export const SchemaInitializerProvider: React.FC = (props) => { const { initializers, components, children } = props; - return ( {children} diff --git a/packages/client/src/schema-initializer/Initializers/BlockInitializers.tsx b/packages/client/src/schema-initializer/buttons/BlockInitializers.tsx similarity index 82% rename from packages/client/src/schema-initializer/Initializers/BlockInitializers.tsx rename to packages/client/src/schema-initializer/buttons/BlockInitializers.tsx index a342ac84d..c47054039 100644 --- a/packages/client/src/schema-initializer/Initializers/BlockInitializers.tsx +++ b/packages/client/src/schema-initializer/buttons/BlockInitializers.tsx @@ -1,30 +1,36 @@ -import { gridRowColWrap } from './utils'; +import { gridRowColWrap } from '../utils'; // 页面里添加区块 export const BlockInitializers = { title: '{{t("Add block")}}', + icon: 'PlusOutlined', wrap: gridRowColWrap, items: [ { + key: 'dataBlocks', type: 'itemGroup', title: 'Data blocks', children: [ { + key: 'table', type: 'item', title: 'Table', component: 'TableBlockInitializer', }, { + key: 'form', type: 'item', title: 'Form', component: 'FormBlockInitializer', }, { + key: 'calendar', type: 'item', title: 'Calendar', component: 'CalendarBlockInitializer', }, { + key: 'kanban', type: 'item', title: 'Kanban', component: 'KanbanBlockInitializer', diff --git a/packages/client/src/schema-initializer/Initializers/CalendarActionInitializers.tsx b/packages/client/src/schema-initializer/buttons/CalendarActionInitializers.tsx similarity index 78% rename from packages/client/src/schema-initializer/Initializers/CalendarActionInitializers.tsx rename to packages/client/src/schema-initializer/buttons/CalendarActionInitializers.tsx index b0812c791..3a81143e5 100644 --- a/packages/client/src/schema-initializer/Initializers/CalendarActionInitializers.tsx +++ b/packages/client/src/schema-initializer/buttons/CalendarActionInitializers.tsx @@ -1,6 +1,7 @@ // 日历的操作配置 export const CalendarActionInitializers = { title: 'Configure actions', + icon: 'SettingOutlined', style: { marginLeft: 8 }, items: [ { @@ -13,7 +14,7 @@ export const CalendarActionInitializers = { component: 'ActionInitializer', schema: { title: 'Today', - 'x-component': 'Calendar.Today', + 'x-component': 'CalendarV2.Today', 'x-action': `calendar:today`, 'x-align': 'left', }, @@ -23,8 +24,8 @@ export const CalendarActionInitializers = { title: 'Turn pages', component: 'ActionInitializer', schema: { - title: 'Turn the page', - 'x-component': 'Calendar.Nav', + title: 'Turn pages', + 'x-component': 'CalendarV2.Nav', 'x-action': `calendar:nav`, 'x-align': 'left', }, @@ -35,7 +36,7 @@ export const CalendarActionInitializers = { component: 'ActionInitializer', schema: { title: 'Title', - 'x-component': 'Calendar.Title', + 'x-component': 'CalendarV2.Title', 'x-action': `calendar:title`, 'x-align': 'left', }, @@ -46,7 +47,7 @@ export const CalendarActionInitializers = { component: 'ActionInitializer', schema: { title: 'Select view', - 'x-component': 'Calendar.ViewSelect', + 'x-component': 'CalendarV2.ViewSelect', 'x-action': `calendar:viewSelect`, 'x-align': 'right', 'x-designer': 'Action.Designer', @@ -57,17 +58,16 @@ export const CalendarActionInitializers = { title: "{{t('Filter')}}", component: 'FilterActionInitializer', schema: { - title: '{{ t("Filter") }}', - 'x-action': 'filter', 'x-align': 'right', - 'x-component': 'Action', - 'x-designer': 'Action.Designer', }, }, { type: 'item', title: '{{ t("Add new") }}', - component: 'AddNewActionInitializer', + component: 'CreateActionInitializer', + schema: { + 'x-align': 'right', + }, }, ], }, diff --git a/packages/client/src/schema-initializer/buttons/CreateFormBlockInitializers.tsx b/packages/client/src/schema-initializer/buttons/CreateFormBlockInitializers.tsx new file mode 100644 index 000000000..dc3f1f98e --- /dev/null +++ b/packages/client/src/schema-initializer/buttons/CreateFormBlockInitializers.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { SchemaInitializer } from '../..'; +import { gridRowColWrap } from '../utils'; + +export const CreateFormBlockInitializers = (props: any) => { + const { t } = useTranslation(); + return ( + + ); +}; diff --git a/packages/client/src/schema-initializer/buttons/FormActionInitializers.tsx b/packages/client/src/schema-initializer/buttons/FormActionInitializers.tsx new file mode 100644 index 000000000..97923f171 --- /dev/null +++ b/packages/client/src/schema-initializer/buttons/FormActionInitializers.tsx @@ -0,0 +1,54 @@ +// 表单的操作配置 +export const FormActionInitializers = { + title: '{{t("Configure actions")}}', + icon: 'SettingOutlined', + items: [ + { + type: 'itemGroup', + title: '{{t("Enable actions")}}', + children: [ + { + type: 'item', + title: '{{t("Submit")}}', + component: 'CreateSubmitActionInitializer', + }, + ], + }, + ], +}; + +export const CreateFormActionInitializers = { + title: '{{t("Configure actions")}}', + icon: 'SettingOutlined', + items: [ + { + type: 'itemGroup', + title: '{{t("Enable actions")}}', + children: [ + { + type: 'item', + title: '{{t("Submit")}}', + component: 'CreateSubmitActionInitializer', + }, + ], + }, + ], +}; + +export const UpdateFormActionInitializers = { + title: '{{t("Configure actions")}}', + icon: 'SettingOutlined', + items: [ + { + type: 'itemGroup', + title: '{{t("Enable actions")}}', + children: [ + { + type: 'item', + title: '{{t("Submit")}}', + component: 'UpdateSubmitActionInitializer', + }, + ], + }, + ], +}; diff --git a/packages/client/src/schema-initializer/buttons/FormItemInitializers.tsx b/packages/client/src/schema-initializer/buttons/FormItemInitializers.tsx new file mode 100644 index 000000000..d71df2db7 --- /dev/null +++ b/packages/client/src/schema-initializer/buttons/FormItemInitializers.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { SchemaInitializer } from '../SchemaInitializer'; +import { gridRowColWrap, useFormItemInitializerFields } from '../utils'; + +// 表单里配置字段 +export const FormItemInitializers = (props: any) => { + const { t } = useTranslation(); + return ( + + {t('Configure fields')} + + ); +}; diff --git a/packages/client/src/schema-initializer/buttons/KanbanCardFormItemInitializers.tsx b/packages/client/src/schema-initializer/buttons/KanbanCardFormItemInitializers.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/packages/client/src/schema-initializer/buttons/ReadPrettyFormActionInitializers.tsx b/packages/client/src/schema-initializer/buttons/ReadPrettyFormActionInitializers.tsx new file mode 100644 index 000000000..391197288 --- /dev/null +++ b/packages/client/src/schema-initializer/buttons/ReadPrettyFormActionInitializers.tsx @@ -0,0 +1,35 @@ +// 表单的操作配置 +export const ReadPrettyFormActionInitializers = { + title: '{{t("Configure actions")}}', + icon: 'SettingOutlined', + style: { + marginLeft: 8, + }, + items: [ + { + type: 'itemGroup', + title: '{{t("Enable actions")}}', + children: [ + { + type: 'item', + title: '{{t("Edit")}}', + component: 'UpdateActionInitializer', + schema: { + 'x-component': 'Action', + 'x-component-props': { + type: 'primary', + }, + }, + }, + { + type: 'item', + title: '{{t("Delete")}}', + component: 'DestroyActionInitializer', + schema: { + 'x-component': 'Action', + }, + }, + ], + }, + ], +}; diff --git a/packages/client/src/schema-initializer/Initializers/GridFormItemInitializers.tsx b/packages/client/src/schema-initializer/buttons/ReadPrettyFormItemInitializers.tsx similarity index 82% rename from packages/client/src/schema-initializer/Initializers/GridFormItemInitializers.tsx rename to packages/client/src/schema-initializer/buttons/ReadPrettyFormItemInitializers.tsx index 048f42d23..598235146 100644 --- a/packages/client/src/schema-initializer/Initializers/GridFormItemInitializers.tsx +++ b/packages/client/src/schema-initializer/buttons/ReadPrettyFormItemInitializers.tsx @@ -1,14 +1,14 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { SchemaInitializer } from '../SchemaInitializer'; -import { gridRowColWrap, useFormItemInitializerFields } from './utils'; +import { gridRowColWrap, useFormItemInitializerFields } from '../utils'; -// Grid 组件里配置字段 -export const GridFormItemInitializers = (props: any) => { +export const ReadPrettyFormItemInitializers = (props: any) => { const { t } = useTranslation(); return ( { { type: 'item', title: t('Add text'), - component: 'GeneralInitializer', + component: 'BlockInitializer', schema: { type: 'void', 'x-editable': false, diff --git a/packages/client/src/schema-initializer/Initializers/RecordBlockInitializers.tsx b/packages/client/src/schema-initializer/buttons/RecordBlockInitializers.tsx similarity index 65% rename from packages/client/src/schema-initializer/Initializers/RecordBlockInitializers.tsx rename to packages/client/src/schema-initializer/buttons/RecordBlockInitializers.tsx index fd0905208..5a7ecfcdc 100644 --- a/packages/client/src/schema-initializer/Initializers/RecordBlockInitializers.tsx +++ b/packages/client/src/schema-initializer/buttons/RecordBlockInitializers.tsx @@ -1,10 +1,7 @@ -import { useFieldSchema } from '@formily/react'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { SchemaInitializer, useCollection } from '../..'; -import { useAPIClient } from '../../api-client'; -import { useDesignable } from '../../schema-component'; -import { gridRowColWrap } from './utils'; +import { gridRowColWrap } from '../utils'; const useRelationFields = () => { const { fields } = useCollection(); @@ -12,35 +9,35 @@ const useRelationFields = () => { .filter((field) => field.interface === 'linkTo') .map((field) => { return { + key: field.name, type: 'item', field, title: field?.uiSchema?.title || field.name, - component: 'RecordRelationBlockInitializer', + component: 'RecordAssociationBlockInitializer', }; }) as any; }; -// 当前行记录所在面板的添加区块 export const RecordBlockInitializers = (props: any) => { - const fieldSchema = useFieldSchema(); - const api = useAPIClient(); - const { refresh } = useDesignable(); const { t } = useTranslation(); return ( { title: 'Media', children: [ { + key: 'markdown', type: 'item', title: 'Markdown', component: 'MarkdownBlockInitializer', }, ], }, - { - type: 'itemGroup', - title: '{{t("Others")}}', - children: [ - { - type: 'item', - title: '{{t("Action logs")}}', - component: 'ActionLogBlockInitializer', - }, - ], - }, ]} /> ); diff --git a/packages/client/src/schema-initializer/buttons/RecordFormBlockInitializers.tsx b/packages/client/src/schema-initializer/buttons/RecordFormBlockInitializers.tsx new file mode 100644 index 000000000..6a3362d1e --- /dev/null +++ b/packages/client/src/schema-initializer/buttons/RecordFormBlockInitializers.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { SchemaInitializer } from '../..'; +import { gridRowColWrap } from '../utils'; + +export const RecordFormBlockInitializers = (props: any) => { + const { t } = useTranslation(); + return ( + + ); +}; diff --git a/packages/client/src/schema-initializer/buttons/SubTableActionInitializers.tsx b/packages/client/src/schema-initializer/buttons/SubTableActionInitializers.tsx new file mode 100644 index 000000000..86e72c2c6 --- /dev/null +++ b/packages/client/src/schema-initializer/buttons/SubTableActionInitializers.tsx @@ -0,0 +1,39 @@ +// 表格操作配置 +export const SubTableActionInitializers = { + title: "{{t('Configure actions')}}", + icon: 'SettingOutlined', + style: { + marginLeft: 8, + }, + // size: 'small', + items: [ + { + type: 'itemGroup', + title: "{{t('Enable actions')}}", + children: [ + { + type: 'item', + title: "{{t('Add new')}}", + component: 'CreateActionInitializer', + schema: { + 'x-align': 'right', + // 'x-component-props': { + // size: 'small', + // }, + }, + }, + { + type: 'item', + title: "{{t('Delete')}}", + component: 'BulkDestroyActionInitializer', + schema: { + 'x-align': 'right', + // 'x-component-props': { + // size: 'small', + // }, + }, + }, + ], + }, + ], +}; diff --git a/packages/client/src/schema-initializer/Initializers/TabPaneInitializers.tsx b/packages/client/src/schema-initializer/buttons/TabPaneInitializers.tsx similarity index 98% rename from packages/client/src/schema-initializer/Initializers/TabPaneInitializers.tsx rename to packages/client/src/schema-initializer/buttons/TabPaneInitializers.tsx index b3345267f..788a0073d 100644 --- a/packages/client/src/schema-initializer/Initializers/TabPaneInitializers.tsx +++ b/packages/client/src/schema-initializer/buttons/TabPaneInitializers.tsx @@ -43,6 +43,7 @@ export const TabPaneInitializers = () => { type: 'void', 'x-component': 'Action', 'x-component-props': { + icon: 'PlusOutlined', style: { borderColor: 'rgb(241, 139, 98)', color: 'rgb(241, 139, 98)', diff --git a/packages/client/src/schema-initializer/buttons/TableActionColumnInitializers.tsx b/packages/client/src/schema-initializer/buttons/TableActionColumnInitializers.tsx new file mode 100644 index 000000000..181100762 --- /dev/null +++ b/packages/client/src/schema-initializer/buttons/TableActionColumnInitializers.tsx @@ -0,0 +1,73 @@ +import { MenuOutlined } from '@ant-design/icons'; +import { useFieldSchema } from '@formily/react'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { SchemaInitializer } from '../..'; +import { useAPIClient } from '../../api-client'; +import { createDesignable, useDesignable } from '../../schema-component'; + +export const TableActionColumnInitializers = (props: any) => { + const fieldSchema = useFieldSchema(); + const api = useAPIClient(); + const { refresh } = useDesignable(); + const { t } = useTranslation(); + return ( + { + const spaceSchema = fieldSchema.reduceProperties((buf, schema) => { + if (schema['x-component'] === 'Space') { + return schema; + } + return buf; + }, null); + if (!spaceSchema) { + return; + } + const dn = createDesignable({ + api, + refresh, + current: spaceSchema, + }); + dn.loadAPIClientEvents(); + dn.insertBeforeEnd(schema); + }} + items={[ + { + type: 'itemGroup', + title: t('Enable actions'), + children: [ + { + type: 'item', + title: t('View'), + component: 'ViewActionInitializer', + schema: { + 'x-component': 'Action.Link', + 'x-action': 'view', + }, + }, + { + type: 'item', + title: t('Edit'), + component: 'UpdateActionInitializer', + schema: { + 'x-component': 'Action.Link', + 'x-action': 'update', + }, + }, + { + type: 'item', + title: t('Delete'), + component: 'DestroyActionInitializer', + schema: { + 'x-component': 'Action.Link', + 'x-action': 'destroy', + }, + }, + ], + }, + ]} + component={} + /> + ); +}; diff --git a/packages/client/src/schema-initializer/buttons/TableActionInitializers.tsx b/packages/client/src/schema-initializer/buttons/TableActionInitializers.tsx new file mode 100644 index 000000000..5afc24aae --- /dev/null +++ b/packages/client/src/schema-initializer/buttons/TableActionInitializers.tsx @@ -0,0 +1,40 @@ +// 表格操作配置 +export const TableActionInitializers = { + title: "{{t('Configure actions')}}", + icon: 'SettingOutlined', + style: { + marginLeft: 8, + }, + items: [ + { + type: 'itemGroup', + title: "{{t('Enable actions')}}", + children: [ + { + type: 'item', + title: "{{t('Filter')}}", + component: 'FilterActionInitializer', + schema: { + 'x-align': 'left', + }, + }, + { + type: 'item', + title: "{{t('Add new')}}", + component: 'CreateActionInitializer', + schema: { + 'x-align': 'right', + }, + }, + { + type: 'item', + title: "{{t('Delete')}}", + component: 'BulkDestroyActionInitializer', + schema: { + 'x-align': 'right', + }, + }, + ], + }, + ], +}; diff --git a/packages/client/src/schema-initializer/buttons/TableColumnInitializers.tsx b/packages/client/src/schema-initializer/buttons/TableColumnInitializers.tsx new file mode 100644 index 000000000..719b0d9e2 --- /dev/null +++ b/packages/client/src/schema-initializer/buttons/TableColumnInitializers.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { SchemaInitializer } from '../SchemaInitializer'; +import { itemsMerge, useTableColumnInitializerFields } from '../utils'; + +// 表格列配置 +export const TableColumnInitializers = (props: any) => { + const { items = [] } = props; + const { t } = useTranslation(); + return ( + { + if (s['x-action-column']) { + return s; + } + return { + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-designer': 'TableV2.Column.Designer', + 'x-component': 'TableV2.Column', + properties: { + [s.name]: { + ...s, + }, + }, + }; + }} + items={itemsMerge( + [ + { + type: 'itemGroup', + title: t('Display fields'), + children: useTableColumnInitializerFields(), + }, + { + type: 'divider', + }, + { + type: 'item', + title: t('Action column'), + component: 'TableActionColumnInitializer', + }, + ], + items, + )} + > + {t('Configure columns')} + + ); +}; diff --git a/packages/client/src/schema-initializer/buttons/TableSelectorInitializers.tsx b/packages/client/src/schema-initializer/buttons/TableSelectorInitializers.tsx new file mode 100644 index 000000000..8b03306c2 --- /dev/null +++ b/packages/client/src/schema-initializer/buttons/TableSelectorInitializers.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { SchemaInitializer } from '../..'; +import { gridRowColWrap } from '../utils'; + +export const TableSelectorInitializers = (props: any) => { + const { t } = useTranslation(); + return ( + + ); +}; diff --git a/packages/client/src/schema-initializer/buttons/index.ts b/packages/client/src/schema-initializer/buttons/index.ts new file mode 100644 index 000000000..22b1f37f1 --- /dev/null +++ b/packages/client/src/schema-initializer/buttons/index.ts @@ -0,0 +1,16 @@ +export * from './BlockInitializers'; +export * from './CalendarActionInitializers'; +export * from './CreateFormBlockInitializers'; +export * from './FormActionInitializers'; +export * from './FormItemInitializers'; +export * from './ReadPrettyFormActionInitializers'; +export * from './ReadPrettyFormItemInitializers'; +export * from './RecordBlockInitializers'; +export * from './RecordFormBlockInitializers'; +export * from './SubTableActionInitializers'; +export * from './TableActionColumnInitializers'; +export * from './TableActionInitializers'; +export * from './TableColumnInitializers'; +export * from './TableSelectorInitializers'; +export * from './TabPaneInitializers'; + diff --git a/packages/client/src/schema-initializer/items/index.tsx b/packages/client/src/schema-initializer/items/index.tsx new file mode 100644 index 000000000..b8e3cb9bc --- /dev/null +++ b/packages/client/src/schema-initializer/items/index.tsx @@ -0,0 +1,679 @@ +import { FormOutlined, TableOutlined } from '@ant-design/icons'; +import { FormDialog, FormLayout } from '@formily/antd'; +import { ISchema, SchemaOptionsContext } from '@formily/react'; +import { merge } from '@formily/shared'; +import React, { useContext } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useAPIClient } from '../../api-client'; +import { useBlockAssociationContext } from '../../block-provider'; +import { useCollection, useCollectionManager } from '../../collection-manager'; +import { SchemaComponent, SchemaComponentOptions } from '../../schema-component'; +import { useSchemaTemplateManager } from '../../schema-templates'; +import { SchemaInitializer } from '../SchemaInitializer'; +import { + createCalendarBlockSchema, + createFormBlockSchema, + createKanbanBlockSchema, + createReadPrettyFormBlockSchema, + createTableBlockSchema, + createTableSelectorSchema, + useCollectionDataSourceItems, + useCurrentSchema, + useRecordCollectionDataSourceItems +} from '../utils'; + +// Block +export const BlockInitializer = (props) => { + const { item, insert } = props; + return ( + { + insert({ + ...item.schema, + }); + }} + /> + ); +}; + +export const InitializerWithSwitch = (props) => { + const { type, schema, item, insert } = props; + const { exists, remove } = useCurrentSchema(schema?.[type] || item?.schema?.[type], type, item.find, item.remove); + return ( + { + if (exists) { + return remove(); + } + const s = merge(schema || {}, item.schema || {}); + item?.schemaInitialize?.(s); + insert(s); + }} + /> + ); +}; + +export const ActionInitializer = (props) => { + return ; +}; + +export const DataBlockInitializer = (props) => { + const { onCreateBlockSchema, componentType, createBlockSchema, insert, ...others } = props; + const { getTemplateSchemaByMode } = useSchemaTemplateManager(); + return ( + } + {...others} + onClick={async ({ item }) => { + if (item.template) { + const s = await getTemplateSchemaByMode(item); + insert(s); + } else { + if (onCreateBlockSchema) { + onCreateBlockSchema({ item }); + } else if (createBlockSchema) { + insert(createBlockSchema({ collection: item.name })); + } + } + }} + items={useCollectionDataSourceItems(componentType)} + /> + ); +}; + +export const TableBlockInitializer = (props) => { + const { insert } = props; + const { getCollection } = useCollectionManager(); + return ( + } + componentType={'Table'} + onCreateBlockSchema={async ({ item }) => { + const collection = getCollection(item.name); + const schema = createTableBlockSchema({ collection: item.name, rowKey: collection.filterTargetKey || 'id' }); + insert(schema); + }} + /> + ); +}; + +export const FormBlockInitializer = (props) => { + return ( + } + componentType={'Form'} + createBlockSchema={createFormBlockSchema} + /> + ); +}; + +export const CalendarBlockInitializer = (props) => { + const { insert } = props; + const { getCollection } = useCollectionManager(); + const options = useContext(SchemaOptionsContext); + return ( + } + onCreateBlockSchema={async ({ item }) => { + const collection = getCollection(item.name); + const stringFields = collection?.fields + ?.filter((field) => field.type === 'string') + ?.map((field) => { + return { + label: field?.uiSchema?.title, + value: field.name, + }; + }); + const dateFields = collection?.fields + ?.filter((field) => field.type === 'date') + ?.map((field) => { + return { + label: field?.uiSchema?.title, + value: field.name, + }; + }); + const values = await FormDialog('创建日历区块', () => { + return ( + + + + + + ); + }).open({ + initialValues: {}, + }); + insert( + createCalendarBlockSchema({ + collection: item.name, + fieldNames: { + ...values, + }, + }), + ); + }} + /> + ); +}; + +export const KanbanBlockInitializer = (props) => { + const { insert } = props; + const { getCollection } = useCollectionManager(); + const options = useContext(SchemaOptionsContext); + const api = useAPIClient(); + return ( + } + onCreateBlockSchema={async ({ item }) => { + const collection = getCollection(item.name); + const fields = collection?.fields + ?.filter((field) => ['select', 'radioGroup'].includes(field.interface)) + ?.map((field) => { + return { + label: field?.uiSchema?.title, + value: field.name, + uiSchema: { + ...field.uiSchema, + name: field.name, + }, + }; + }); + const values = await FormDialog('创建看板区块', () => { + return ( + + + + + + ); + }).open({ + initialValues: {}, + }); + const sortName = `${values.groupField.value}_sort`; + const exists = collection?.fields?.find((field) => field.name === sortName); + if (!exists) { + await api.resource('collections.fields', item.name).create({ + values: { + type: 'sort', + name: sortName, + hidden: true, + scopeKey: values.groupField.value, + }, + }); + } + insert( + createKanbanBlockSchema({ + groupField: values.groupField.value, + collection: item.name, + params: { + sort: [sortName], + paginate: false, + }, + }), + ); + }} + /> + ); +}; + +export const MarkdownBlockInitializer = (props) => { + const { insert } = props; + const { t } = useTranslation(); + return ( + } + onClick={() => { + insert({ + type: 'void', + 'x-designer': 'Markdown.Void.Designer', + 'x-decorator': 'CardItem', + 'x-component': 'Markdown.Void', + 'x-editable': false, + 'x-component-props': { + content: t('This is a demo text, **supports Markdown syntax**.'), + }, + }); + }} + /> + ); +}; + +export const FilterActionInitializer = (props) => { + const schema = { + type: 'void', + title: '{{ t("Filter") }}', + 'x-action': 'filter', + 'x-designer': 'Filter.Action.Designer', + 'x-component': 'Filter.Action', + 'x-component-props': { + icon: 'FilterOutlined', + useProps: '{{ useFilterActionProps }}', + }, + }; + return ; +}; + +export const CreateActionInitializer = (props) => { + const schema = { + type: 'void', + title: '{{ t("Add new") }}', + 'x-action': 'create', + 'x-designer': 'Action.Designer', + 'x-component': 'Action', + 'x-component-props': { + icon: 'PlusOutlined', + openMode: 'drawer', + type: 'primary', + }, + properties: { + drawer: { + type: 'void', + title: '{{ t("Add record") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'CreateFormBlockInitializers', + properties: {}, + }, + }, + }, + }, + }; + return ; +}; + +export const ViewActionInitializer = (props) => { + const schema = { + type: 'void', + title: '{{ t("View") }}', + 'x-action': 'view', + 'x-designer': 'Action.Designer', + 'x-component': 'Action', + 'x-component-props': { + openMode: 'drawer', + }, + properties: { + drawer: { + type: 'void', + title: '{{ t("View record") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + tabs: { + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'TabPaneInitializers', + properties: { + tab1: { + type: 'void', + title: '详情', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + properties: { + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'RecordBlockInitializers', + properties: {}, + }, + }, + }, + }, + }, + }, + }, + }, + }; + return ; +}; + +export const UpdateActionInitializer = (props) => { + const schema = { + type: 'void', + title: '{{ t("Edit") }}', + 'x-action': 'update', + 'x-designer': 'Action.Designer', + 'x-component': 'Action', + 'x-component-props': { + openMode: 'drawer', + icon: 'EditOutlined', + }, + properties: { + drawer: { + type: 'void', + title: '{{ t("Edit record") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'RecordFormBlockInitializers', + properties: {}, + }, + }, + }, + }, + }; + return ; +}; + +export const DestroyActionInitializer = (props) => { + const schema = { + title: '{{ t("Delete") }}', + 'x-action': 'destroy', + 'x-component': 'Action', + 'x-designer': 'Action.Designer', + 'x-component-props': { + icon: 'DeleteOutlined', + confirm: { + title: "{{t('Delete record')}}", + content: "{{t('Are you sure you want to delete it?')}}", + }, + useProps: '{{ useDestroyActionProps }}', + }, + }; + return ; +}; + +export const BulkDestroyActionInitializer = (props) => { + const schema = { + title: '{{ t("Delete") }}', + 'x-action': 'destroy', + 'x-component': 'Action', + 'x-designer': 'Action.Designer', + 'x-component-props': { + icon: 'DeleteOutlined', + confirm: { + title: "{{t('Delete record')}}", + content: "{{t('Are you sure you want to delete it?')}}", + }, + useProps: '{{ useBulkDestroyActionProps }}', + }, + }; + return ; +}; + +export const SubmitActionInitializer = (props) => { + const schema = { + title: '{{ t("Submit") }}', + 'x-action': 'submit', + 'x-component': 'Action', + 'x-designer': 'Action.Designer', + 'x-component-props': { + type: 'primary', + htmlType: 'submit', + // useProps: '{{ bp.useSubmitActionProps }}', + }, + }; + return ; +}; + +export const CreateSubmitActionInitializer = (props) => { + const schema = { + title: '{{ t("Submit") }}', + 'x-action': 'submit', + 'x-component': 'Action', + 'x-designer': 'Action.Designer', + 'x-component-props': { + type: 'primary', + htmlType: 'submit', + useProps: '{{ useCreateActionProps }}', + }, + }; + return ; +}; + +export const UpdateSubmitActionInitializer = (props) => { + const schema = { + title: '{{ t("Submit") }}', + 'x-action': 'submit', + 'x-component': 'Action', + 'x-designer': 'Action.Designer', + 'x-component-props': { + type: 'primary', + htmlType: 'submit', + useProps: '{{ useUpdateActionProps }}', + }, + }; + return ; +}; + +export const CreateFormBlockInitializer = (props) => { + const { onCreateBlockSchema, componentType, createBlockSchema, insert, ...others } = props; + const { getTemplateSchemaByMode } = useSchemaTemplateManager(); + const association = useBlockAssociationContext(); + const collection = useCollection(); + return ( + } + {...others} + onClick={async ({ item }) => { + if (item.template) { + const s = await getTemplateSchemaByMode(item); + insert(s); + } else { + insert( + createFormBlockSchema({ + actionInitializers: 'CreateFormActionInitializers', + association, + collection: collection.name, + }), + ); + } + }} + items={useRecordCollectionDataSourceItems('CreateForm')} + /> + ); +}; + +export const RecordFormBlockInitializer = (props) => { + const { onCreateBlockSchema, componentType, createBlockSchema, insert, ...others } = props; + const { getTemplateSchemaByMode } = useSchemaTemplateManager(); + const collection = useCollection(); + const association = useBlockAssociationContext(); + return ( + } + {...others} + onClick={async ({ item }) => { + if (item.template) { + const s = await getTemplateSchemaByMode(item); + insert(s); + } else { + insert( + createFormBlockSchema({ + association, + collection: collection.name, + action: 'get', + useSourceId: '{{ useSourceIdFromParentRecord }}', + useParams: '{{ useParamsFromRecord }}', + actionInitializers: 'UpdateFormActionInitializers', + }), + ); + } + }} + items={useRecordCollectionDataSourceItems('RecordForm')} + /> + ); +}; + +export const RecordReadPrettyFormBlockInitializer = (props) => { + const { onCreateBlockSchema, componentType, createBlockSchema, insert, ...others } = props; + const { getTemplateSchemaByMode } = useSchemaTemplateManager(); + const collection = useCollection(); + const association = useBlockAssociationContext(); + return ( + } + {...others} + onClick={async ({ item }) => { + if (item.template) { + const s = await getTemplateSchemaByMode(item); + insert(s); + } else { + insert( + createReadPrettyFormBlockSchema({ + association, + collection: collection.name, + action: 'get', + useSourceId: '{{ useSourceIdFromParentRecord }}', + useParams: '{{ useParamsFromRecord }}', + }), + ); + } + }} + items={useRecordCollectionDataSourceItems('ReadPrettyForm')} + /> + ); +}; + +export const RecordAssociationBlockInitializer = (props) => { + const { item, onCreateBlockSchema, componentType, createBlockSchema, insert, ...others } = props; + const { getTemplateSchemaByMode } = useSchemaTemplateManager(); + const { getCollection } = useCollectionManager(); + return ( + } + {...others} + onClick={async ({ item }) => { + console.log('RecordAssociationBlockInitializer', item); + const field = item.field; + const collection = getCollection(field.target); + insert( + createTableBlockSchema({ + rowKey: collection.filterTargetKey, + collection: field.target, + resource: `${field.collectionName}.${field.name}`, + association: `${field.collectionName}.${field.name}`, + }), + ); + // if (item.template) { + // const s = await getTemplateSchemaByMode(item); + // insert(s); + // } else { + // insert(createTableBlockSchema({ collection: item.name })); + // } + }} + // items={useRecordCollectionDataSourceItems('Table')} + /> + ); +}; + +export const TableActionColumnInitializer = (props) => { + const schema = { + type: 'void', + title: '{{ t("Actions") }}', + 'x-decorator': 'TableV2.Column.ActionBar', + 'x-component': 'TableV2.Column', + 'x-designer': 'TableV2.ActionColumnDesigner', + 'x-initializer': 'TableActionColumnInitializers', + 'x-action-column': 'actions', + properties: { + actions: { + type: 'void', + 'x-decorator': 'DndContext', + 'x-component': 'Space', + 'x-component-props': { + split: '|', + }, + properties: {}, + }, + }, + }; + return ; +}; + +export const TableCollectionFieldInitializer = (props) => { + const schema: ISchema = {}; + return ; +}; + +export const CollectionFieldInitializer = (props) => { + const schema: ISchema = {}; + return ; +}; + +export const TableSelectorInitializer = (props) => { + const { onCreateBlockSchema, componentType, createBlockSchema, insert, ...others } = props; + const { getTemplateSchemaByMode } = useSchemaTemplateManager(); + const collection = useCollection(); + return ( + } + {...others} + onClick={async ({ item }) => { + const field = item.field; + insert( + createTableSelectorSchema({ + rowKey: collection.filterTargetKey, + collection: collection.name, + resource: collection.name, + }), + ); + }} + /> + ); +}; diff --git a/packages/client/src/schema-initializer/utils.ts b/packages/client/src/schema-initializer/utils.ts new file mode 100644 index 000000000..133f2b531 --- /dev/null +++ b/packages/client/src/schema-initializer/utils.ts @@ -0,0 +1,712 @@ +import { ISchema, Schema, useFieldSchema, useForm } from '@formily/react'; +import { uid } from '@formily/shared'; +import { useTranslation } from 'react-i18next'; +import { SchemaInitializerItemOptions } from '../'; +import { useCollection, useCollectionManager } from '../collection-manager'; +import { useDesignable } from '../schema-component'; +import { useSchemaTemplateManager } from '../schema-templates'; + +export const itemsMerge = (items1, items2) => { + return items1; +}; + +export const gridRowColWrap = (schema: ISchema) => { + return { + type: 'void', + 'x-component': 'Grid.Row', + properties: { + [uid()]: { + type: 'void', + 'x-component': 'Grid.Col', + properties: { + [schema.name || uid()]: schema, + }, + }, + }, + }; +}; + +export const removeTableColumn = (schema, cb) => { + cb(schema.parent); +}; + +export const removeGridFormItem = (schema, cb) => { + cb(schema, { + removeParentsIfNoChildren: true, + breakRemoveOn: { + 'x-component': 'Grid', + }, + }); +}; + +export const findTableColumn = (schema: Schema, key: string, action: string, deepth: number = 0) => { + return schema.reduceProperties((buf, s) => { + if (s[key] === action) { + return s; + } + const c = s.reduceProperties((buf, s) => { + if (s[key] === action) { + return s; + } + return buf; + }); + if (c) { + return c; + } + return buf; + }); +}; + +export const useTableColumnInitializerFields = () => { + const { name, fields = [] } = useCollection(); + const { getInterface } = useCollectionManager(); + return fields + .filter((field) => field?.interface && field?.interface !== 'subTable') + .map((field) => { + const interfaceConfig = getInterface(field.interface); + const schema = { + name: field.name, + 'x-collection-field': `${name}.${field.name}`, + 'x-component': 'CollectionField', + 'x-read-pretty': true, + 'x-component-props': {}, + }; + // interfaceConfig?.schemaInitialize?.(schema, { field, readPretty: true, block: 'Table' }); + return { + type: 'item', + title: field?.uiSchema?.title || field.name, + component: 'TableCollectionFieldInitializer', + find: findTableColumn, + remove: removeTableColumn, + schemaInitialize: (s) => { + interfaceConfig?.schemaInitialize?.(s, { field, readPretty: true, block: 'Table' }); + }, + field, + schema, + } as SchemaInitializerItemOptions; + }); +}; + +export const useFormItemInitializerFields = () => { + const { name, fields } = useCollection(); + const { getInterface } = useCollectionManager(); + const form = useForm(); + return fields + ?.filter((field) => field?.interface) + ?.map((field) => { + const interfaceConfig = getInterface(field.interface); + const schema = { + type: 'string', + name: field.name, + // title: field?.uiSchema?.title || field.name, + 'x-designer': 'FormItem.Designer', + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + 'x-collection-field': `${name}.${field.name}`, + }; + // interfaceConfig?.schemaInitialize?.(schema, { field, block: 'Form', readPretty: form.readPretty }); + return { + type: 'item', + title: field?.uiSchema?.title || field.name, + component: 'CollectionFieldInitializer', + remove: removeGridFormItem, + schemaInitialize: (s) => { + interfaceConfig?.schemaInitialize?.(s, { field, block: 'Form', readPretty: form.readPretty }); + }, + schema, + } as SchemaInitializerItemOptions; + }); +}; + +const findSchema = (schema: Schema, key: string, action: string) => { + return schema.reduceProperties((buf, s) => { + if (s[key] === action) { + return s; + } + const c = findSchema(s, key, action); + if (c) { + return c; + } + return buf; + }); +}; + +const removeSchema = (schema, cb) => { + return cb(schema); +}; + +export const useCurrentSchema = (action: string, key: string, find = findSchema, rm = removeSchema) => { + const fieldSchema = useFieldSchema(); + const { remove } = useDesignable(); + const schema = find(fieldSchema, key, action); + return { + schema, + exists: !!schema, + remove() { + schema && rm(schema, remove); + }, + }; +}; + +export const useRecordCollectionDataSourceItems = (componentName) => { + const collection = useCollection(); + const { getTemplatesByCollection } = useSchemaTemplateManager(); + const templates = getTemplatesByCollection(collection.name).filter((template) => { + return componentName && template.componentName === componentName; + }); + if (!templates.length) { + return []; + } + const index = 0; + return [ + { + type: 'item', + name: collection.name, + title: '空白区块', + }, + { + type: 'divider', + }, + { + key: `${componentName}_table_subMenu_${index}_copy`, + type: 'subMenu', + name: 'copy', + title: '复制模板', + children: templates.map((template) => { + return { + type: 'item', + mode: 'copy', + name: collection.name, + template, + title: template.name || '未命名', + }; + }), + }, + { + key: `${componentName}_table_subMenu_${index}_ref`, + type: 'subMenu', + name: 'ref', + title: '引用模板', + children: templates.map((template) => { + return { + type: 'item', + mode: 'reference', + name: collection.name, + template, + title: template.name || '未命名', + }; + }), + }, + ]; +}; + +export const useCollectionDataSourceItems = (componentName) => { + const { t } = useTranslation(); + const { collections } = useCollectionManager(); + const { getTemplatesByCollection } = useSchemaTemplateManager(); + return [ + { + key: 'tableBlock', + type: 'itemGroup', + title: t('Select data source'), + children: collections + ?.filter((item) => !item.inherit) + ?.map((item, index) => { + const templates = getTemplatesByCollection(item.name).filter((template) => { + return componentName && template.componentName === componentName; + }); + if (!templates.length) { + return { + type: 'item', + name: item.name, + title: item.title, + }; + } + return { + key: `${componentName}_table_subMenu_${index}`, + type: 'subMenu', + name: `${item.name}_${index}`, + title: item.title, + children: [ + { + type: 'item', + name: item.name, + title: '空白区块', + }, + { + type: 'divider', + }, + { + key: `${componentName}_table_subMenu_${index}_copy`, + type: 'subMenu', + name: 'copy', + title: '复制模板', + children: templates.map((template) => { + return { + type: 'item', + mode: 'copy', + name: item.name, + template, + title: template.name || '未命名', + }; + }), + }, + { + key: `${componentName}_table_subMenu_${index}_ref`, + type: 'subMenu', + name: 'ref', + title: '引用模板', + children: templates.map((template) => { + return { + type: 'item', + mode: 'reference', + name: item.name, + template, + title: template.name || '未命名', + }; + }), + }, + ], + }; + }), + }, + ]; +}; + +export const createFormBlockSchema = (options) => { + const { + formItemInitializers = 'FormItemInitializers', + actionInitializers = 'FormActionInitializers', + collection, + resource, + association, + ...others + } = options; + const schema: ISchema = { + type: 'void', + 'x-decorator': 'FormBlockProvider', + 'x-decorator-props': { + ...others, + resource: resource || association || collection, + collection, + association, + // action: 'get', + // useParams: '{{ useParamsFromRecord }}', + }, + 'x-designer': 'FormV2.Designer', + 'x-component': 'CardItem', + properties: { + [uid()]: { + type: 'void', + 'x-component': 'FormV2', + 'x-component-props': { + useProps: '{{ useFormBlockProps }}', + }, + properties: { + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': formItemInitializers, + properties: {}, + }, + actions: { + type: 'void', + 'x-initializer': actionInitializers, + 'x-component': 'ActionBar', + 'x-component-props': { + layout: 'one-column', + style: { + marginTop: 24, + }, + }, + properties: {}, + }, + }, + }, + }, + }; + console.log(JSON.stringify(schema, null, 2)); + return schema; +}; + +export const createReadPrettyFormBlockSchema = (options) => { + const { + formItemInitializers = 'ReadPrettyFormItemInitializers', + actionInitializers = 'ReadPrettyFormActionInitializers', + collection, + association, + resource, + ...others + } = options; + const schema: ISchema = { + type: 'void', + 'x-decorator': 'FormBlockProvider', + 'x-decorator-props': { + resource: resource || association || collection, + collection, + association, + readPretty: true, + action: 'get', + useParams: '{{ useParamsFromRecord }}', + ...others, + }, + 'x-designer': 'FormV2.ReadPrettyDesigner', + 'x-component': 'CardItem', + properties: { + [uid()]: { + type: 'void', + 'x-component': 'FormV2', + 'x-read-pretty': true, + 'x-component-props': { + useProps: '{{ useFormBlockProps }}', + }, + properties: { + actions: { + type: 'void', + 'x-initializer': actionInitializers, + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 24, + }, + }, + properties: {}, + }, + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': formItemInitializers, + properties: {}, + }, + }, + }, + }, + }; + console.log(JSON.stringify(schema, null, 2)); + return schema; +}; + +export const createTableBlockSchema = (options) => { + const { collection, resource, rowKey, ...others } = options; + const schema: ISchema = { + type: 'void', + 'x-decorator': 'TableBlockProvider', + 'x-decorator-props': { + collection, + resource: resource || collection, + action: 'list', + params: { + pageSize: 20, + }, + rowKey, + showIndex: true, + dragSort: false, + ...others, + }, + 'x-designer': 'TableBlockDesigner', + 'x-component': 'CardItem', + properties: { + actions: { + type: 'void', + 'x-initializer': 'TableActionInitializers', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 16, + }, + }, + properties: {}, + }, + [uid()]: { + type: 'array', + 'x-initializer': 'TableColumnInitializers', + 'x-component': 'TableV2', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + useProps: '{{ useTableBlockProps }}', + }, + properties: { + actions: { + type: 'void', + title: '{{ t("Actions") }}', + 'x-action-column': 'actions', + 'x-decorator': 'TableV2.Column.ActionBar', + 'x-component': 'TableV2.Column', + 'x-designer': 'TableV2.ActionColumnDesigner', + 'x-initializer': 'TableActionColumnInitializers', + properties: { + actions: { + type: 'void', + 'x-decorator': 'DndContext', + 'x-component': 'Space', + 'x-component-props': { + split: '|', + }, + properties: {}, + }, + }, + }, + }, + }, + }, + }; + console.log(JSON.stringify(schema, null, 2)); + return schema; +}; + +export const createTableSelectorSchema = (options) => { + const { collection, resource, rowKey, ...others } = options; + const schema: ISchema = { + type: 'void', + 'x-decorator': 'TableSelectorProvider', + 'x-decorator-props': { + collection, + resource: resource || collection, + action: 'list', + params: { + pageSize: 20, + }, + rowKey, + ...others, + }, + 'x-designer': 'TableSelectorDesigner', + 'x-component': 'BlockItem', + properties: { + actions: { + type: 'void', + 'x-initializer': 'TableActionInitializers', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 16, + }, + }, + properties: {}, + }, + value: { + type: 'array', + 'x-initializer': 'TableColumnInitializers', + 'x-component': 'TableV2.Selector', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + useProps: '{{ useTableSelectorProps }}', + }, + properties: {}, + }, + }, + }; + console.log(JSON.stringify(schema, null, 2)); + return schema; +}; + +export const createCalendarBlockSchema = (options) => { + const { collection, resource, fieldNames, ...others } = options; + const schema: ISchema = { + type: 'void', + 'x-decorator': 'CalendarBlockProvider', + 'x-decorator-props': { + collection: collection, + resource: resource || collection, + action: 'list', + fieldNames: { + id: 'id', + ...fieldNames, + }, + params: { + paginate: false, + }, + ...others, + }, + 'x-designer': 'CalendarV2.Designer', + 'x-component': 'CardItem', + properties: { + [uid()]: { + type: 'void', + 'x-component': 'CalendarV2', + 'x-component-props': { + useProps: '{{ useCalendarBlockProps }}', + }, + properties: { + toolBar: { + type: 'void', + 'x-component': 'CalendarV2.ActionBar', + 'x-component-props': { + style: { + marginBottom: 24, + }, + }, + 'x-initializer': 'CalendarActionInitializers', + properties: {}, + }, + event: { + type: 'void', + 'x-component': 'CalendarV2.Event', + properties: { + drawer: { + type: 'void', + 'x-component': 'Action.Drawer', + 'x-component-props': { + className: 'nb-action-popup', + }, + title: '{{ t("View record") }}', + properties: { + tabs: { + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'TabPaneInitializers', + properties: { + tab1: { + type: 'void', + title: '详情', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + properties: { + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'RecordBlockInitializers', + properties: {}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }; + console.log(JSON.stringify(schema, null, 2)); + return schema; +}; + +export const createKanbanBlockSchema = (options) => { + const { collection, resource, groupField, ...others } = options; + const schema: ISchema = { + type: 'void', + 'x-decorator': 'KanbanBlockProvider', + 'x-decorator-props': { + collection: collection, + resource: resource || collection, + action: 'list', + groupField, + params: { + paginate: false, + }, + ...others, + }, + 'x-designer': 'KanbanV2.Designer', + 'x-component': 'BlockItem', + properties: { + [uid()]: { + type: 'array', + 'x-component': 'KanbanV2', + 'x-component-props': { + useProps: '{{ useKanbanBlockProps }}', + }, + properties: { + card: { + type: 'void', + 'x-decorator': 'BlockItem', + 'x-component': 'KanbanV2.Card', + 'x-designer': 'KanbanV2.Card.Designer', + properties: { + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-component-props': { dndContext: false }, + }, + }, + }, + cardAdder: { + title: '添加卡片', + type: 'void', + 'x-designer': 'Action.Designer', + 'x-designer-props': { + draggable: false, + }, + 'x-component': 'KanbanV2.CardAdder', + 'x-component-props': { + type: 'text', + openMode: 'drawer', + }, + properties: { + drawer: { + type: 'void', + title: '{{ t("Add record") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'CreateFormBlockInitializers', + properties: {}, + }, + }, + }, + }, + }, + cardViewer: { + type: 'void', + title: '{{ t("View") }}', + 'x-designer': 'Action.Designer', + 'x-component': 'KanbanV2.CardViewer', + 'x-component-props': { + openMode: 'drawer', + }, + properties: { + drawer: { + type: 'void', + title: '{{ t("View record") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + tabs: { + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'TabPaneInitializers', + properties: { + tab1: { + type: 'void', + title: '详情', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + properties: { + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'RecordBlockInitializers', + properties: {}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }; + return schema; +}; diff --git a/packages/client/src/schema-settings/SchemaSettings.tsx b/packages/client/src/schema-settings/SchemaSettings.tsx index c3c84ee61..01986810e 100644 --- a/packages/client/src/schema-settings/SchemaSettings.tsx +++ b/packages/client/src/schema-settings/SchemaSettings.tsx @@ -13,7 +13,7 @@ import { SchemaComponentOptions, useActionContext, useAPIClient, - useCollection, + useCollection } from '..'; import { useSchemaTemplateManager } from '../schema-templates'; import { useBlockTemplateContext } from '../schema-templates/BlockTemplate'; @@ -245,7 +245,7 @@ SchemaSettings.SelectItem = (props) => {
{title} -
); @@ -264,7 +264,7 @@ SchemaSettings.SwitchItem = (props) => { >
{title} - +
); diff --git a/packages/client/src/schema-templates/AddBlockTemplate.tsx b/packages/client/src/schema-templates/AddBlockTemplate.tsx deleted file mode 100644 index 4e3b7751e..000000000 --- a/packages/client/src/schema-templates/AddBlockTemplate.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { uid } from '@formily/shared'; -import { Button, Dropdown, Menu } from 'antd'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { useHistory } from 'react-router-dom'; -import { useAPIClient, useCollectionManager, useCompile, useSchemaTemplateManager } from '..'; -import { createTableBlockSchema } from '../schema-initializer/Initializers/Items'; - -export const AddBlockTemplate = (props: any) => { - const { t } = useTranslation(); - const history = useHistory(); - const api = useAPIClient(); - const { refresh } = useSchemaTemplateManager(); - const { collections } = useCollectionManager(); - const compile = useCompile(); - const insert = ({ uiSchema, collectionName }) => { - const key = uid(); - api - .resource('uiSchemaTemplates') - .create({ - values: { - key, - collectionName, - uiSchema, - }, - }) - .then((res) => { - refresh(); - history.push(`/admin/block-templates/${key}`); - }); - }; - return ( - <> - - - {collections?.map((collection) => { - return ( - { - const uiSchema = createTableBlockSchema(collection.name); - insert({ uiSchema, collectionName: collection.name }); - }} - > - {compile(collection.title)} - - ); - })} - - - } - > - - - - ); -}; diff --git a/packages/client/src/user/CurrentUser.tsx b/packages/client/src/user/CurrentUser.tsx index 6e5f313e6..bbe38cbc5 100644 --- a/packages/client/src/user/CurrentUser.tsx +++ b/packages/client/src/user/CurrentUser.tsx @@ -1,4 +1,4 @@ -import { Button, Dropdown, Menu } from 'antd'; +import { Dropdown, Menu } from 'antd'; import React, { createContext, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useHistory } from 'react-router-dom'; @@ -43,9 +43,9 @@ export const CurrentUser = () => { } > - +
diff --git a/packages/client/src/user/EditProfile.tsx b/packages/client/src/user/EditProfile.tsx index b74c9b185..eaf715563 100644 --- a/packages/client/src/user/EditProfile.tsx +++ b/packages/client/src/user/EditProfile.tsx @@ -105,7 +105,7 @@ export const EditProfile = () => { return ( { setVisible(true); ctx.setVisible(false); diff --git a/packages/client/src/user/LanguageSettings.tsx b/packages/client/src/user/LanguageSettings.tsx index c8b056b0c..c66ae3194 100644 --- a/packages/client/src/user/LanguageSettings.tsx +++ b/packages/client/src/user/LanguageSettings.tsx @@ -10,6 +10,7 @@ export const LanguageSettings = () => { const ctx = useCurrentUserContext(); return ( { setOpen(true); }} diff --git a/packages/client/src/user/SwitchRole.tsx b/packages/client/src/user/SwitchRole.tsx index 21b8d76df..7d2ee6780 100644 --- a/packages/client/src/user/SwitchRole.tsx +++ b/packages/client/src/user/SwitchRole.tsx @@ -19,7 +19,7 @@ export const SwitchRole = () => { return null; } return ( - + {t('Switch role')}{' '}