mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-02 03:02:19 +08:00
Merge branch 'next' into develop
This commit is contained in:
commit
e29f9fa39e
@ -19,6 +19,7 @@ import {
|
|||||||
DataBlockProvider,
|
DataBlockProvider,
|
||||||
TableFieldResource,
|
TableFieldResource,
|
||||||
WithoutTableFieldResource,
|
WithoutTableFieldResource,
|
||||||
|
useCollectionManager,
|
||||||
useCollectionParentRecord,
|
useCollectionParentRecord,
|
||||||
useCollectionRecord,
|
useCollectionRecord,
|
||||||
useCollectionRecordData,
|
useCollectionRecordData,
|
||||||
@ -286,10 +287,10 @@ export const useFilterByTk = (blockProps?: any) => {
|
|||||||
const recordIndex = useRecordIndex();
|
const recordIndex = useRecordIndex();
|
||||||
const recordData = useCollectionRecordData();
|
const recordData = useCollectionRecordData();
|
||||||
const collection = useCollection_deprecated();
|
const collection = useCollection_deprecated();
|
||||||
const { getCollectionField } = useCollectionManager_deprecated();
|
|
||||||
const association = useBlockAssociationContext();
|
const association = useBlockAssociationContext();
|
||||||
const assoc = blockProps?.association || association;
|
const assoc = blockProps?.association || association;
|
||||||
const withoutTableFieldResource = useContext(WithoutTableFieldResource);
|
const withoutTableFieldResource = useContext(WithoutTableFieldResource);
|
||||||
|
const cm = useCollectionManager();
|
||||||
|
|
||||||
if (!withoutTableFieldResource) {
|
if (!withoutTableFieldResource) {
|
||||||
if (resource instanceof TableFieldResource || __parent?.block === 'TableField') {
|
if (resource instanceof TableFieldResource || __parent?.block === 'TableField') {
|
||||||
@ -298,8 +299,8 @@ export const useFilterByTk = (blockProps?: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (assoc) {
|
if (assoc) {
|
||||||
const association = getCollectionField(assoc);
|
const association = cm.getCollectionField(assoc);
|
||||||
return recordData?.[association.targetKey || 'id'];
|
return recordData?.[association.targetKey || association.sourceKey || 'id'];
|
||||||
}
|
}
|
||||||
if (isArray(collection.filterTargetKey)) {
|
if (isArray(collection.filterTargetKey)) {
|
||||||
const filterByTk = {};
|
const filterByTk = {};
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { expect, test } from '@nocobase/test/e2e';
|
||||||
|
import { subTableLinkageRules } from './templatesOfBug';
|
||||||
|
|
||||||
|
test('linkage rules', async ({ page, mockPage }) => {
|
||||||
|
// Linkage rules have been set in the sub-table, the rule is: disable the singleLineText field
|
||||||
|
await mockPage(subTableLinkageRules).goto();
|
||||||
|
|
||||||
|
// Open the data selector popup, in the form within the dialog, the singleLineText field should not be disabled
|
||||||
|
await page.getByText('Add new').click();
|
||||||
|
await page.getByTestId('select-data-picker').click();
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('drawer-AssociationField.Selector-collection3-Select record').getByRole('textbox'),
|
||||||
|
).not.toBeDisabled();
|
||||||
|
});
|
@ -8682,6 +8682,525 @@ export const hideColumnBasic = {
|
|||||||
'x-index': 1,
|
'x-index': 1,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
export const subTableLinkageRules = {
|
||||||
|
collections: [
|
||||||
|
{
|
||||||
|
name: 'collection1',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'manyToMany',
|
||||||
|
interface: 'm2m',
|
||||||
|
target: 'collection2',
|
||||||
|
targetKey: 'id',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'collection2',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'manyToMany',
|
||||||
|
type: 'belongsToMany',
|
||||||
|
interface: 'm2m',
|
||||||
|
target: 'collection3',
|
||||||
|
targetKey: 'id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'singleLineText',
|
||||||
|
interface: 'input',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'collection3',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'singleLineText',
|
||||||
|
interface: 'input',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pageSchema: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Page',
|
||||||
|
'x-app-version': '1.4.0-alpha.2',
|
||||||
|
'x-index': 1,
|
||||||
|
properties: {
|
||||||
|
w8odriar0fx: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-initializer': 'page:addBlock',
|
||||||
|
'x-app-version': '1.4.0-alpha.2',
|
||||||
|
'x-index': 1,
|
||||||
|
properties: {
|
||||||
|
'9mn12l8qk3r': {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid.Row',
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
properties: {
|
||||||
|
gdis5heg9mx: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid.Col',
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
properties: {
|
||||||
|
y39h574el5n: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-acl-action-props': {
|
||||||
|
skipScopeCheck: true,
|
||||||
|
},
|
||||||
|
'x-acl-action': 'collection1:create',
|
||||||
|
'x-decorator': 'FormBlockProvider',
|
||||||
|
'x-use-decorator-props': 'useCreateFormBlockDecoratorProps',
|
||||||
|
'x-decorator-props': {
|
||||||
|
dataSource: 'main',
|
||||||
|
collection: 'collection1',
|
||||||
|
},
|
||||||
|
'x-toolbar': 'BlockSchemaToolbar',
|
||||||
|
'x-settings': 'blockSettings:createForm',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
properties: {
|
||||||
|
'5pmngglyxio': {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'FormV2',
|
||||||
|
'x-use-component-props': 'useCreateFormBlockProps',
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
properties: {
|
||||||
|
grid: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-initializer': 'form:configureFields',
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
properties: {
|
||||||
|
pnizc73vd6o: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid.Row',
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
properties: {
|
||||||
|
a1wbkt2egco: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid.Col',
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
properties: {
|
||||||
|
manyToMany: {
|
||||||
|
'x-uid': 'g7p9ry97ker',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'string',
|
||||||
|
'x-toolbar': 'FormItemSchemaToolbar',
|
||||||
|
'x-settings': 'fieldSettings:FormItem',
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-collection-field': 'collection1.manyToMany',
|
||||||
|
'x-component-props': {
|
||||||
|
fieldNames: {
|
||||||
|
label: 'id',
|
||||||
|
value: 'id',
|
||||||
|
},
|
||||||
|
mode: 'SubTable',
|
||||||
|
},
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
default: null,
|
||||||
|
'x-linkage-rules': [
|
||||||
|
{
|
||||||
|
condition: {
|
||||||
|
$and: [],
|
||||||
|
},
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
targetFields: ['singleLineText'],
|
||||||
|
operator: 'disabled',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: {
|
||||||
|
o3qcjhecdlo: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'AssociationField.SubTable',
|
||||||
|
'x-initializer': 'table:configureColumns',
|
||||||
|
'x-initializer-props': {
|
||||||
|
action: false,
|
||||||
|
},
|
||||||
|
'x-index': 1,
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
properties: {
|
||||||
|
y63w2f1hrax: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'TableV2.Column.Decorator',
|
||||||
|
'x-toolbar': 'TableColumnSchemaToolbar',
|
||||||
|
'x-settings': 'fieldSettings:TableColumn',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
properties: {
|
||||||
|
manyToMany: {
|
||||||
|
'x-uid': '7y1cq78xa6x',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
'x-collection-field': 'collection2.manyToMany',
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-component-props': {
|
||||||
|
fieldNames: {
|
||||||
|
value: 'id',
|
||||||
|
label: 'id',
|
||||||
|
},
|
||||||
|
ellipsis: true,
|
||||||
|
size: 'small',
|
||||||
|
mode: 'Picker',
|
||||||
|
},
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-decorator-props': {
|
||||||
|
labelStyle: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
properties: {
|
||||||
|
ube58vjj300: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'AssociationField.Selector',
|
||||||
|
title: '{{ t("Select record") }}',
|
||||||
|
'x-component-props': {
|
||||||
|
className: 'nb-record-picker-selector',
|
||||||
|
},
|
||||||
|
'x-index': 1,
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
properties: {
|
||||||
|
grid: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-initializer': 'popup:tableSelector:addBlock',
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
properties: {
|
||||||
|
j3twr0dskm5: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid.Row',
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
properties: {
|
||||||
|
hnpuy24xv2w: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid.Col',
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
properties: {
|
||||||
|
ibb7t79b30o: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'FilterFormBlockProvider',
|
||||||
|
'x-use-decorator-props':
|
||||||
|
'useFilterFormBlockDecoratorProps',
|
||||||
|
'x-decorator-props': {
|
||||||
|
dataSource: 'main',
|
||||||
|
collection: 'collection3',
|
||||||
|
},
|
||||||
|
'x-toolbar': 'BlockSchemaToolbar',
|
||||||
|
'x-settings': 'blockSettings:filterForm',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
'x-filter-targets': [],
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
properties: {
|
||||||
|
'78t3oxrrgjw': {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'FormV2',
|
||||||
|
'x-use-component-props':
|
||||||
|
'useFilterFormBlockProps',
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
properties: {
|
||||||
|
grid: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-initializer':
|
||||||
|
'filterForm:configureFields',
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
properties: {
|
||||||
|
'4iwr1ecvrk2': {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid.Row',
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
properties: {
|
||||||
|
txbjwktdsw2: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid.Col',
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
properties: {
|
||||||
|
singleLineText: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
'x-toolbar':
|
||||||
|
'FormItemSchemaToolbar',
|
||||||
|
'x-settings':
|
||||||
|
'fieldSettings:FilterFormItem',
|
||||||
|
'x-component':
|
||||||
|
'CollectionField',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-use-decorator-props':
|
||||||
|
'useFormItemProps',
|
||||||
|
'x-collection-field':
|
||||||
|
'collection3.singleLineText',
|
||||||
|
'x-component-props': {
|
||||||
|
utc: false,
|
||||||
|
underFilter: true,
|
||||||
|
},
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
'x-uid': '8n98emo4bul',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'jhyq7js69mq',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'gzt99p515bl',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'v66mxmusgg3',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
rc7wwrfaepk: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-initializer':
|
||||||
|
'filterForm:configureActions',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-component-props': {
|
||||||
|
layout: 'one-column',
|
||||||
|
style: {
|
||||||
|
float: 'right',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
'x-uid': 'mnbgi1qs904',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '8bmuukycdk3',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '20fn8d2oa9m',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'dfjh0jd36zn',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '5yskpcvgzo6',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'vlxhgd15094',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'Action.Container.Footer',
|
||||||
|
'x-component-props': {},
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-component-props': {},
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
properties: {
|
||||||
|
submit: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
title: '{{ t("Submit") }}',
|
||||||
|
'x-action': 'submit',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-use-component-props': 'usePickActionProps',
|
||||||
|
'x-toolbar': 'ActionSchemaToolbar',
|
||||||
|
'x-settings': 'actionSettings:submit',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
htmlType: 'submit',
|
||||||
|
},
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
'x-uid': 'crmin5h15fl',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'qb5ytzc65va',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '4v6bewewfpz',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'de83n28qz8k',
|
||||||
|
'x-async': false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'f5icd0gzkh9',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
nklog6a4gzm: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'TableV2.Column.Decorator',
|
||||||
|
'x-toolbar': 'TableColumnSchemaToolbar',
|
||||||
|
'x-settings': 'fieldSettings:TableColumn',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
properties: {
|
||||||
|
singleLineText: {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
'x-collection-field': 'collection2.singleLineText',
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-component-props': {
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-decorator-props': {
|
||||||
|
labelStyle: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
'x-uid': 'jf7br426xbi',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'h2bfuft16e4',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'mhkztghm6mf',
|
||||||
|
'x-async': false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'x63q5i1qfcs',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '3e993koh0hf',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'bgguat3qw3l',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
'7ep15tqi252': {
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
version: '2.0',
|
||||||
|
type: 'void',
|
||||||
|
'x-initializer': 'createForm:configureActions',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-component-props': {
|
||||||
|
layout: 'one-column',
|
||||||
|
},
|
||||||
|
'x-app-version': '1.4.30',
|
||||||
|
'x-uid': '2glco96x6t4',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '2zm95rp9wl4',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'suynqot1a08',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'jfa41aikubo',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'eaw2pioddrg',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '0f1w4u9shko',
|
||||||
|
'x-async': false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'w3bigfwo8ws',
|
||||||
|
'x-async': true,
|
||||||
|
},
|
||||||
|
};
|
||||||
export const popupConfigurationShouldPersistAcrossDifferentRowsInTheSameColumn = {
|
export const popupConfigurationShouldPersistAcrossDifferentRowsInTheSameColumn = {
|
||||||
pageSchema: {
|
pageSchema: {
|
||||||
_isJSONSchemaObject: true,
|
_isJSONSchemaObject: true,
|
||||||
|
@ -15,38 +15,42 @@ import { observer, useFieldSchema } from '@formily/react';
|
|||||||
import { action } from '@formily/reactive';
|
import { action } from '@formily/reactive';
|
||||||
import { each } from '@formily/shared';
|
import { each } from '@formily/shared';
|
||||||
import { useUpdate } from 'ahooks';
|
import { useUpdate } from 'ahooks';
|
||||||
import { Button, Card, Divider, Tooltip, Space } from 'antd';
|
import { Button, Card, Divider, Space, Tooltip } from 'antd';
|
||||||
import React, { useCallback, useContext, useState, useMemo } from 'react';
|
import React, { useCallback, useContext, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FormActiveFieldsProvider } from '../../../block-provider/hooks/useFormActiveFields';
|
import {
|
||||||
import { useCollection, CollectionProvider } from '../../../data-source';
|
FormProvider,
|
||||||
|
RecordPickerContext,
|
||||||
|
RecordPickerProvider,
|
||||||
|
SchemaComponentOptions,
|
||||||
|
useActionContext,
|
||||||
|
} from '../..';
|
||||||
import { useCreateActionProps } from '../../../block-provider/hooks';
|
import { useCreateActionProps } from '../../../block-provider/hooks';
|
||||||
|
import { FormActiveFieldsProvider } from '../../../block-provider/hooks/useFormActiveFields';
|
||||||
|
import { TableSelectorParamsProvider } from '../../../block-provider/TableSelectorProvider';
|
||||||
|
import { CollectionProvider, useCollection } from '../../../data-source';
|
||||||
import {
|
import {
|
||||||
useCollectionRecord,
|
useCollectionRecord,
|
||||||
useCollectionRecordData,
|
useCollectionRecordData,
|
||||||
} from '../../../data-source/collection-record/CollectionRecordProvider';
|
} from '../../../data-source/collection-record/CollectionRecordProvider';
|
||||||
import { isNewRecord, markRecordAsNew } from '../../../data-source/collection-record/isNewRecord';
|
import { isNewRecord, markRecordAsNew } from '../../../data-source/collection-record/isNewRecord';
|
||||||
import { FlagProvider } from '../../../flag-provider';
|
import { FlagProvider } from '../../../flag-provider';
|
||||||
import { NocoBaseRecursionField, RefreshComponentProvider } from '../../../formily/NocoBaseRecursionField';
|
import {
|
||||||
|
NocoBaseRecursionField,
|
||||||
|
RefreshComponentProvider,
|
||||||
|
useRefreshComponent,
|
||||||
|
} from '../../../formily/NocoBaseRecursionField';
|
||||||
import { RecordIndexProvider, RecordProvider } from '../../../record-provider';
|
import { RecordIndexProvider, RecordProvider } from '../../../record-provider';
|
||||||
import { isPatternDisabled, isSystemField } from '../../../schema-settings';
|
import { isPatternDisabled, isSystemField } from '../../../schema-settings';
|
||||||
import { TableSelectorParamsProvider } from '../../../block-provider/TableSelectorProvider';
|
|
||||||
import {
|
import {
|
||||||
DefaultValueProvider,
|
DefaultValueProvider,
|
||||||
IsAllowToSetDefaultValueParams,
|
IsAllowToSetDefaultValueParams,
|
||||||
interfacesOfUnsupportedDefaultValue,
|
interfacesOfUnsupportedDefaultValue,
|
||||||
} from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
|
} from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
|
||||||
|
import { useCompile } from '../../hooks';
|
||||||
|
import { Action, ActionContextProvider } from '../action';
|
||||||
import { AssociationFieldContext } from './context';
|
import { AssociationFieldContext } from './context';
|
||||||
import { SubFormProvider, useAssociationFieldContext, useFieldNames } from './hooks';
|
import { SubFormProvider, useAssociationFieldContext, useFieldNames } from './hooks';
|
||||||
import { Action, ActionContextProvider } from '../action';
|
|
||||||
import { useCompile } from '../../hooks';
|
|
||||||
import {
|
|
||||||
FormProvider,
|
|
||||||
RecordPickerProvider,
|
|
||||||
SchemaComponentOptions,
|
|
||||||
useActionContext,
|
|
||||||
RecordPickerContext,
|
|
||||||
} from '../..';
|
|
||||||
import { useTableSelectorProps } from './InternalPicker';
|
import { useTableSelectorProps } from './InternalPicker';
|
||||||
import { getLabelFormatValue, useLabelUiSchema } from './util';
|
import { getLabelFormatValue, useLabelUiSchema } from './util';
|
||||||
|
|
||||||
@ -136,6 +140,13 @@ const ToManyNester = observer(
|
|||||||
const recordData = useCollectionRecordData();
|
const recordData = useCollectionRecordData();
|
||||||
const collection = useCollection();
|
const collection = useCollection();
|
||||||
const update = useUpdate();
|
const update = useUpdate();
|
||||||
|
|
||||||
|
const refreshComponent = useRefreshComponent();
|
||||||
|
const refresh = useCallback(() => {
|
||||||
|
update();
|
||||||
|
refreshComponent?.();
|
||||||
|
}, [update, refreshComponent]);
|
||||||
|
|
||||||
const [visibleSelector, setVisibleSelector] = useState(false);
|
const [visibleSelector, setVisibleSelector] = useState(false);
|
||||||
const [selectedRows, setSelectedRows] = useState([]);
|
const [selectedRows, setSelectedRows] = useState([]);
|
||||||
const fieldNames = useFieldNames(props);
|
const fieldNames = useFieldNames(props);
|
||||||
@ -235,7 +246,7 @@ const ToManyNester = observer(
|
|||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<RefreshComponentProvider refresh={update}>
|
<RefreshComponentProvider refresh={refresh}>
|
||||||
{field.value.map((value, index) => {
|
{field.value.map((value, index) => {
|
||||||
let allowed = allowDissociate;
|
let allowed = allowDissociate;
|
||||||
if (!allowDissociate) {
|
if (!allowDissociate) {
|
||||||
|
@ -15,6 +15,7 @@ import { CreateAction } from '../../../../schema-initializer/components';
|
|||||||
import { ActionContextProvider, useActionContext } from '../../action';
|
import { ActionContextProvider, useActionContext } from '../../action';
|
||||||
import { useAssociationFieldContext, useInsertSchema } from '../hooks';
|
import { useAssociationFieldContext, useInsertSchema } from '../hooks';
|
||||||
import schema from '../schema';
|
import schema from '../schema';
|
||||||
|
import { TabsContextProvider } from '../../tabs/context';
|
||||||
|
|
||||||
export const CreateRecordAction = observer(
|
export const CreateRecordAction = observer(
|
||||||
(props) => {
|
(props) => {
|
||||||
@ -39,14 +40,16 @@ export const CreateRecordAction = observer(
|
|||||||
<CreateAction {...props} onClick={(arg) => addbuttonClick(arg)} />
|
<CreateAction {...props} onClick={(arg) => addbuttonClick(arg)} />
|
||||||
<ActionContextProvider value={{ ...ctx, visible: visibleAddNewer, setVisible: setVisibleAddNewer }}>
|
<ActionContextProvider value={{ ...ctx, visible: visibleAddNewer, setVisible: setVisibleAddNewer }}>
|
||||||
<CollectionProvider_deprecated name={currentCollection} dataSource={currentDataSource}>
|
<CollectionProvider_deprecated name={currentCollection} dataSource={currentDataSource}>
|
||||||
<NocoBaseRecursionField
|
<TabsContextProvider>
|
||||||
onlyRenderProperties
|
<NocoBaseRecursionField
|
||||||
basePath={field.address}
|
onlyRenderProperties
|
||||||
schema={fieldSchema}
|
basePath={field.address}
|
||||||
filterProperties={(s) => {
|
schema={fieldSchema}
|
||||||
return s['x-component'] === 'AssociationField.AddNewer';
|
filterProperties={(s) => {
|
||||||
}}
|
return s['x-component'] === 'AssociationField.AddNewer';
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</TabsContextProvider>
|
||||||
</CollectionProvider_deprecated>
|
</CollectionProvider_deprecated>
|
||||||
</ActionContextProvider>
|
</ActionContextProvider>
|
||||||
</CollectionProvider_deprecated>
|
</CollectionProvider_deprecated>
|
||||||
|
@ -150,7 +150,7 @@ const field2option = (field, depth, nonfilterable, dataSourceManager, collection
|
|||||||
return option;
|
return option;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOptions = _.memoize((fields, depth, nonfilterable, dataSourceManager, collectionManager) => {
|
const getOptions = (fields, depth, nonfilterable, dataSourceManager, collectionManager) => {
|
||||||
const options = [];
|
const options = [];
|
||||||
fields.forEach((field) => {
|
fields.forEach((field) => {
|
||||||
const option = field2option(field, depth, nonfilterable, dataSourceManager, collectionManager);
|
const option = field2option(field, depth, nonfilterable, dataSourceManager, collectionManager);
|
||||||
@ -159,7 +159,7 @@ const getOptions = _.memoize((fields, depth, nonfilterable, dataSourceManager, c
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return options;
|
return options;
|
||||||
});
|
};
|
||||||
|
|
||||||
export const useFilterFieldOptions = (fields) => {
|
export const useFilterFieldOptions = (fields) => {
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
@ -192,6 +192,7 @@ export const removeNullCondition = (filter, customFlat = flat) => {
|
|||||||
export const useFilterActionProps = () => {
|
export const useFilterActionProps = () => {
|
||||||
const collection = useCollection();
|
const collection = useCollection();
|
||||||
const options = useFilterOptions(collection?.name);
|
const options = useFilterOptions(collection?.name);
|
||||||
|
console.log(options);
|
||||||
const props = useDataBlockProps();
|
const props = useDataBlockProps();
|
||||||
return useFilterFieldProps({ options, params: props?.params });
|
return useFilterFieldProps({ options, params: props?.params });
|
||||||
};
|
};
|
||||||
|
@ -9,27 +9,37 @@
|
|||||||
|
|
||||||
/* eslint-disable react-hooks/rules-of-hooks */
|
/* eslint-disable react-hooks/rules-of-hooks */
|
||||||
import { Field } from '@formily/core';
|
import { Field } from '@formily/core';
|
||||||
import { useField, useFieldSchema } from '@formily/react';
|
import { Schema, useField, useFieldSchema } from '@formily/react';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useFlag } from '../../../../flag-provider';
|
|
||||||
import { bindLinkageRulesToFiled } from '../../../../schema-settings/LinkageRules/bindLinkageRulesToFiled';
|
import { bindLinkageRulesToFiled } from '../../../../schema-settings/LinkageRules/bindLinkageRulesToFiled';
|
||||||
import { forEachLinkageRule } from '../../../../schema-settings/LinkageRules/forEachLinkageRule';
|
import { forEachLinkageRule } from '../../../../schema-settings/LinkageRules/forEachLinkageRule';
|
||||||
import useLocalVariables from '../../../../variables/hooks/useLocalVariables';
|
import useLocalVariables from '../../../../variables/hooks/useLocalVariables';
|
||||||
import useVariables from '../../../../variables/hooks/useVariables';
|
import useVariables from '../../../../variables/hooks/useVariables';
|
||||||
import { useSubFormValue } from '../../association-field/hooks';
|
import { useSubFormValue } from '../../association-field/hooks';
|
||||||
|
import { isSubMode } from '../../association-field/util';
|
||||||
|
|
||||||
|
const isSubFormOrSubTableField = (fieldSchema: Schema) => {
|
||||||
|
while (fieldSchema) {
|
||||||
|
if (isSubMode(fieldSchema)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldSchema['x-component'] === 'FormV2') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldSchema = fieldSchema.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* used to bind the linkage rules of the sub-table or sub-form with the current field
|
* used to bind the linkage rules of the sub-table or sub-form with the current field
|
||||||
*/
|
*/
|
||||||
export const useLinkageRulesForSubTableOrSubForm = () => {
|
export const useLinkageRulesForSubTableOrSubForm = () => {
|
||||||
const { isInSubForm, isInSubTable } = useFlag();
|
|
||||||
|
|
||||||
if (!isInSubForm && !isInSubTable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const field = useField<Field>();
|
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
|
const field = useField<Field>();
|
||||||
const { fieldSchema: schemaOfSubTableOrSubForm, formValue } = useSubFormValue();
|
const { fieldSchema: schemaOfSubTableOrSubForm, formValue } = useSubFormValue();
|
||||||
const localVariables = useLocalVariables();
|
const localVariables = useLocalVariables();
|
||||||
const variables = useVariables();
|
const variables = useVariables();
|
||||||
@ -37,6 +47,10 @@ export const useLinkageRulesForSubTableOrSubForm = () => {
|
|||||||
const linkageRules = getLinkageRules(schemaOfSubTableOrSubForm);
|
const linkageRules = getLinkageRules(schemaOfSubTableOrSubForm);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isSubFormOrSubTableField(fieldSchema)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(field.onUnmount as any).__rested) {
|
if (!(field.onUnmount as any).__rested) {
|
||||||
const _onUnmount = field.onUnmount;
|
const _onUnmount = field.onUnmount;
|
||||||
field.onUnmount = () => {
|
field.onUnmount = () => {
|
||||||
|
@ -177,7 +177,7 @@ export function useTableColumnInitializerFields() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useAssociatedTableColumnInitializerFields() {
|
export function useAssociatedTableColumnInitializerFields() {
|
||||||
const { name, fields } = useCollection_deprecated();
|
const { fields } = useCollection_deprecated();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { getInterface, getCollectionFields, getCollection } = useCollectionManager_deprecated();
|
const { getInterface, getCollectionFields, getCollection } = useCollectionManager_deprecated();
|
||||||
const groups = fields
|
const groups = fields
|
||||||
|
@ -109,6 +109,66 @@ const RuleTypes = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
randomChar: {
|
||||||
|
title: `{{t("Random character", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
optionRenders: {
|
||||||
|
length: function Length({ value }) {
|
||||||
|
return <code>{value}</code>;
|
||||||
|
},
|
||||||
|
charsets: function Charsets({ value }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const charsetLabels = {
|
||||||
|
number: t('Number', { ns: NAMESPACE }),
|
||||||
|
lowercase: t('Lowercase letters', { ns: NAMESPACE }),
|
||||||
|
uppercase: t('Uppercase letters', { ns: NAMESPACE }),
|
||||||
|
symbol: t('Symbols', { ns: NAMESPACE })
|
||||||
|
};
|
||||||
|
return <code>{value?.map(charset => charsetLabels[charset]).join(', ') || t('Number', { ns: NAMESPACE })}</code>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fieldset: {
|
||||||
|
length: {
|
||||||
|
type: 'number',
|
||||||
|
title: `{{t("Length", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
description: `{{t("Will generate random characters with specified length.", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'InputNumber',
|
||||||
|
'x-component-props': {
|
||||||
|
min: 1,
|
||||||
|
max: 32,
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
default: 6,
|
||||||
|
},
|
||||||
|
charsets: {
|
||||||
|
type: 'array',
|
||||||
|
title: `{{t("Character sets", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
description: `{{t("Select character sets to generate random characters.", { ns: "${NAMESPACE}" })}}`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
'x-component-props': {
|
||||||
|
mode: 'multiple',
|
||||||
|
allowClear: false,
|
||||||
|
},
|
||||||
|
enum: [
|
||||||
|
{ value: 'number', label: `{{t("Number", { ns: "${NAMESPACE}" })}}` },
|
||||||
|
{ value: 'lowercase', label: `{{t("Lowercase letters", { ns: "${NAMESPACE}" })}}` },
|
||||||
|
{ value: 'uppercase', label: `{{t("Uppercase letters", { ns: "${NAMESPACE}" })}}` },
|
||||||
|
{ value: 'symbol', label: `{{t("Symbols", { ns: "${NAMESPACE}" })}}` }
|
||||||
|
],
|
||||||
|
required: true,
|
||||||
|
default: ['number'],
|
||||||
|
'x-validator': {
|
||||||
|
minItems: 1,
|
||||||
|
message: `{{t("At least one character set should be selected", { ns: "${NAMESPACE}" })}}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaults: {
|
||||||
|
length: 6,
|
||||||
|
charsets: ['number'],
|
||||||
|
},
|
||||||
|
},
|
||||||
integer: {
|
integer: {
|
||||||
title: `{{t("Autoincrement", { ns: "${NAMESPACE}" })}}`,
|
title: `{{t("Autoincrement", { ns: "${NAMESPACE}" })}}`,
|
||||||
optionRenders: {
|
optionRenders: {
|
||||||
|
@ -24,5 +24,14 @@
|
|||||||
"Monthly": "每月",
|
"Monthly": "每月",
|
||||||
"Yearly": "每年",
|
"Yearly": "每年",
|
||||||
"Operations": "操作",
|
"Operations": "操作",
|
||||||
"Customize": "自定义"
|
"Customize": "自定义",
|
||||||
|
"Random character": "随机字符",
|
||||||
|
"Length": "长度",
|
||||||
|
"Will generate random characters with specified length.": "将生成指定长度的随机字符。",
|
||||||
|
"Character sets": "字符集",
|
||||||
|
"Select character sets to generate random characters.": "选择用于生成随机字符的字符集。",
|
||||||
|
"Number": "数字",
|
||||||
|
"Lowercase letters": "小写字母",
|
||||||
|
"Uppercase letters": "大写字母",
|
||||||
|
"Symbols": "符号"
|
||||||
}
|
}
|
||||||
|
@ -295,6 +295,79 @@ sequencePatterns.register('date', {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 字符集常量定义
|
||||||
|
const CHAR_SETS = {
|
||||||
|
number: '0123456789',
|
||||||
|
lowercase: 'abcdefghijklmnopqrstuvwxyz',
|
||||||
|
uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
||||||
|
// 符号只保留常用且安全的符号,有需要的可以自己加比如[]{}|;:,.<>放在链接或者文件名里容易出问题的字符
|
||||||
|
symbol: '!@#$%^&*_-+'
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
interface RandomCharOptions {
|
||||||
|
length?: number;
|
||||||
|
charsets?: Array<keyof typeof CHAR_SETS>;
|
||||||
|
}
|
||||||
|
|
||||||
|
sequencePatterns.register('randomChar', {
|
||||||
|
validate(options?: RandomCharOptions) {
|
||||||
|
if (!options?.length || options.length < 1) {
|
||||||
|
return 'options.length should be configured as a positive integer';
|
||||||
|
}
|
||||||
|
if (!options?.charsets || options.charsets.length === 0) {
|
||||||
|
return 'At least one character set should be selected';
|
||||||
|
}
|
||||||
|
if (options.charsets.some(charset => !CHAR_SETS[charset])) {
|
||||||
|
return 'Invalid charset selected';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
generate(instance: any, options: RandomCharOptions) {
|
||||||
|
const {
|
||||||
|
length = 6,
|
||||||
|
charsets = ['number']
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
const chars = [...new Set(
|
||||||
|
charsets.reduce((acc, charset) => acc + CHAR_SETS[charset], '')
|
||||||
|
)];
|
||||||
|
|
||||||
|
const getRandomChar = () => {
|
||||||
|
const randomIndex = Math.floor(Math.random() * chars.length);
|
||||||
|
return chars[randomIndex];
|
||||||
|
};
|
||||||
|
|
||||||
|
return Array.from({ length }, () => getRandomChar()).join('');
|
||||||
|
},
|
||||||
|
|
||||||
|
batchGenerate(instances: any[], values: string[], options: RandomCharOptions) {
|
||||||
|
instances.forEach((instance, i) => {
|
||||||
|
values[i] = sequencePatterns.get('randomChar').generate.call(this, instance, options);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getLength(options: RandomCharOptions) {
|
||||||
|
return options.length || 6;
|
||||||
|
},
|
||||||
|
|
||||||
|
getMatcher(options: RandomCharOptions) {
|
||||||
|
const pattern = [...new Set(
|
||||||
|
(options.charsets || ['number']).reduce((acc, charset) => {
|
||||||
|
switch (charset) {
|
||||||
|
case 'number': return acc + '0-9';
|
||||||
|
case 'lowercase': return acc + 'a-z';
|
||||||
|
case 'uppercase': return acc + 'A-Z';
|
||||||
|
case 'symbol': return acc + CHAR_SETS.symbol.replace('-', '').replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') + '-';
|
||||||
|
default: return acc;
|
||||||
|
}
|
||||||
|
}, '')
|
||||||
|
)].join('');
|
||||||
|
|
||||||
|
return `[${pattern}]{${options.length || 6}}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
interface PatternConfig {
|
interface PatternConfig {
|
||||||
type: string;
|
type: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
@ -134,70 +134,3 @@ export async function createMiddleware(ctx: Context, next: Next) {
|
|||||||
await next();
|
await next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function destroyMiddleware(ctx: Context, next: Next) {
|
|
||||||
const { resourceName, actionName, sourceId } = ctx.action;
|
|
||||||
const collection = ctx.db.getCollection(resourceName);
|
|
||||||
|
|
||||||
if (collection?.options?.template !== 'file' || actionName !== 'destroy') {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
const repository = ctx.db.getRepository(resourceName, sourceId);
|
|
||||||
|
|
||||||
const { filterByTk, filter } = ctx.action.params;
|
|
||||||
|
|
||||||
const records = await repository.find({
|
|
||||||
filterByTk,
|
|
||||||
filter,
|
|
||||||
context: ctx,
|
|
||||||
});
|
|
||||||
|
|
||||||
const storageIds = new Set(records.map((record) => record.storageId));
|
|
||||||
const storageGroupedRecords = records.reduce((result, record) => {
|
|
||||||
const storageId = record.storageId;
|
|
||||||
if (!result[storageId]) {
|
|
||||||
result[storageId] = [];
|
|
||||||
}
|
|
||||||
result[storageId].push(record);
|
|
||||||
return result;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
const storages = await ctx.db.getRepository('storages').find({
|
|
||||||
filter: {
|
|
||||||
id: [...storageIds] as any[],
|
|
||||||
paranoid: {
|
|
||||||
$ne: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let count = 0;
|
|
||||||
const undeleted = [];
|
|
||||||
const plugin = ctx.app.pm.get(Plugin);
|
|
||||||
await storages.reduce(
|
|
||||||
(promise, storage) =>
|
|
||||||
promise.then(async () => {
|
|
||||||
const storageConfig = plugin.storageTypes.get(storage.type);
|
|
||||||
const result = await storageConfig.delete(plugin.parseStorage(storage), storageGroupedRecords[storage.id]);
|
|
||||||
count += result[0];
|
|
||||||
undeleted.push(...result[1]);
|
|
||||||
}),
|
|
||||||
Promise.resolve(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (undeleted.length) {
|
|
||||||
const ids = undeleted.map((record) => record.id);
|
|
||||||
ctx.action.mergeParams({
|
|
||||||
filter: {
|
|
||||||
id: {
|
|
||||||
$notIn: ids,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.logger.error('[file-manager] some of attachment files are not successfully deleted: ', { ids });
|
|
||||||
}
|
|
||||||
|
|
||||||
await next();
|
|
||||||
}
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import actions from '@nocobase/actions';
|
import actions from '@nocobase/actions';
|
||||||
import { createMiddleware, destroyMiddleware } from './attachments';
|
import { createMiddleware } from './attachments';
|
||||||
import * as storageActions from './storages';
|
import * as storageActions from './storages';
|
||||||
|
|
||||||
export default function ({ app }) {
|
export default function ({ app }) {
|
||||||
@ -18,6 +18,4 @@ export default function ({ app }) {
|
|||||||
});
|
});
|
||||||
app.resourcer.use(createMiddleware, { tag: 'createMiddleware', after: 'auth' });
|
app.resourcer.use(createMiddleware, { tag: 'createMiddleware', after: 'auth' });
|
||||||
app.resourcer.registerActionHandler('upload', actions.create);
|
app.resourcer.registerActionHandler('upload', actions.create);
|
||||||
|
|
||||||
app.resourcer.use(destroyMiddleware);
|
|
||||||
}
|
}
|
||||||
|
@ -12,14 +12,14 @@ import { Registry } from '@nocobase/utils';
|
|||||||
|
|
||||||
import { basename } from 'path';
|
import { basename } from 'path';
|
||||||
|
|
||||||
import { Transactionable } from '@nocobase/database';
|
import { Model, Transactionable } from '@nocobase/database';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { STORAGE_TYPE_ALI_OSS, STORAGE_TYPE_LOCAL, STORAGE_TYPE_S3, STORAGE_TYPE_TX_COS } from '../constants';
|
import { STORAGE_TYPE_ALI_OSS, STORAGE_TYPE_LOCAL, STORAGE_TYPE_S3, STORAGE_TYPE_TX_COS } from '../constants';
|
||||||
import { FileModel } from './FileModel';
|
import { FileModel } from './FileModel';
|
||||||
import initActions from './actions';
|
import initActions from './actions';
|
||||||
import { getFileData } from './actions/attachments';
|
import { getFileData } from './actions/attachments';
|
||||||
import { AttachmentInterface } from './interfaces/attachment-interface';
|
import { AttachmentInterface } from './interfaces/attachment-interface';
|
||||||
import { IStorage, StorageModel } from './storages';
|
import { AttachmentModel, IStorage, StorageModel } from './storages';
|
||||||
import StorageTypeAliOss from './storages/ali-oss';
|
import StorageTypeAliOss from './storages/ali-oss';
|
||||||
import StorageTypeLocal from './storages/local';
|
import StorageTypeLocal from './storages/local';
|
||||||
import StorageTypeS3 from './storages/s3';
|
import StorageTypeS3 from './storages/s3';
|
||||||
@ -29,6 +29,16 @@ export type * from './storages';
|
|||||||
|
|
||||||
const DEFAULT_STORAGE_TYPE = STORAGE_TYPE_LOCAL;
|
const DEFAULT_STORAGE_TYPE = STORAGE_TYPE_LOCAL;
|
||||||
|
|
||||||
|
class FileDeleteError extends Error {
|
||||||
|
data: Model;
|
||||||
|
|
||||||
|
constructor(message: string, data: Model) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'FileDeleteError';
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type FileRecordOptions = {
|
export type FileRecordOptions = {
|
||||||
collectionName: string;
|
collectionName: string;
|
||||||
filePath: string;
|
filePath: string;
|
||||||
@ -46,6 +56,23 @@ export class PluginFileManagerServer extends Plugin {
|
|||||||
storageTypes = new Registry<IStorage>();
|
storageTypes = new Registry<IStorage>();
|
||||||
storagesCache = new Map<number, StorageModel>();
|
storagesCache = new Map<number, StorageModel>();
|
||||||
|
|
||||||
|
afterDestroy = async (record: Model, options) => {
|
||||||
|
const { collection } = record.constructor as typeof Model;
|
||||||
|
if (collection?.options?.template !== 'file' && collection.name !== 'attachments') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const storage = this.storagesCache.get(record.get('storageId'));
|
||||||
|
if (storage?.paranoid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const storageConfig = this.storageTypes.get(storage.type);
|
||||||
|
const result = await storageConfig.delete(storage, [record as unknown as AttachmentModel]);
|
||||||
|
if (!result[0]) {
|
||||||
|
throw new FileDeleteError('Failed to delete file', record);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
registerStorageType(type: string, options: IStorage) {
|
registerStorageType(type: string, options: IStorage) {
|
||||||
this.storageTypes.register(type, options);
|
this.storageTypes.register(type, options);
|
||||||
}
|
}
|
||||||
@ -190,6 +217,8 @@ export class PluginFileManagerServer extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
|
this.db.on('afterDestroy', this.afterDestroy);
|
||||||
|
|
||||||
this.storageTypes.register(STORAGE_TYPE_LOCAL, new StorageTypeLocal());
|
this.storageTypes.register(STORAGE_TYPE_LOCAL, new StorageTypeLocal());
|
||||||
this.storageTypes.register(STORAGE_TYPE_ALI_OSS, new StorageTypeAliOss());
|
this.storageTypes.register(STORAGE_TYPE_ALI_OSS, new StorageTypeAliOss());
|
||||||
this.storageTypes.register(STORAGE_TYPE_S3, new StorageTypeS3());
|
this.storageTypes.register(STORAGE_TYPE_S3, new StorageTypeS3());
|
||||||
@ -224,8 +253,7 @@ export class PluginFileManagerServer extends Plugin {
|
|||||||
|
|
||||||
initActions(this);
|
initActions(this);
|
||||||
|
|
||||||
this.app.acl.allow('attachments', 'upload', 'loggedIn');
|
this.app.acl.allow('attachments', ['upload', 'create'], 'loggedIn');
|
||||||
this.app.acl.allow('attachments', 'create', 'loggedIn');
|
|
||||||
this.app.acl.allow('storages', 'getBasicInfo', 'loggedIn');
|
this.app.acl.allow('storages', 'getBasicInfo', 'loggedIn');
|
||||||
|
|
||||||
// this.app.resourcer.use(uploadMiddleware);
|
// this.app.resourcer.use(uploadMiddleware);
|
||||||
|
@ -65,7 +65,7 @@ const mobileComponents = {
|
|||||||
Select: (props) => {
|
Select: (props) => {
|
||||||
const { designable } = useDesignable();
|
const { designable } = useDesignable();
|
||||||
if (designable !== false) {
|
if (designable !== false) {
|
||||||
return <Select {...props} />;
|
return <Select {...props} popupMatchSelectWidth={true} />;
|
||||||
} else {
|
} else {
|
||||||
return <MobilePicker {...props} />;
|
return <MobilePicker {...props} />;
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,8 @@ const MobileDateTimePicker = connect(
|
|||||||
timeFormat = 'HH:mm',
|
timeFormat = 'HH:mm',
|
||||||
showTime = false,
|
showTime = false,
|
||||||
picker,
|
picker,
|
||||||
...others
|
disabled,
|
||||||
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
@ -81,18 +82,19 @@ const MobileDateTimePicker = connect(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div contentEditable="false" onClick={() => setVisible(true)}>
|
<div contentEditable="false" onClick={() => !disabled && setVisible(true)}>
|
||||||
<NBDatePicker
|
<NBDatePicker
|
||||||
onClick={() => setVisible(true)}
|
onClick={() => setVisible(true)}
|
||||||
value={value}
|
value={value}
|
||||||
picker={picker}
|
picker={picker}
|
||||||
{...others}
|
disabled={disabled}
|
||||||
|
{...rest}
|
||||||
popupStyle={{ display: 'none' }}
|
popupStyle={{ display: 'none' }}
|
||||||
style={{ pointerEvents: 'none', width: '100%' }}
|
style={{ pointerEvents: 'none', width: '100%' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...others}
|
{...rest}
|
||||||
cancelText={t('Cancel')}
|
cancelText={t('Cancel')}
|
||||||
confirmText={t('Confirm')}
|
confirmText={t('Confirm')}
|
||||||
visible={visible}
|
visible={visible}
|
||||||
@ -102,8 +104,8 @@ const MobileDateTimePicker = connect(
|
|||||||
}}
|
}}
|
||||||
precision={showTime && picker === 'date' ? getPrecision(timeFormat) : picker === 'date' ? 'day' : picker}
|
precision={showTime && picker === 'date' ? getPrecision(timeFormat) : picker === 'date' ? 'day' : picker}
|
||||||
renderLabel={labelRenderer}
|
renderLabel={labelRenderer}
|
||||||
min={others.min || new Date(1000, 0, 1)}
|
min={rest.min || new Date(1000, 0, 1)}
|
||||||
max={others.max || new Date(9999, 11, 31)}
|
max={rest.max || new Date(9999, 11, 31)}
|
||||||
onConfirm={(val) => {
|
onConfirm={(val) => {
|
||||||
handleConfirm(val);
|
handleConfirm(val);
|
||||||
}}
|
}}
|
||||||
|
@ -15,12 +15,11 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
const MobilePicker = connect(
|
const MobilePicker = connect(
|
||||||
(props) => {
|
(props) => {
|
||||||
const { value, onChange, options = [], mode } = props;
|
const { value, onChange, disabled, options = [], mode } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [selected, setSelected] = useState(value || []);
|
const [selected, setSelected] = useState(value || []);
|
||||||
const [searchText, setSearchText] = useState(null);
|
const [searchText, setSearchText] = useState(null);
|
||||||
|
|
||||||
const filteredItems = useMemo(() => {
|
const filteredItems = useMemo(() => {
|
||||||
if (searchText) {
|
if (searchText) {
|
||||||
return options.filter((item) => item.label.toLowerCase().includes(searchText.toLowerCase()));
|
return options.filter((item) => item.label.toLowerCase().includes(searchText.toLowerCase()));
|
||||||
@ -38,9 +37,9 @@ const MobilePicker = connect(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div contentEditable="false" onClick={() => setVisible(true)}>
|
<div contentEditable="false" onClick={() => !disabled && setVisible(true)}>
|
||||||
<Select
|
<Select
|
||||||
placeholder={t('Select')}
|
disabled={disabled}
|
||||||
value={value}
|
value={value}
|
||||||
dropdownStyle={{ display: 'none' }}
|
dropdownStyle={{ display: 'none' }}
|
||||||
multiple={mode === 'multiple'}
|
multiple={mode === 'multiple'}
|
||||||
|
@ -208,7 +208,7 @@ export default class DateFieldScheduleTrigger {
|
|||||||
const modExp = fn(
|
const modExp = fn(
|
||||||
'MOD',
|
'MOD',
|
||||||
literal(
|
literal(
|
||||||
`${Math.round(timestamp / 1000)} - ${db.sequelize.getQueryInterface().quoteIdentifiers(tsFn(field))}`,
|
`${Math.round(timestamp / 1000)} - ${tsFn(db.sequelize.getQueryInterface().quoteIdentifiers(field))}`,
|
||||||
),
|
),
|
||||||
Math.round(repeat / 1000),
|
Math.round(repeat / 1000),
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user